Using WebLogic JNDI
Overview of JNDI in the WebLogic FrameworkIn the enterprise, naming and directory services -- the ability of your application to locate an object or service that may be anywhere on the network -- play an important part in building a distributed application. Particularly in a clustered environment, where clients may connect arbitrarily to any machine, the environment must have a naming service that can be depended upon to locate any service object requested no matter its physical location. JNDI is a standard Java interface for accessing distributed services and data by name. It provides a portable, unified interface for services that need naming and directory services. The JNDI specification was defined independently of any specific naming or directory service implementation, but a developer can use it to access different, multiple naming and directory services such as LDAP, NDS, and NIS (YP). The JNDI specification also includes a specification for a service provider interface (SPI) to provide access into such heterogeneous service providers. WebLogic JNDI is a multitier implementation of the Java Naming and Directory Interface. Through WebLogic's implementation of the JNDI specification, introduced with version 3.0, a client can access any JNDI service provider that is accessible to the WebLogic Server. This effectively gives clients seamless access to enterprise services and data without any knowledge of physical location. JNDI distinguishes between naming services and directory services:
Currently there are JNDI implementations provided by JavaSoft for the NIS naming service and the LDAP directory service. WebLogic JNDI also implements a naming provider that allows clients to access WebLogic services. This document assumes a basic understanding of naming and directory services and of the JNDI specification, which is available from JavaSoft. Included in this document is an overview and implementation guide for WebLogic's implementation of JNDI within the WebLogic framework. WebLogic's implementation of JNDI supplies an SPI (Service Provider Interface) for access to all of WebLogic's services. The SPI includes methods to give clients access to the WebLogic name services, methods to make objects available in the WebLogic name system, and methods to retrieve objects from the WebLogic name system. WebLogic clusters are supported by a replicated cluster-wide JNDI tree that provides access to both replicated and pinned RMI and EJB objects. The integrated naming and directory services provided by WebLogic JNDI may be used by many other WebLogic services. WebLogic RMI, for example, can bind and access remote objects by both the standard RMI methods as well as by JNDI methods. The WebLogic JNDI APIWebLogic's implementation of JNDI is based on the JavaSoft JNDI 1.2 and SPI specifications, available from JavaSoft. This document assumes knowledge of the specification and the use of the JNDI javadocs from JavaSoft, available online at JavaSoft. WebLogic JNDI API reference
Class java.lang.Object Class weblogic.jndi.Environment Interface weblogic.jndi.WLContext Class weblogic.jndi.WLInitialContextFactory
Overview of the WebLogic JNDI APIThe WLInitialContextFactory provides the public WebLogic API for accessing and using WebLogic's JNDI implementation. In general, the WLInitialContextFactory interface sets your InitialContext to use WebLogic JDNI services. You use WebLogic JNDI services for two purposes:
Using WebLogic JNDI from a clientA client may access objects in the WebLogic framework by creating an InitialContext within the WebLogic name system, and then retrieving objects by name from within that system. The two basic operations are creating an initial context and looking up objects in the name space.Creating a ContextTo access a JNDI service provider using WebLogic JNDI, you create an InitialContext and pass it a context factory, which creates the context for a certain provider. In the WebLogic case, you will use the WebLogic context factory, weblogic.jndi.WLInitialContextFactory, as an argument. Once the context is created, it provides client access to naming services within a naming system, through a WebLogic as name service provider. To create a WebLogic context from a client, you must minimally specify this factory as the initial context factory, and the URL of a WebLogic Server in the JNDI environment as properties passed to the constructor of the InitialContext. There are two processes for creating an initial context. One uses a Hashtable for setting properties, and the other uses a WebLogic Environment object to pass properties to the initial context. Both methods require setting property values. We'll discuss the properties first, and then explain how those properties get set in two methods of creating a context: We'll also show how to create a context for use with server-side objects that will operate exclusively within the same VM as the server: Properties that affect the context creationYou will need to set parameters to create a Context. The initial context factory uses the various properties to customize your InitialContext for a specific environment. You can set these properties either by using a Hashtable or by using the setXXX() methods associated with WebLogic Environment object. These properties (name/value pairs) determine how the WLInitialContext factory creates the Context. Set them either using a Hashtable, or using setXXX() methods in the WebLogic Environment class:
You can use the same properties on either a client or a server. If on the server, a local Context will be used. If on a client or another server, the Context will delegate to a remote Context running on the server specified by the java.naming.provider.url property. The following code illustrates how to obtain an InitialContext for the WebLogic naming service, using the properties java.naming.factory.initial (for which we substitute the constant Context.INITIAL_CONTEXT_FACTORY) and java.naming.provider.url (for which we substitute the constant Context.PROVIDER_URL): Context ctx = null; Hashtable ht = new Hashtable(); ht.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); ht.put(Context.PROVIDER_URL, "t3://localhost:7001"); try { ctx = new InitialContext(ht); // Use the context in your program } catch (NamingException e) { // a failure occurred } finally { try {ctx.close();} catch (Exception e) { // a failure occurred } } Note that you should always close a context when you have finished working with it, just as you close database and client connections and other finite resources. We recommend that you do so in a finally{} block, and that you wrap your close() method in a try{} block. If you attempt to close a Context that was never instantiated because of some other error, your program will throw an Exception that you should catch and deal with. If the code is written for an application (instead of an applet, which requires that you set properties explicitly), you can create the initial context simply by specifying the JNDI properties as system properties and using the default constructor, as in: try { Context ctx = new InitialContext(); } catch (NamingException e) { // a failure occurred } finally { try {ctx.close();} catch (Exception e) { // a failure occurred } } Note that this method of setting properties cannot be used in an applet, because of restrictions in the sandbox security model. There are other WebLogic-specific properties that you set to configure security parameters and that affect how objects are bound into the cluster-wide JNDI tree. Note that objects may be replicated across the tree or "pinned" to a particular server for use from within the cluster. These properties are identified by constants in the weblogic.jndi.WLContext class. There is more on JNDI-related clustering issues in the Developers Guide Using WebLogic Clusters. Creating a Context using a HashtableYou can create an InitialContext with a Hashtable in which you have put several properties (name/value pairs) that are used during the creation of the Context. Initially, you pass the Hashtable to the constructor for InitialContext; at that point, the property java.naming.factory.initial is used to choose how the initial Context will be created. (In these examples, we use the constants found in the Context class for all property names.) To use WebLogic JNDI, you must always set this property to weblogic.jndi.WLInitialContextFactory, which identifies the factory that will actually create your Context. This part of the creation of an InitialContext is invariable. You set this property using the property name java.naming.factory.initial. Note that in this example, we use the constant Context.INITIAL_CONTEXT_FACTORY. This property (name/value pair) specifies the factory used for creating the context. To gain access to WebLogic naming services, you must specify this property as weblogic.jndi.WLInitialContextFactory.
This completes the first step in the creation of a Context, using the
property java.naming.factory.initial, which you set to Associating a principal with a thread
Creating a Context using a WebLogic Environment objectThe simplest way to create an initial context is by using a WebLogic Environment object. Although the Environment object is WebLogic-specific, it offers you two really nice advantages: a set of defaults, which cuts down on the code you have to write, and some convenience "setter" methods that provide compile-type type-safety. The type-safety setXXX() methods can save you time both writing and debugging. Now that you understand all about properties that are passed to the initial context, you can see that setting up these properties requires a lot of typing, and, in the end, those property names and values are just Strings that you might have mistyped or misnamed. The Environment object reduces the chance you will make such mistakes. Here are a list of properties that the Environment method supplies setXXX() methods for. These constants come from both the javax.naming.Context class, and from the weblogic.jndi.WLContext class, which supplies Context parameters for WebLogic-specific features associated with clustering and security.
The Environment comes with a set of defaults:
This means that if you want to set up an initial context with these defaults, you can just write: Environment env = new Environment(); Context ctx = env.getInitialContext(); Or if, for example, you want to set just a server to a DNS name for client cluster access, you might write: Environment env = new Environment(); env.setProviderURL("t3://myweblogiccluster.com:7001"); Context ctx = env.getInitialContext(); Creating a context from within a server-side objectYou may also need to create a Context from an object that is instantiated in the WebLogic Server's VM, like an EJB or RMI object. There are a couple of considerations for server-side use; you do not need to specify things like provider URL and user, because these are already known to the VM creating the context. Server-side contexts will, of course, run in the context of the server itself. To create a context from within a server-side object, all you need to do is new InitialContext, for example: Context ctx = new InitialContext(); You do not need to specify a factory, or a provider URL; by default, the context will be created as a WebLogic context and will connect to the local naming service. Associating a principal with a threadIn order to perform work that requires authentication or access control, you must associate a principal, or user, with the thread that will perform the work. To do so, create a context with the appropriate principal and credential property values as described in the sections above. To disassociate a principal with the thread, close the context. When a thread is associated with a principal, that principal becomes the default for the thread. If any contexts are subsequently created without principal or credential properties, the principal associated with the thread will remain unchanged. While it is technically possible to share a single context among multiple threads, passing a context to another thread does not associate the principal used in creating the context with the new thread. The only way to associate a principal with a new thread is to create a new context within that thread. In addition, a context can only be closed within the thread with which it was created. For these reasons, it is strongly suggested that all work performed with a given context be handled in the same thread. A thread may be associated with only one principal at any given time. If multiple contexts are created within a single thread without closing any contexts, the thread will be associated with the principal used in the last context created. principal information is stored with the thread in a stack. If the last context is closed, the thread becomes associated with the principal used in creating previous context, and so on. Delegating to another service providerUsing WebLogic to support your calls to a delegate service provider provides several advantages:
To use another JNDI provider, you must provide a third property that specifies the property list to be used for connecting to the provider from the WebLogic Server. The following code illustrates this by creating an InitialDirContext that uses the WebLogic Server as an intermediate for accessing an LDAP directory service at bigfoot.com.
DirContext bigfootDirCtx = null; Hashtable delegateHT = new Hashtable(); // Use the LDAP provider that provided by Sun // to create a properties object for use with // a remote provider delegateHT.put(Context.INITIAL_CONTEXT_FACTORY, "sun.jndi.ldap.LdapCtxFactory"); delegateHT.put(Context.PROVIDER_URL, "ldap://ldap.bigfoot.com:389"); Hashtable ht = new Hashtable(); ht.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); ht.put(Context.PROVIDER_URL, "t3://localhost:7001"); // Pass the server properties as the value this property ht.put(WLContext.DELEGATE_ENVIRONMENT, delegateHT); ht.put(Context.SECURITY_CREDENTIALS, myUserInfo); try { bigfootDirCtx = new InitialDirContext(ht); // Do some work with this context } catch (NamingException e) { // a failure occurred } finally { try {bigfootDirCtx.close();} catch (Exception e) { // a failure occurred } } Note that the purpose of WebLogic JNDI in this case is to allow the client to access a directory service that it may not otherwise be able to access (because of security restrictions). When delegating to another service provider, WebLogic JNDI is supported by the underlying WebLogic framework for all its communications. There is a single connection from the client application to the WebLogic Server, and there is a single connection from the WebLogic Server to the server or backend process on which the delegate service provider depends for its objects. If you are accessing a JNDI/LDAP object through another service, however -- perhaps the object is being called as an Event action -- it will not lead to a performance/resource penalty for connection-related resources. WebLogic multiplexes all connections between a client and a WebLogic Server on a single socket, so you may have various types of traffic -- including JDNI objects, WebLogic Events, and JDBC -- all using the same connection. Consequently, the overhead for your client application's connections is always the same, no matter which or how many of the enterprise APIs your client program uses. WebLogic creates the delegated context on the WebLogic Server and wraps it in a remote object that implements the same interface, which then delegates its calls to the original context. The remote wrapper is exported to the client. When the client invokes a method on the remote wrapper, the call to the context blocks and the method calls is propagated to the server, where the original delegated context method call is invoked; that is, the only the client thread blocks. After the result is returned to the client, the client thread is unblocked and can continue processing with the result. Note that calls to list() and listBindings() do not require that all bindings be collected before returning; in this case, the remote context returns a special NamingEnumeration that pulls over a subset of the bindings with each call to next(). Looking up an objectOnce you have obtained a Context, you can use it to look up and retrieve named objects. You look up an object by the name under which it was bound when it was first placed in the name system. The following code retrieves an Enterprise Java Bean named "SeviceBean": try { ServiceBean bean = (ServiceBean)ctx.lookup("ejb.serviceBean"); } catch (NameNotFoundException e) { // binding does not exist } catch (NamingException e) { // a failure occurred } Using JNDI in a distributed application environmentFor an object to be useable in a distributed environment, it must be bound to a name in the name system so that clients of the name system have an unambiguous way to ask for a reference to the object. Then a client calls the Context.lookup() method with the name as an argument, and is returned an object. JNDI provides several ways to hook into the object retrieval machinery so that the implementor of a naming service can control what object is ultimately returned to the client performing a lookup. Objects can be stored as references (which encapsulate a factory for reconstructing the object on retrieval). It is also possible to specify an object factory that will be given the opportunity to transform objects retrieved from a naming service. When a client retrieves an object from the naming service, the object should be useable in a meaningful way. If the client retrieves a Printer object, for example, the client should be able to use this object to print. In a distributed environment, this requires that objects returned by lookup can be transported from the server to the client in a form that preserves their functionality. For simple objects that have no state, this can be achieved by returning an object that is serializable (that is, that implements java.io.Serializable). Whenever the lookup() method returns a serializable object to a client, the client actually receives a copy of the original object. Practically, when binding from the client, only Remote objects or simple serializable Objects that represent values can be bound to a name (in effect, a replacement for calls to RMI's Naming.bind()). Making an object available by binding it to a nameAfter an object is bound to a name in a name system, any client that has access to that name system can retrieve it. This implies that all objects bound in the name system should be useable in a distributed environment; that is, the object that one client "binds" into the naming tree must be useable by any clients that can look it up. At the least, such a concept of usability requires that the bound object be serializable. (In some cases, the object must also have a remote component, which we cover in the next section). Here we illustrate the simplest case of making an object available to clients of a name system in a distributed environment, by demonstrating how to bind a string in the WebLogic name space. Because String implements java.io.Serializable, any client that looks up the object will get a copy of the string. try { ctx.bind("testMessage", "this is a test"); } catch (NamingException e) { // a failure occurred } Making an object transportableFor objects that have state or refer to a shared resource, a remote object that is transportable should be returned -- that is, an object that implements java.rmi.Remote, weblogic.rmi.Remote, or in some other way provides access to an object that resides on a remote VM. A Printer is an example of an object for which a remote implementation is natural. If the printer object bound in the namespace implements Remote, a lookup will cause a remote stub to be returned to the client. This stub will delegate to the actual implementation, which resides in the VM in which the actual object was created. Here, we refer to objects that can be passed to and effectively used in the client VM as transportable objects. There are two ways to ensure that an object returned from a lookup (or search) is transportable. The first is to bind only transportable objects in the namespace. We recommend this approach whenever possible; it is simple and direct. Since every object bound in the namespace is transportable, any object can be passed freely between VMs without exception. The second way to ensure that an object returned by a call to Context.lookup() is transportable is to use object factories to transform non-transportable objects bound in the namespace into transportable objects that can be passed on to the client. This approach may be required in cases where it is not possible to ensure that objects bound in the namespace are transportable. A TransportableObjectFactory provides one method that takes a non-transportable object and returns a transportable representative of that object. It must implement the following interface: interface TransportableObjectFactory { Object getTransportableInstance(Object boundObject, Properties props) throws Exception; } The getTransportableInstance() method is called with an object being retrieved from the namespace as its argument. It can either return null, which signals that it doesn't handle objects of the given type, or a new object that will act as a transportable representative of the bound Object. The new object must implement java.io.Serializable so that the object can be successfully returned to the client. Transportable factories are registered with the WebLogic Server by setting the weblogic.jndi.transportableObjectFactories server property in the weblogic.properties file. (For more information on the properties file, read the WebLogic Administrators Guide document, Setting WebLogic properties.) This property should contain a comma-separated (,) list of classes that implement the TransportableObjectFactory interface. Whenever WebLogic JNDI retrieves an object from a namespace, it will call each factory in the list in succession until one of the factories creates a new object. If all of the factories return null for the bound object, the bound object is passed to the client in whatever state it exists.
WebLogic controls access to internal resources like JNDI through ACLs set up in the WebLogic Realm. Entries for ACLs in the WebLogic Realm are listed as properties in the weblogic.properties file. The access control list name for JNDI is weblogic.jndi.service, where service is the name of an object or service. You can set the Permissions "lookup", "modify", and "list" for JNDI by entering a property in the properties file. If you do not set an ACL for JNDI, all permissions are by default granted to everyone. Here is a breakdown of how these permissions map to actual JNDI methods:
In the first two examples, we illustrate how you might set ACLs for looking up or modifying RMI objects. Peter, Brown, and Eric can look up and use RMI objects, but only the system user can change RMI objects. The third example shows how you might set an ACL for listing bound objects in the "myapps" context. Only Peter, Brown, and Eric can obtain a list of objects bound to the "myapps" context. Example: |
|
Copyright © 2000 BEA Systems, Inc. All rights reserved.
|