[ Team LiB ] |
JNDI and WebLogic ServerWebLogic 8.1 provides a SPI implementation as specified in JNDI Specification 1.2.1. This enables Java clients to connect to WebLogic Server using standard JNDI calls. Clients can get access to WebLogic name services and make objects available, as well as retrieve them, in the WebLogic namespace. The naming service implementation of WebLogic uses a JNDI tree. Each WebLogic Server instance maintains its own copy of the JNDI tree where the object bindings that resolve into this server and other servers in the cluster are stored. A Java client that wants to access services of an object that's already been loaded into a WebLogic Server's JNDI tree would typically perform the following tasks:
Establishing an InitialContext to the WebLogic ServerThis is the first step in accessing a bound object in a WebLogic namespace. The bootstrap context that an application will obtain is known as the InitialContext. The InitialContext is obtained from an InitialContext factory. This factory uses a few properties to identify the WebLogic Server that the context needs to point to. Some important properties that are used to customize the InitialContext for connecting to a WebLogic Server are as follows:
You can look up the InitialContext by setting the properties using a Hashtable, by using the environment where the client is executing, or by using a WebLogic environment object. We look at these three mechanisms in the following subsections. Obtaining the InitialContext Using a HashtableTo create an InitialContext object using a Hashtable, identify the various properties discussed in the previous section in a Hashtable instance, and pass it as a parameter to the constructor to the InitialContext. The following code snippet illustrates such use: Hashtable props = new Hashtable () ; props.put( Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory" ) ; props.put( Context.PROVIDER_URL, "t3://localhost:7001" ) ; Context ctx = null ; boolean contextInitialized = false ; try { ctx = new InitialContext( props ); contextInitialized = true ; } catch ( NamingException ne ) { // Unable to obtain InitialContext. // handle the NamingException here. ne.printStackTrace( ); } // The initial context can be used here. You can find this example in ch08/com/wlsunleashed/jndi/Example1.java. This example obtains the InitialContext object from WebLogic Server and then looks up javax.transaction.UserTransaction. To execute this example, start the WebLogic Server instance in your local host, listen in port 7001, and execute the corresponding class. Obtaining the InitialContext Using the Client's EnvironmentThe properties can be initialized in the client's environment, thus avoiding the need to hardcode the values for these properties inside the client's code. To do this, the following environment variables must be set. If any of the properties isn't set, its default value (if any) is assumed:
These properties may be set in the Java runtime's system properties, or in cases of an applet, in the applet's parameter list. Another way of setting these properties in the JNDI environment is by the use of resource files. A resource file is a flat file containing key-value pairs that define the JNDI's environment. The keys are the different properties that have been discussed in this section. This file should be named jndi.properties and should be available in the application's class path. In addition to this, JNDI also looks for the jndi.properties file under the lib/ subdirectory under java.home. All the readable resource files are loaded into the application's environment. In most cases, if the same property is defined in different resources, the first value found is used. In a few other cases, where it makes sense, the values found in the different resource files are concatenated. After these properties have been set, you can initialize the InitialContext simply by invoking its default constructor: Context ctx = new InitialContext() ; You can find this example in ch08/com/wlsunleashed/jndi/Example2.java. This example obtains the InitialContext object from the WebLogic Server and tries to looks up javax.transaction.UserTransaction. To execute this example, start the WebLogic Server instance in your local host, listen in port 7001, and execute the corresponding class. Pass in the different fields discussed in this section as command-line parameters. For example, you can execute the class file using the following command line: C:> java -Djava.naming.factory.initial= weblogic.jndi.WLInitialContextFactory -Djava.naming.provider.url=t3://localhost:7001 com.wlsunleashed.jndi.Example2 Experiment with this example a little more by creating a jndi.properties file and putting it in your classpath. Now execute this class without the command-line arguments. You'll get the same results. Perform the test again, but this time provide the command-line arguments along with the jndi.properties file. Make the port number in the command line incorrect. You'll notice that the value provided in the command line is used, and the class file errors out. It's quite easy to notice that the command line gets precedence over the jndi.properties file. Obtaining the InitialContext Using WebLogic's Environment ObjectWebLogic provides a weblogic.jndi.Environment class, which can be used to obtain the InitialContext. This method offers certain advantages, at the expense of making the code specific to WebLogic. It provides with convenient set() methods to set the various properties. This ensures type safety and identifies problems during compile time. This class also provides default values to several properties, and predefined constants for certain values.
This enables the programmer to create a default Environment object and modify only the values for which the defaults won't work. After setting the appropriate values, the Environment object can be requested to provide the InitialContext for further use. The following code snippet can be used to create an InitialContext object using WebLogic's Environment object: Environment env = new Environment () ; env.setInitialContextFactory( Environment.DEFAULT_INITIAL_CONTEXT_FACTORY ); env.setProviderURL("t3://localhost:7021") ; env.setSecurityPrincipal( "johnny" ); env.setSecurityCredentials( "begood" ); Context ctx = env.getInitialContext () ; You can find this example in ch08/com/wlsunleashed/jndi/Example3.java. This example obtains the InitialContext object from the WebLogic Server and looks up javax.transaction.UserTransaction. To execute this example, start the WebLogic Server instance in your local host, listen in port 7001, and execute the corresponding class. Precedence of the Various Mechanisms of Creating InitialContextIn the preceding few subsections, we saw three mechanisms for creating the InitialContext object. So, how do these mechanisms play out as far as their precedence goes? Passing in a Hashtable or an Environment object always takes first precedence. If neither of these is passed in, command-line properties are considered, and if passed, are used. If command-line properties are not present, the jndi.properties file in the classpath and then in ${JAVA_HOME}/lib is looked up and used. If none of these mechanisms are available, the default values, if any, are used. Obtaining the InitialContext from a Server-Side ObjectIt's often necessary to access the WebLogic JNDI tree from objects that have been instantiated inside the server JVM. This use can be seen while accessing EJB instances from within a servlet, for instance. To do this, you don't need to specify the INITIAL_CONTEXT_FACTORY or the PROVIDER_URL property. The InitialContext is built to the server where it's being requested. SECURITY_PRINCIPAL and SECURITY_CREDENTIALS must be provided only if you want to sign in as a specific user. To create an InitialContext from within WebLogic Server, you can simply do the following: Context ctx = new InitialContext (); The correct InitialContext for WebLogic Server is returned. Querying the WebLogic JNDI TreeAfter the InitialContext has been established, the client application can now query from WebLogic's JNDI tree. To do this, the object should already be available in the tree. To query a named object, use the Context.lookup() method and pass the name of the binding to it: AccountBean anAccountBean = null ; try { // Obtain the InitialContext first Context ctx = getInitialContext() ; anAccountBean = ctx.lookup( "java:comp/env/ejb/AccountBean" ); } catch ( NameNotFoundException nfe ) { // there is no object bound under the given name } catch ( NamingException ne ) { // there has been a failure while doing this operation. } // continue to use the anAccountBean here. Examples 1, 2, and 3 in the directory ch08/com/wlsunleashed/jndi demonstrate such a lookup. You can also list the contents of the JNDI tree or a subtree using the Context interface. Context.listBindings() method accepts the name of a context, and returns an Enumeration containing the bindings in the context: try { // Obtain the InitialContext first Context ctx = getInitialContext() ; System.out.println("Listing Bindings under javax.transaction"); NamingEnumeration enum = ctx.listBindings("javax.transaction"); while (enum.hasMore()) { System.out.println( enum.next() ); } System.out.println("listing done"); } catch ( NameNotFoundException nfe ) { // there is no object bound under the given name } catch ( NamingException ne ) { // there has been a failure while doing this operation. } This code snippet, when executed, produces the following output: Listing Bindings under javax.transaction UserTransaction: weblogic.transaction.internal.ClientTransaction ManagerImpl :ClientTM[myserver+192.168.1.102:7001+mydomain+t3+] TransactionManager: weblogic.transaction.internal.ClientTransaction ManagerImpl :ClientTM[myserver+192.168.1.102:7001+mydomain+t3+] listing done This example can be found in ch08/com/wlsunleashed/jndi/Example4.java. Updating the WebLogic JNDI TreeUsing the Context interface, you can also perform several operations apart from a lookup. This section discusses a few of these update operations. While reading this part of the chapter, keep in mind that as a J2EE developer, one rarely if ever performs these kinds of updates on the JNDI tree. WebLogic performs these updates automatically when resources are deployed, and are destroyed when the server (and hence the JNDI tree) shuts down. All you have to do is to configure these resources in the appropriate configuration descriptor. If you find yourselves in a situation where you have to store data somewhere, you'll be better off considering the use of a database to store your data. Also remember that the objects that are typically stored on the JNDI tree have a small footprint. Create New BindingsTo create a new binding in the WebLogic JNDI tree, use the Context.bind() method. This method accepts the name of the new binding, along with an object that has to be bound to this name. Note that the object passed in must be serializable; that is, the class representing the object should implement the java.io.Serializable tagging interface. This interface does not contain any methods that the class needs to implement—it simply tells the VM that objects of this class can be serialized. The following code snippet creates an object of type SimpleObject and binds it under the name TestBinding: SimpleObject myObject = new SimpleObject(); Context c = getInitialContext(); System.out.println("Binding "+ myObject + " to " + getName() ); c.bind(name, myObject); System.out.println ("Bind : Done" ); Delete an Existing Binding (unbind)Now let's delete the binding that we created in the previous section. To do this, use the Context.unbind() method. This operation removes the binding on the JNDI tree and the object is no longer accessible from this tree. Context c = getInitialContext(); c.unbind(getName()); The sample program ch08/com/wlsunleashed/jndi/Example5.java demonstrates how to create and delete object bindings. Create a SubcontextIt's often useful to bind objects under subcontexts rather than under the root context. This allows the JNDI tree to be organized. For example, consider the two bindings that you may find in the JNDI tree of your WebLogic installation: weblogic.jms.ConnectionFactory handles JMS connections to your WebLogic Server instance, whereas weblogic.transaction.UserTransaction enables you to manage transactions using JTA. These are two noticeably different operations, and thus are stored in different subcontexts: weblogic.jms and weblogic.transaction. To create a subcontext for the use of your application, use the Context.createSubContext() method. The following code snippet creates a subcontext named wlsunleashed under the root context: Context ctx = getInitialContext(); ctx.createSubcontext("wlsunleashed"); NOTE Creating a subcontext named wlsunleashed.examples is a two-step process. You must first create the subcontext wlsunleashed, followed by the subcontext wlsunleashed.examples. Destroying a SubcontextJust as subcontexts can be created, they can be destroyed. To do this, you use the Context.destroySubcontext() method. The following code snippet destroys the wlsunleashed subcontext: Context ctx = getInitialContext(); ctx.destroySubcontext("wlsunleashed"); Remember that the destroySubContext method is not recursive. In other words, if a subcontext has bindings or other subcontexts within, the destroy operation fails with a ContextNotEmptyException. The sample program ch08/com/wlsunleashed/jndi/Example6.java demonstrates how to create and destroy a subcontext. Closing the ContextAfter the InitialContext has been used, the context isn't necessary any more. It's recommended that you close the context at this point. Closing the context simply releases any resources that have been allocated while opening and using the context. if ( ctx != null && contextInitialized ) { ctx.close () ; contextInitialized = false; } Browsing the JNDI Tree from the WebLogic ConsoleWebLogic Server enables you to browse through its JNDI tree. This can be useful while debugging your application, especially if your application needs to create bindings. You can access the tree using the command line as well as using the administration console. To access the JNDI tree using the command line, invoke the weblogic.Admin Java class, with the operation LIST. java weblogic.Admin -username system -password password LIST To view the JNDI tree using the console, log on to the WebLogic Server console. Assuming that WebLogic Server is running in your localhost at port 7001, you can access the console by typing in http://localhost:7001/console in your Web browser. If you have a different server name and/or port number, modify the URL accordingly. Navigate to the link mydomain→Servers→myserver. (If you have different names for your domain and/or server, modify the link accordingly.) Right-click the server name node (myserver) and click on View JNDI Tree. You can see a screenshot of this menu in Figure 8.2. Figure 8.2. Click on View JNDI Tree to browse the JNDI tree.This opens the JNDI tree of the server in a second browser window. The subcontexts are listed in a tree-like structure on the left pane, with the leaf-nodes representing the names of the bindings. If you have subtrees without bindings, the leaf nodes will not represent bindings. Clicking a binding name displays information about the object bound to this name on the right panel. This includes a String representation of this object. Figure 8.3 shows a sample screenshot of the JNDI tree. Figure 8.3. Browsing the JNDI tree of a WebLogic server.If you've configured a J2EE component and service such as EJBs, JMS, JDBC, or RMI in the appropriate screens on the console, you'll be able to see that binding here. As you'll notice, the name of the binding you see here is the same name that you specified for the JNDI Name parameter while creating the component or service. Note that you cannot add objects directly to the JNDI tree through this screen. Private JNDI TreeWebLogic Server also uses a private JNDI tree, which is visible only for certain components. This tree cannot be browsed using the WebLogic Server console. Only the target component has permissions to access this tree. For example, an EJB may refer to a DataSource object to access data from the database. To provide a level of abstraction, the EJB implementation itself will use a JNDI name such as java:comp/env/jdbc/DataSourceName. However, your DataSource object might have been loaded onto the JNDI tree under the name com.company.data.DataSourceName. The mapping between the internal name and the actual JNDI name is done in the configuration files for the EJB. Such JNDI bindings are present in the private tree of that EJB. No other object will have access to this binding. Any time a JNDI context begins with java:comp/env, that binding is present in a private tree. You'll learn more about the use of such private trees in Part V, “Using Enterprise JavaBeans in WebLogic Applications,” in this book. JNDI and WebLogic ClustersWebLogic clusters are used to provide high performance and fault tolerance. Several servers may participate in a WebLogic cluster. For clients to access the cluster's services, they first access the clusterwide JNDI tree. However, in practice, the clusterwide JNDI tree is not a single tree. Each server in the cluster maintains its own copy of the JNDI tree. The clustered bindings from different servers are replicated across to all the servers, keeping them in sync. This way of maintaining the clusterwide tree offers better performance and transparency. To a client, each naming service is identical, regardless of which clustered server it's connected to—hence the concept of a single clusterwide JNDI tree. When the servers start, the local implementations of the services (EJB, JMS, and so on) are bound to the local JNDI tree. After this, the RMI stubs of the services are sent to the other servers in the cluster by way of IP multicast. Using IP multicast ensures that all servers are updated using a single network message. When a server receives the message, it updates its copy of the JNDI tree with the received information. The RMI stub is cluster-aware; that is, it has information about which servers this service has been deployed to. When the service is deployed to one of the servers, the stub is updated with information about that server, and another multicast message is sent out, enabling the updates to the other servers. This enables the cluster to pick a server when the service is requested. Figure 8.4 shows the JNDI tree aggregation process. Figure 8.4. JNDI binding propagation between servers in a WebLogic cluster.Because of this mechanism, it's possible for the JNDI tree of a server to get out of sync with the other servers for a brief period of time. This can happen when an object is added to a server's tree, but the information has not yet been propagated to the other servers. But this should not pose a major problem because JNDI tree updates must be sent only in cases of a bind, rebind, or an unbind, and these operations are, and should be, relatively rare during the normal operations of the server. These operations are normally performed at server startup. Replicated Versus Nonreplicated BindingsRMI objects (such as EJBs) are usually deployed as replicated objects, which ensures that any server can act as the naming server for the service, whether or not that service is deployed in that server. If the service is deployed in several servers in the cluster under the same name, the cluster can choose any of the servers to service client requests. This provides for high performance and fault tolerance (only for idempotent services) in the cluster. When such objects are bound to a name in the JNDI tree, the information is propagated to the other servers in the cluster, updating their copies of the JNDI tree. At the same time, it's possible for you to bind an object into the server's JNDI tree without replicating this information. This is quite useful in situations in which you want applications residing in each of the servers to have their own versions of objects bound in their respective JNDI trees, but you do not want them to share the data among the application instances. For example, an EJB application might want to read some server-specific properties out of a flat file and load them into the JNDI tree for later use. Here each deployment would use the same JNDI name, but bind its own Properties object into the JNDI tree of its server. These are known as non-replicated bindings. Unless you choose the object not to be clustered, the binding is replicated across to the other servers. To tag a binding as a non-replicated, set the value of the WLContext.REPLICATE_BINDINGS property to false while creating the context. Alternatively, you can also use WebLogic's Environment object, and use the method setReplicateBindings(boolean) while creating the InitialContext, to achieve the same result. Naming Conflicts in a ClusterImagine that two servers participate in a cluster. First, you bind an object of type A to server-1 under the name MyObject. This object is bound as nonreplicated, therefore, by definition, the binding information is not sent to all other servers. In the meantime, you bind a second RMI object of type B to server-2, under the name MyObject. Let's make this binding replicated. Server-2 sends out a multicast message to server-1 saying that it has received a new binding of type B under the name MyObject. Server-1 is now confused because the object type that's already bound under the name MyObject (A) is different from the one that's being bound to server-2. This situation is known as a conflict. Obviously, there's nothing that the server can do to work around such a conflict. WebLogic Server detects such conflicts and prints out messages to the system log files. Such a condition typically implies that WebLogic Server has been configured incorrectly. However, while using only nonreplicated bindings, there will not be any naming conflicts; that is, two servers in the cluster can have different objects using the same name bound to their JNDI trees. TIP Alternatively, you can also use only replicated bindings, and completely avoid nonreplicated bindings, which is a best practice in JNDI usage. |
[ Team LiB ] |