The JNDI SPI provides the means by which developers can write different naming and directory service providers and make them available so that the corresponding services are accessible from applications that use the JNDI API. A service provider is a set of modules that together satisfy JNDI API requests. In addition, because JNDI allows the use of names that span multiple namespaces, one service provider implementation may need to interact with another in order to complete an operation. The SPI provides methods that allow different provider implementations to cooperate to complete client JNDI operations.
This document describes the components of the SPI and explains how developers can build service providers for JNDI. It is assumed that the reader is familiar with the contents of the JNDI API document.
All service provider developers should read the "Security Considerations" section of the JNDI API document. It contains important issues that all developers using JNDI, especially those writing service providers, should consider.
There are several types of
implementations that sit beneath the JNDI API. A service provider
contains at a minimum a context implementation. A context
implementation implements the Context
interface or any
of its subinterfaces, such as DirContext
,
EventContext
, or LdapContext
. The
complexity of the implementation depends primarily on the
complexity of the underlying service, and secondarily on the number
of JNDI features that the implementation supports. Chapter 2 describes the details of
building a context implementation.
A context implementation can be accessed in different ways. The most common way is to access it from the initial context. Chapter 3 describes two ways that a context implementation can be accessed from the initial context: via an initial context factory and a URL context factory.
The JNDI architecture defines components/implementations that can be used to augment the behavior of context implementations. This allows users and applications to customize the implementation. These components are supported through factories. JNDI defines three types of factories and provides SPI methods that make use of them. These factories are described in Chapter 4.
The JNDI SPI is contained in the
javax.naming.spi
package. The following sections
provide an overview of the SPI. For more details on the SPI, see
the corresponding javadoc.
The information in this graphic is available in the API documentation.
The NamingManager
class
contains static methods that perform provider-related operations.
For example, it contains methods to create instances of objects
using Reference
, to obtain an instance of the initial
context using the java.naming.factory.initial
property, and to install ObjectFactoryBuilder
and
InitialContextFactoryBuilder
. The
DirectoryManager
class provides similar static methods
for DirContext
related operations.
InitialContextFactory
is the
interface for creating an initial context instance. See Section 3.1 for more details.
InitialContextFactoryBuilder
is the interface for creating InitialContextFactory
instances. See Section 3.3 for
more details.
ObjectFactory
is the
interface for supporting creation of objects using information
stored in the namespace. DirObjectFactory
is a
subinterface of ObjectFactory
for use by context
implementations that implement the DirContext
interface. See Section 4.1
for more details.
ObjectFactoryBuilder
is the
interface for creating object factories. See Section 4.1.4 for more details.
StateFactory
is the
interface for supporting converting objects into storable formats
supported by the naming/directory service.
DirStateFactory
is a subinterface of
StateFactory
for use by context implementations that
implement the DirContext
interface.
DirStateFactory.Result
is a class for holding a pair
of java.lang.Object
and Attributes
that
is returned by DirStateFactory.getStateToBind()
. See
Section 4.2 for more
details.
The Resolver
interface
defines a method for providers to implement that allows them to
participate in a federation for supporting extended interfaces to
Context
. See "Resolving Through to Subinterfaces of
Context" on page 10 for more details.
ResolveResult
is the return
value of calling Resolver.resolveToClass()
. It
contains the object to which resolution succeeded, and the
remaining name yet to be resolved.
One of the basic tasks in building a
service provider is to define a class that implements the
Context
interface or any of its subinterfaces. This
class is called a context implementation. The following
guidelines should be used for developing a context
implementation.
In general, any object passed as a
parameter to methods in the Context
interface (or
subinterfaces) and
NamingManager
/DirectoryManager
utility
methods is owned by the caller. In many cases, the parameter
eventually reaches a context implementation. Because the caller
owns the object, the context implementation is prohibited from
modifying the object. Furthermore, the context implementation is
allowed to maintain a pointer to the object only for the duration
of the operation and not beyond. If a context implementation needs
to save the information contained in a parameter beyond the
duration of the operation, it should maintain its own copy.
For purposes of parameter ownership, an
operation on a context instance is not considered to have completed
while any referrals generated by that operation are still being
followed, or if the operation returns a
NamingEnumeration
, while the enumeration is still in
use.
A context instance need not be reentrant. Two threads that need to access the same context instance concurrently should synchronize amongst themselves and provide the necessary locking.
However, different context instances must be safe for concurrent multithreaded access. That is, two threads each operating concurrently on their respective context instance should not need to synchronize their access. For example, even though two contexts might share the same resources (such as the same connection), it must be possible (and safe) for two separate threads to operate on each of those contexts without the threads having to do any explicit synchronization.
For purposes of concurrency control, an
operation on a context instance is not considered to have completed
while any referrals generated by that operation are still being
followed, or if the operation returns a
NamingEnumeration
, while the enumeration is still in
use.
The context implementation defines
implementations for each of the methods in the Context
interface or subinterfaces that the implementation supports.
If a method is not supported, it should
throw OperationNotSupportedException
.
For methods in the Context
interface or subinterfaces that accept a name argument (either as a
String
or a Name
), an empty name denotes
the current context. For example, if an empty name is supplied to
lookup()
, that means to return a new instance of the
current context. If an empty name is supplied to
list()
, that means to enumerate the names in the
current context. If an empty name is supplied to
getAttributes()
, that means to retrieve the attributes
associated with this context.
Appendix A contains an example context implementation that implements a flat, in-memory namespace.
JNDI encourages providers to supply
implementations of the Context
and its subinterfaces
that are natural and intuitive for the Java application programmer.
For example, when looking up a printer name in the namespace, it is
natural for the programmer to expect to get back a printer object
on which to operate.
Context ctx = new InitialContext(); Printer prt = (Printer)ctx.lookup(somePrinterName); prt.print(someStreamOfData);
Similarly, when storing an application's object into the underlying service, it is most portable and convenient if the application does not have to know about the underlying data representation.
However, what is bound in the underlying directory or naming services typically are not objects in the Java programming language but merely reference information which can be used to locate or access the actual object. This case is quite common, especially for Java applications accessing and sharing services in an existing installed base. The reference in effect acts as a "pointer" to the real object. In the printer example, what is actually bound might be information on how to access the printer (e.g., its protocol type, its server address). To enable this easy-to-use model for the application developer, the context implementation must do the transformation of the data to/from the underlying service into the appropriate objects in the Java programming language.
There are different ways to achieve this
goal. One context implementation might have access to all the
implementation classes of objects that a directory can return;
another context implementation might have a special class loader
for locating implementation classes for its objects. JNDI provides
the Reference
class as a standard way of representing
references. Applications and context implementations are encouraged
to use this class, rather than invent separate mechanisms on their
own. However, this does not preclude context implementations from
using their own mechanisms for achieving the same goal.
JNDI provides utilities for context implementations to use when reading/storing objects in the Java programming language in a format-independent way to the underlying service. This section describes these utilities. These utilities interact with components called object and state factories that do the actual transformations. These factories are described in Chapter 4.
JNDI provides the following methods that context implementations should use to transform data read from the underlying service into objects in the Java programming language:
Object NamingManager.getObjectInstance(Object refInfo, Name name, Context nameCtx, Hashtable env) throws Exception; Object DirectoryManager.getObjectInstance(Object refInfo, Name name, Context nameCtx, Hashtable env, Attributes attrs) throws Exception;
refInfo
is the data (representing the object) read
from the underlying service. name
is the name of the
object while nameCtx
is the context in which to
resolve name
. The
name
/nameCtx
pair can be used to obtain
more information about the object than is available from
refInfo
. env
is the environment of the
context from which getObjectInstance()
is being
invoked. attrs
is the collection of attributes read
from the directory about the object, usually in the same request
that was used to get refInfo
. It might not be the
complete collection of attributes if such was not requested.
The method in the
NamingManager
class should be used by context
implementations that implement the Context
interface,
while the method in the DirectoryManager
class should
be used by context implementations that implement the
DirContext
interface.
When constructing objects to be returned
for the following methods, the context implementation should call
getObjectInstance()
, or its own mechanism for
generating objects from the bound information, if it wants this
feature to be enabled in their contexts. (String overloads not
shown.)
javax.naming.Context.lookup(Name name) javax.naming.Context.lookupLink(Name name) javax.naming.Binding.getObject() javax.naming.directory.SearchResult.getObject()
For Binding
and
SearchResult
, the context implementation should either
pass an object that is the result of calling
getObjectInstance()
or its equivalent to the
constructor, or override the default implementation of
Binding
and SearchResult
so that their
getObject()
implementations call
getObjectInstance()
or its equivalent before
returning.
Here is an example. Suppose printers are
represented in the namespace using Reference
s. To turn
a printer Reference
into a live Printer
object, the context implementation would use the
NamingManager.getObjectInstance()
method. In this way,
the underlying service need not know anything specific about
printers.
Object lookup(Name name) { ... Reference ref = <some printer reference looked up from naming service>; return NamingManager.getObjectInstance(ref, name, this, env); }
In another example, suppose printers are
represented in the directory as a collection of attributes. To turn
a printer's directory entry into a live Printer
object, the context implementation would use
DirectoryManager.getObjectInstance()
.
Object lookup(Name name) { ... Attributes attrs = <read attributes from directory>; Reference ref = <construct reference from attributes>; return DirectoryManager.getObjectInstance(ref, name, this, env, attrs); }
JNDI provides the following methods that context implementations should use to transform an object before storing it in the underlying service:
Object NamingManager.getStateToBind( Object obj, Name name, Context nameCtx, Hashtable env) throws NamingException; DirStateFactory.Result DirectoryManager.getStateToBind( Object obj, Name name, Context nameCtx, Hashtable env, Attributes attrs) throws NamingException;
obj
is the object to be
stored in the underlying service. name
is the name of
the object while nameCtx
is the context in which to
resolve name
. The
name
/nameCtx
pair can be used to obtain
more information about the object than is available from
obj
. env
is the environment of the
context from which getStateToBind()
is being invoked.
attrs
is the collection of attributes that is to be
bound with the object. DirStateFactory.Result
is a
class that contains an object and a collection of attributes.
The method in the
NamingManager
class should be used by context
implementations that implement the Context
interface,
while the method in the DirectoryManager
class should
be used by context implementations that implement the
DirContext
interface.
Before storing an object supplied by the
application, the context implementation should call
getStateToBind()
, or its own mechanism for generating
information to be bound, if it wants this feature to be enabled in
their contexts. (String overloads not shown.)
javax.naming.Context.bind(Name name, Object o) javax.naming.Context.rebind(Name name, Object o) javax.naming.DirContext.bind(Name name, Object o, Attributes attrs) javax.naming.DirContext.rebind(Name name, Object o, Attributes attrs)
Here's an example of how a
Context
implementation supports
Context.bind
:
// First do transformation obj = NamingManager.getStateToBind(obj, name, ctx, env); // Check for Referenceable if (obj instanceof Referenceable) { obj = ((Referenceable)obj).getReference(); } if (obj instanceof Reference) { // store as ref } else if (obj instanceof Serializable) { // serialize } else { ... }
Here's an example of how a
DirContext
implementation supports
DirContext.bind
:
// First do transformation DirStateFactory.Result res = DirectoryManager.getStateToBind( obj, name, ctx, env, inAttrs); obj = res.getObject(); Attributes outAttrs = res.getAttributes(); // Check for Referenceable if (obj instanceof Referenceable) { obj = ((Referenceable)obj).getReference(); } if (obj instanceof Reference) { // store as ref and add outAttrs } else if (obj instanceof Serializable) { // serialize and add outAttrs } else if (obj instanceof DirContext) { // grab attributes and merge with outAttrs } else { ... }
As shown in these examples, a context
implementation might be able to store different types of objects
(Reference
, Serializable
, and
DirContext
). If the context implementation cannot
store Referenceable
objects directly and
getStateToBind()
returns such an object, the context
implementation should subsequently call
Referenceable.getReference()
and store the resulting
Reference
instead.
If a context implementation can store different types of objects, it should follow this order for the following common types:
This order is recommended because it is most likely to capture the intent of the caller of thebind()
/rebind()
method. For example, a
Reference
is Serializable
, so if you
performed the Serializable
check first, no
Reference
objects would ever be stored in the
reference format (that is, they would all be serialized).
When a context is given a string name argument, the name represents a composite name that may span multiple namespaces, or it may have only a single compound name component (which in turn may be made up of one or several atomic names) that belongs to a single namespace. The context implementation must determine which part of the name is to be resolved/processed in its context and pass the rest onto the next context. This may be done syntactically by examining the name, or dynamically by resolving the name.
When a context is given a
Name
argument, if it is an instance of
CompositeName
, then it will be treated as a composite
name. Otherwise, it will be treated as a compound name that is
implemented by the CompoundName
class or some other
compound name implementation.
A context participates in a federation by
performing the resolution phase of all of the context operations.
The lookup()
method must always be supported. Support
for other methods is optional, but if the context is to participate
in a federation, then the resolution implicit in all operations
must be supported.
Figure 1: Example of Resolving through Intermediate Contexts to Perform a bind().
For example, suppose a context does not
support the bind()
operation. When that context is
being used as an intermediate context for bind()
, it
must perform the resolution part of that operation to enable the
operation to continue to the next context. It should only throw
OperationNotSupportedException
if it is being asked to
create a binding in its own context. Figure 1 shows an example of
how the bind()
operation is passed through
intermediate contexts to be performed in the target context.
To invoke a DirContext
method (such as getAttributes()
), the application
first obtains an initial DirContext
, and then perform
the operation on the DirContext
.
DirContext ctx = new InitialDirContext(); Attributes attrs = ctx.getAttributes(someName);From the context implementation's perspective, in order to retrieve the attributes,
getAttributes()
might need to traverse
multiple naming systems. Some of these naming systems only support
the Context
interface, not the DirContext
interface. These naming systems are being used as intermediaries
for resolving towards the target context. The target context must
support the DirContext
interface. Figure 2 shows an
example of this.
Figure 2: Example of Resolving Through Intermediate non-DirContexts
In order for intermediate naming systems
to participate in the federation for extensions of
Context
, they must implement the Resolver
interface. The Resolver
interface is used by the JNDI
framework to resolve through intermediate contexts that do not
support a particular subinterface of Context
. It
consists of two overloaded forms of the method
resolveToClass()
. This method is used to partially
resolve a name, stopping at the first context that is an instance
of the required subinterface. By providing support for this method
and the resolution phase of all methods in the Context
interface, a context implementation can act as an intermediate
context for extensions (subinterfaces) of Context
.
public interface Resolver { public ResolveResult resolveToClass(Name name, Class contextType)
throws NamingException; public ResolveResult resolveToClass(String name, Class contextType)
throws NamingException; }
The resolution of a (multicomponent) composite name proceeds from one naming system to the next, with the resolution of the components that span each naming system typically handled by a corresponding context implementation. From a context implementation's point of view, it passes the components for which it is not responsible to the (context implementation of the) next naming system.
There are several ways in which the
context implementation for the next naming system may be located.
It may be done explicitly through the use of a
junction, where a name in one naming system is bound to a
context (or a Reference
to a context) in the next
naming system. For example, with the composite name
"cn=fs,ou=eng/lib/xyz.zip", the LDAP name "cn=fs,ou=eng" might
resolve to a file system context in which the name "lib/xyz.zip"
could then be resolved.
Alternately, the next naming system may
be located implicitly. For example, a context
implementation may choose the next naming system based upon
service-specific knowledge of the object that it has resolved. For
example, with the composite name "ldap.wiz.com/cn=fs,ou=eng", the
DNS name ldap.wiz.com
might name a DNS entry. To get
the next naming system beyond DNS, the DNS context implementation
might construct a context using SRV resource records found in that
entry, which in this case, happens to name an LDAP context. When
the next naming system is located in this fashion, JNDI composite
name separator is used to denote the boundary from one naming
system to the next, and is referred to as the implicit next
naming system pointer.
However the next naming system is located, the context implementation must hand the next naming system the remaining portion of the composite name to resolve.
In performing an operation on a name that
spans multiple namespaces, a context in an intermediate naming
system needs to pass the operation onto the next naming system. The
context does this by first constructing a
CannotProceedException
containing information
pinpointing how far it has proceeded. In so doing it sets the
resolved object, resolved name, remaining name, and environment
parts of the exception.2 (In the
case of the Context.rename()
method, it also sets the
"resolved newname" part.)
It then obtains a continuation
context from JNDI by passing the
CannotProceedException
to static method
NamingManager
.getContinuationContext()
public class NamingManager { public static Context getContinuationContext( CannotProceedException e) throws NamingException; ... }
The information in the exception is used
by getContinuationContext()
to create the context
instance in which to continue the operation.
To obtain a continuation context for the
DirContext
operations, use
Directory-Manager.getContinuationDirContext()
.
public class DirectoryManager { public static getContinuationDirContext( CannotProceedException e) throws NamingException; ... }
Upon receiving the continuation context, the operation should be continued using the remainder of the name that has not been resolved.
For example, when attempting to continue
a bind()
operation, the code in the context
implementation might look as follows:
public void bind(Name name, Object obj) throws NamingException { ... try { internal_bind(name, obj); ... } catch (CannotProceedException e) { Context cctx = NamingManager.getContinuationContext(e); cctx.bind(e.getRemainingName(), obj); } }
In this example, bind()
depends on an internal method, internal_bind(),
to
carry out the actual work of the bind and to throw a
CannotProceedException
when it discovers that it is
going beyond this naming system. The exception is then passed to
getContinuationContext()
in order to continue the
operation. If the operation cannot be continued, the continuation
context will throw the CannotProceedException
to the
caller of the original bind()
operation.
In some federation configurations, the result of resolution in one naming system does not indicate which is the next naming system. The only conclusion that the context implementation can draw is that resolution has terminated in the current naming system and should proceed to the next naming system.
For example, suppose the composite name "lib/xyz.zip/part1/abc" consists of two parts: "lib/xyz.zip", which names a file in ZIP format, and "part1/abc", which names an entry within the ZIP file. Although the resolution of "lib/xyz.zip" results in a file object, the desired result is a context in which to resolve names of ZIP entries. Similarly, another composite name could name an entry within a file in "tar" format, and the desired result of the resolution of the file component of the composite name would be a context in which to resolve tar entries.
In effect, any type of context might be federated beneath the file system namespace depending on the format of the files. Such relationships should be symmetric: it should be possible for the ZIP file context and other similar contexts to federate beneath other, non-file system namespaces. Furthermore, developers writing the file system context implementation and those writing the context implementations for the ZIP file context, the tar file context, or a context for some yet-to-be defined format, should be able to work independently.
To support this type of federation, JNDI
defines a special form of Reference
called an nns
reference ("nns" stands for "next naming system"). This
Reference
has an address with type nns
.
The address contents is the resolved object (in the above example,
the ZIP file). Continuing with the file system example, the file
system context implementation might create the nns reference as
follows:
RefAddr addr = new RefAddr("nns") { public Object getContent() { return theFile; } }; Reference ref = new Reference("java.io.File", addr);
Next, the context implementation
constructs a CannotProceedException
(as with the
junction case) by using the nns reference as the resolved object,
and a resolved name consisting of the resolved file name and an
empty component. The empty component is being used as an implicit
next naming system pointer and indicates that the resolution has
succeeded to the point of resolving the next naming system. (Notice
how the values of the resolved object and resolved name are
matched.) The context impementation then passes the
CannotProceedException
to
getContinuationContext()
.
As with any resolved object in a
CannotProceedException
,
getContinuationContext()
searches for a context
implementation that accepts this nns reference. The ZIP file
context implementation, for instance, might accept an nns reference
and other information provided, such as the name of the file
(relative to a given context). If the context implementation
determines that the file is a ZIP file, it would then construct a
context for resolving names within that file.
Central to the JNDI SPI's framework for
federation is the CannotProceedException
. A
Cannot-ProceedException
contains information such as
the resolved name/object and remaining name, inherited from the
NamingException
superclass. In addition, a
CannotProceedException
also contains fields for the
"alt" name and "alt" name context. While the resolved name from
NamingException
is the full composite name (relative
to the starting context of the operation), alt name is the resolved
name relative to the alt name context. That is, alt name might not
necessarily be the same as the resolved name. Alt name and alt name
context are used as arguments to
NamingManager
/DirectoryManager.getObjectInstance()
.
They allow the factories that are called by this method to obtain
more information about the resolved object (for example, it could
be used to get a special attribute about the object). These
factories are described in Chapter
4.
While the emphasis of the JNDI SPI framework is on "looking forward" and trying to find the next naming system, some context implementations, once located, need to "look back" the resolution chain to obtain contextual information. For example, a particular context implementation that is federated off of a host naming system might be designed such that the only means by which it can find out host information is to ask its (possibly not immediate) superior naming system. To do that, it needs contextual information-information about how the resolution proceeded to its current point.
Summarizing earlier discussions on
federation, when performing an operation on a name that spans
multiple namespaces, the context implementation first constructs a
CannotProceed-Exception
containing information
pinpointing how far it has proceeded. It then obtains a
continuation context from JNDI by calling
getContinuationContext()
. To support the retrieval of
contextual information, getContinuationContext()
automatically adds the environment property
java.naming.spi.CannotProceedException
, with the value
of the Cannot-ProceedException
argument, to the
continuation context's environment. This property is inherited by
the continuation context and may be used by that context's
implementation to inspect the fields of the exception.
LDAP-style directory services support the
notion of referrals for redirecting a client's request to
another server. A referral differs from the federation continuation
mechanism described earlier in that a referral may be presented to
the JNDI client, who then decides whether to follow it, whereas a
CannotProceedException
should be returned to the
client only when no further progress is possible. Another
difference is that an individual context implementation offers the
capability of continuing the operation using the referral (and
itself determines the mechanism for doing so). In a federation, the
mechanism of continuation is beyond the scope of individual context
implementations: individual context implementations benefit from
the common federation mechanism provided by the JNDI SPI
framework.
A context implementation that supports
referrals defines a subclass of ReferralException
and
provides implementations for its abstract methods.
getReferralContext()
returns a context at which to
carry on the operation, and getReferralInfo()
returns
information on where the referral leads to, in a format appropriate
to the context implementation.
The environment property
java.naming.referral
specifies how the context
implementation should treat referrals. If the context
implementation is asked to throw an exception when a referral is
encountered, or if the context implementation encounters problems
following a referral, it throws a ReferralException
to
the application. To continue the operation, the application
re-invokes the method on the referral context using the same
arguments it supplied to the original method. The following code
sample shows how ReferralException
may be used by an
application:3
while (true) { try { bindings = ctx.listBindings(name); while (bindings.hasMore()) { b = (Binding) bindings.next(); ... } break; } catch (ReferralException e) { ctx = e.getReferralContext(); } }
This convention of re-invoking the method using the original arguments is a simple one for applications to follow. This places the burden on the implementation of the ReferralException to supply enough information to the implementation of the referral context for the operation to be continued. Note that this will likely render some of the arguments passed to the re-invoked operation superfluous. The referral context implementation is free to ignore any redundant or unneeded information.
It is possible for an operation to return results in addition to a referral. For example, when searching a context, the server might return several results in addition to a few referrals as to where to obtain further results. These results and referrals might be interleaved at the protocol level. If referrals require user interaction (i.e., not followed automatically), the context implementation should return the results through the search enumeration first. When the results have been returned, the referral exception can then be thrown. This allows a simple programming model to be used when presenting the user with a clear relationship between a referral and its set of results.
JNDI defines the Attribute
interface for representing an attribute in a directory. An
attribute consists of an attribute identifier (a string) and a set
of attribute values, which can be any object in the Java
programming language. There are also methods defined in
Attribute
for obtaining the attribute's definition and
syntax definition from the directory's schema.
public class Attribute { public DirContext getAttributeDefinition() throws NamingException; public DirContext getAttributeSyntaxDefinition()
throws NamingException; ... }
The utility class,
BasicAttribute
, does not provide useful
implementations for these methods. A directory context
implementation that has support for such schema information should
provide implementations of Attribute
that implement
these two methods based on its schema mechanisms, perhaps by
subclassing BasicAttribute
and overriding these two
methods. The context implementation should then return instances of
these subclasses when asked to return instances of
Attribute
. The context implementation, when it
receives an Attribute
instance that do not have
meaningful implementations of these two methods, should use
reasonable defaults to determine the attribute's definition and
syntax, using information such as the attribute values' class names
or conventions used for the attribute identifier.
The DirContext
interface
contains schema-related methods:
public class DirContext { ... public DirContext getSchema(Name name) throws NamingException; public DirContext getSchema(String name) throws NamingException; public DirContext getSchemaClassDefinition(Name name) throws NamingException; public DirContext getSchemaClassDefinition(String name) throws NamingException; }
getSchema()
returns the
schema tree for the named object, while
getSchemaClassDefinition()
returns a context
containing schema class definitions for the named object. Some
systems have just one global schema and, regardless of the value of
the name
argument, will return the same schema tree.
Others support finer grained schema definitions, and may return
different schema trees depending on which context is being
examined.
A context implementation supports event
notification by providing implementation for the methods in the
EventContext
/EventDirContext
interfaces.
The event model advocated by these interfaces can be readily
supported using a multithreaded model. When an application uses
addNamingListener()
to register a listener with a
context, the context records the requests and takes action to
collect information required to generate the events. When the
context eventually receives information to generate the events, it
fires the events to the listener. The thread that does the
registration is typically different from the thread that runs the
listener. The context implementation typically uses a thread that
it has created and manages to run the listener method. When one
event is dispatched to multiple listeners, the context
implementation may choose to (and is generally encouraged) to
execute the listener methods concurrently in separate threads.
The addNamingListener()
methods accept an instance of NamingListener
. The
instance might implement one or more subinterfaces of
NamingListener
. If the listener implements more than
one subinterface, the context implementation should try to conserve
resources required to satisfy the registration. For example, an
implementation might be able to submit a single request to the
server that captures all of the requests of the subinterfaces.
Where possible, the context
implementation should fire a NamingExceptionEvent
to a
listener if the context will be unable to fire further events and
then automatically deregister the listener. For example, if the
connection to the server is broken subsequent to the registration
of the listener and no information will be available to fire
events, the context should fire a NamingExceptionEvent
to the listener.
Each instance of Context
(or
its subinterfaces) can have associated with it an
environment which contains preferences expressed by the
application of how it would like to access the services offered by
the context. Examples of information found in an environment are
security-related information that specify the user's credentials
and desired level of security (none
,
simple
, strong
), and configuration
information, such as the server to use. See Chapter 6 and Appendix
A of the JNDI API document for more details about
environment properties.
Environment properties are defined generically in order to ensure maximum portability. Individual service providers should map these generic properties to characteristics appropriate for their service. Properties that are not relevant to a provider should be recorded and silently ignored. The environment may also be used for storing service provider-specific properties or preferences, in which case their applicability across different providers is limited.
See Section 6.1 in the JNDI
API document for a description of how environment
properties are named. Service provider-specific properties should
have a prefix that reflects their uniqueness to the provider. A
common practice is to use the package name of the service provider
as the prefix. For example, since Sun's LDAP provider is primarily
contained in the package com.sun.jndi.ldap
, properties
specific to Sun's LDAP provider have the prefix
"com.sun.jndi.ldap.".
When creating an initial context (either
using the constructors from InitialContext
or its
subclasses), the application can supply an environment as a
parameter. The parameter is represented as a Hashtable
or any of its subclasses (e.g., Properties
). The JNDI
class library augments the data from this parameter with data from
other sources (see Chapter 6 in the JNDI API
document) and passes this to the context implementation.
Like all other parameters, the
environment parameter received by a context implementation is owned
by the caller. The context implementation should make a copy of the
environment parameter it gets or otherwise take steps to ensure
that changes by the caller to the parameter would not affect what
the context implementation sees and vice versa. Note also that if
the environment parameter is a Properties
instance,
enumeration and Hashtable.get()
on the parameter only
examine the top-level properties (not any nested defaults). This is
the expected behavior. The context implementation is not expected
to retrieve or enumerate values in the Properties
instance's nested defaults.
The JNDI library is responsible for merging properties from different sources, such as the environment parameter to the initial context, resource files, and, where appropriate, system properties and applet parameters (see the JNDI API document, Chapter 6). The context implementation typically just reads the property it needs from the environment which it was supplied. There is seldom a need for a context implementation to consult other sources.
The environment is inherited from parent to child as the context methods proceed from one context to the next. The entire environment of a context instance is inherited by the child context instances, regardless of whether certain properties within the environment are ignored by a particular context.
A context implementation must pass on the
environment from one context instance to the next in order to
implement this "inheritance" trait of environments. Within one
context implementation it can do so by passing the environment as
an argument to the Context
constructor, or to the
NamingManager/DirectoryManager.getObjectInstance()
method for creating Context
instances.
Across context implementations in a
federation, this is supported by passing the environment as part of
the CannotProceedException
parameter of
NamingManager.getContinuationContext()/DirectoryManager.getContinuationDirContext()
,
which in turn will use this environment when creating an instance
of the context in which to continue the operation.
Inheritance can be implemented in any way as long as it preserves the semantics that each context has its own view of its environment. For example, a copy-on-write implementation could be used to defer copying of the environment until it is absolutely necessary.
The environment of a context can be
updated via the use of the addToEnvironment() and
removeFromEnvironment()
methods in the Context
interface.
public interface Context { ... public Object addToEnvironment(String propName, Object propVal) throws NamingException; public Object removeFromEnvironment(String propName) throws NamingException; }
These methods update the environment of
this instance of Context
. An environment property that
is not relevant to the context implementation is silently ignored
but maintained as part of the environment. The updated environment
affects this instance of Context
, and will be
inherited by any new child Context
instances, but does
not affect any Context
instances already in existence.
A lookup of the empty name on a Context
will return a
new Context
instance with an environment inherited as
with any other child.
See Section 6.6 in the JNDI API document for details.
Each service provider has an optional resource file that contains properties specific to that provider. The name of this resource is:
[prefix/]jndiprovider.propertieswhere prefix is the package name of the provider's context implementation(s), with each period (".") converted to a slash ("/"). For example, suppose a service provider defines a context implementation with class name
com.sun.jndi.ldap.LdapCtx
. The provider resource for
this provider is named
com/sun/jndi/ldap/jndiprovider.properties
.
The JNDI class library will consult this file when it needs to determine the value of a property, as described in Section 6.5.2 in the JNDI API document.
When the service provider needs to determine the value of a property, it will generally take that value directly from the environment. The service provider may define provider-specific properties to be placed in its own provider resource file. In that case it needs to read them from its property resource file and merge them in a way consistent with the algorithm described in Section 6.5.2 in the JNDI API document.
For a context implementation that uses a client/server protocol, there is not necessarily a one-to-one mapping between a context and a connection between the client and the server. JNDI is a high-level API that does not deal directly with connections. It is the job of the context implementation to do any necessary connection management. Hence, a single connection may be shared by multiple context instances, and a context implementation is free to use its own algorithms to conserve connection and network usage. Thus, when a method is invoked on the context instance, the context implementation might need to do some connection management in addition to performing the requested operation.
The Context.close()
and
NamingEnumeration.close()
methods can be used by
applications to provide hints to the context implementation as to
when to free connection-related resources. A context implementation
may choose to (and is generally encouraged to) take other measures
to garbage-collect and conserve its connection-related
resources.
Some environment properties affect a context's connection. For example, if the application changes the security-related properties, the context implementation might need to modify or create a new connection using those updated properties. If the connection was being shared by other contexts prior to the change, the connection change should not affect contexts whose properties have not been updated.
Since all naming methods are performed relative to a context, an application needs a starting context in order to invoke them. This starting context is referred to as the initial context. The bindings in the initial context are determined by policies set forth by the initial context implementation, perhaps using standard policies for naming global and enterprise-wide namespaces. For example, the initial context might contain a binding to the Internet DNS namespace, a binding to the enterprise-wide namespace, and a binding to a personal directory belonging to the user who is running the application.
An application obtains an initial context by making the following call:
Context ctx = new InitialContext();An alternate constructor allows an environment to be passed as an argument. This allows the application to pass in preferences or security information to be used in the construction of the initial context.
Hashtable env = new Hashtable();4 env.put(Context.SECURITY_PRINCIPAL, "jsmith"); env.put(Context.SECURITY_CREDENTIALS, "xxxxxxx"); Context ctx = new InitialContext(env);
Subsequent to getting an initial context,
the application can invoke Context
methods.
Object obj = ctx.lookup("this/is/a/test");
The InitialContext
class (and
subclasses) selects an implementation using a default algorithm
that can be overridden by installing an initial context factory
builder (described below).
The InitialDirContext
is an
extension of InitialContext
. It is used for performing
directory operations using the initial context. The
InitialLdapContext
class is an extension of
InitialDirContext
. It is used for performing special
LDAP v3 operations using the initial context. The algorithms and
policies described in this section also apply to
InitialDirContext
and InitialLdapContext
.
Places where DirContext/LdapContext
is required
instead of Context
have been noted.
An initial context factory is a
class that creates an instance of a context that has been
implemented following the guidelines outlined in Chapter 2. The factory is used by the
InitialContext
class (or subclass) constructor.
Given an environment, the factory returns
an instance of Context
(or its subinterfaces).
public interface InitialContextFactory { public Context getInitialContext(Hashtable env)
throws NamingException; }
Appendix A contains an example of an
InitialContextFactory
.
Once the context instance has been
created, when a method is invoked on InitialContext
by
using a non-URL name (see below), the method is forwarded and
invoked on that context instance.
JNDI selects the initial context
implementation to use by using the property
java.naming.factory.initial
. This property contains
the fully-qualified class name of an initial context factory. The
class must implement the InitialContextFactory
interface and have a public constructor that does not take any
arguments. JNDI will load the initial context factory class and
then invoke getInitialContext()
on it to obtain a
Context
instance to be used as the initial
context.
An application that wants to use a
particular initial context must supply the
java.naming.factory.initial
property in the
environment passed to the InitialContext
(or subclass)
constructors, or via resource files, system properties, or applet
parameters.
When the property
java.naming.factory.initial
is set to a
non-null
value, the InitialContext
(and
subclass) constructors will try to load and instantiate an initial
context factory, which will then create a context instance. If the
factory or context cannot be created, for example as a result of an
authentication problem, the initial context factory can throw an
exception to indicate this problem. Note however that it is up to
the context implementation when it verifies and indicates
to users of the initial context any environment property- or
connection- related problems. It can do so lazily-delaying until an
operation is performed on the context, or eagerly, at the time the
context is created.
If the property
java.naming.factory.initial
is not set, no attempt
will be made to create an underlying context for the initial
context. The initial context is still useful, for instance, for
processing URL names, as described next.
If a URL5 string is passed to the initial
context, it will be resolved using the corresponding URL
context implementation. This feature is supported by the
InitialContext
class (and subclasses) and is
independent of the setting of the
java.naming.factory.initial
environment property.
This feature allows applications to use the initial context to reach any namespace for which a URL context implementation has been made available. For example, the following code lists an LDAP namespace from the initial context:
new
InitialContext().list("ldap://lserver/ou=eng,o=wiz,c=us");
A URL string has the following format:
For example, an LDAP URL string has the scheme id "ldap"; a file URL has the scheme id "file".
A URL context implementation is a class
that implements the Context
interface (and possibly
some subinterfaces) and accepts name arguments that are URL strings
of the scheme that it supports. For example, an LDAP URL context
accepts "ldap" URL strings.
When a URL string name is passed to a URL
context, the context methods that accept String
treat
the name as a URL with the syntax defined by the URL scheme. When a
Name
object in which the first component is a URL
string name is passed to a URL context, the first component is
treated as a URL string, and the rest is used for federation (that
is, resolution of the first component will indicate which naming
system to use to resolve the rest). The Name
instance
should be a CompositeName
; otherwise, an
InvalidNameException
should be thrown.
Name arguments that are not URL strings,
and URL strings with an inappropriate scheme id should be rejected
with an InvalidNameException
.
A URL context factory is a class (actually a special type object factory (see Section 4.1)) that creates an instance of a URL context for URLs of one or more schemes.
When the InitialContext
class receives a URL string as a name argument, it will look for a
URL context factory by using the following algorithm. The
environment property java.naming.factory.url.pkgs
contains a colon-separated list of package prefixes. The factory's
class name is constructed by using the following rule:
package_prefix + "." +
scheme_id + "." +
scheme_idURLContextFactory
for each package prefix listed in the
property. The default package prefix com.sun.jndi.url
is appended to the end of the list.
For example, if the URL is
"ldap://somehost:389
" and
java.naming.factory.url.pkgs
contains
"com.widget:com.wiz.jndi
", the
InitialContext
class will attempt to locate the
corresponding factory class by loading the following classes until
one is successfully instantiated:
com.widget.ldap.ldapURLContextFactory com.wiz.jndi.ldap.ldapURLContextFactory com.sun.jndi.url.ldap.ldapURLContextFactoryThe factory class implements the
ObjectFactory
interface (see "URL Context
Factory" on page 31) and has a public constructor that
takes no arguments. The InitialContext
class passes
the scheme id as the resolved object to the factory's
getObjectInstance()
method, which in turn creates a
URL context for the URL scheme. The URL context will then be used
to carry out the originally intended Context
or
DirContext
operation on the URL supplied to
InitialContext
.
There is no requirement that a service
provider supply a URL context factory and URL context
implementation. It only does so if it wants to allow URL string
names with its URL scheme to be accepted by the
InitialContext
class. A service provider, for
instance, might just provide an initial context factory and a
context implementation that is accessed through that factory.
The policy of creating an initial context
factory using the java.naming.factory.initial
environment property and URL support is built into the
InitialContext
class. There are two ways an
application can override some or all of this policy.
If an application does not want URL
strings to be treated specially, it can use the method
NamingManager.getInitialContext()
, which creates a
context instance using the factory named in the
java.naming.factory.initial
environment property.
This method is also useful if the
application needs to access interfaces implemented by the context
created by the initial context factory, but which are not one of
Context
, DirContext
, or
LdapContext
. Here is a code fragment that gets a
context using NamingManager.getInitialContext()
and
then casts it to a subclass:
FooContext ctx = (FooContext) NamingManager.getInitialContext(env); ... Object obj = ctx.lookup(name); ctx.fooMethod1(...);Note that installing an initial context factory builder (discussed next) affects the result of
NamingManager.getInitialContext()
.
An initial context factory builder is a class that creates instances of initial context factories.
An application can install an initial
context factory builder to define its own policy of how to locate
and construct initial context implementations. When a builder has
been installed, it is solely responsible for creating the initial
context factories. None of the default policies
(java.naming.factory.initial
property or URL support)
normally used by JNDI are employed.
An implementation of an initial context
factory builder must implement the
InitialContext-FactoryBuilder
interface. Its
createInitialContextFactory()
method creates instances
of InitialContextFactory
.
After a builder has been installed. the
application can get the initial context by either using the
InitialContext
/InitialDirContext
/InitialLdapContext
constructors, or by using
NamingManager.getInitialContext()
. When one of the
constructors is used, its class is basically a wrapper around the
underlying context implementation returned by
NamingManager.getInitialContext()
.
When there is a need to provide an
initial context that supports an interface that extends from
Context
, DirContext
, or
LdapContext
, the service provider should supply a
subclass of InitialContext
(or
InitialDirContext/InitialLdapContext
).
To add support for URLs in the same way
InitialContext
and InitialDirContext
do,
the subclass should use the protected methods available in
InitialContext
as follows. This only makes sense for
interfaces that have methods that accept name argument.
For example, suppose
FooContext
is a subinterface of
DirContext
. Its initial context implementation would
define getURLOrDefaultInitFooCtx()
methods (for both
Name
and String
parameters) that retrieve
the real initial context to use.
public class InitialFooContext extends InitialDirContext { ... protected FooContext getURLOrDefaultInitFooCtx(Name name) throws NamingException { Context answer = getURLOrDefaultInitCtx(name); if (!(answer instanceof FooContext)) { throw new NoInitialContextException("Not a FooContext"); } return (FooContext)answer; } // similar code for getURLOrDefaultInitFooCtx(String name) }When providing implementations for the new methods in the
FooContext
interface that accept a name argument,
getURLOrDefaultInitFooCtx()
is used in the following
way.
public Object FooMethod1(Name name, ...) throws NamingException { return getURLOrDefaultInitFooCtx(name).FooMethod1(name, ...); }
When providing implementations for the
new methods in the FooContext
interface that do not
have a name argument, or for which URL support is not required, use
InitialContext.getDefaultInitCtx()
.
protected FooContext getDefaultInitFooCtx() throws NamingException { Context answer = getDefaultInitCtx(); if (!(answer instanceof FooContext)) { throw new NoInitialContextException("Not an FooContext"); } return (FooContext)answer; } public Object FooMethod2(Args args) throws NamingException { return getDefaultInitFooCtx().FooMethod2(args); }
The implementation should provide
appropriate constructors for the class. The constructor should call
the appropriate constructor of the superclass. If the environment
needs to be modified or examined prior to the superclass's
constructor being called, it should use the protected constructor
that accepts a boolean flag to control the initialization of the
initial context, and then use the init()
method to
initialize the context. Here is an example:
public InitialFooContext(Hashtable environment, Object otherArg) throws NamingException { super(true); // don't initialize yet // Clone environment and adjust Hashtable env = (environment == null) ? new Hashtable(11) : (Hashtable)environment.clone(); ... init(env); }Client programs that use this new initial context would look as follows.
import com.widget.jndi.InitialFooContext; ... FooContext ctx = new InitialFooContext(env); Object obj = ctx.lookup(name); ctx.FooMethod1(name, ...);
JNDI allows a context implementation to be customized-by the application, the application's deployer or user, or the service provider-in how it reads and stores objects in the naming/directory service. A similar facility is also available for narrowing LDAP v3 control classes.
You can think of these facilities as modules that plug into a context implementation.
JNDI provides a generic way of creating
objects (including instances of Context
) using
information stored in the namespace. That information may be of
arbitrary type (java.lang.Object
). For example, it may
be a Reference
, or a URL, or any other data required
to create the object. Turning such information stored in the
namespace into an object is supported through the use of object
factories. An object factory is a class that implements the
ObjectFactory
interface (or the
DirObjectFactory
subinterface):
public interface ObjectFactory { public Object getObjectInstance(Object refObj, Name name, Context nameCtx, Hashtable env) throws Exception; } public interface DirObjectFactory extends ObjectFactory { public Object getObjectInstance(Object refObj, Name name, Context nameCtx, Hashtable env, Attributes attrs) throws Exception; }Given some reference information (
refObj
) about an
object, optional information about the name of the object and where
it is bound, and optionally some additional environment information
(for example, some identity or authentication information about the
user creating the object), the factory attempts to create an object
represented by the reference information. For example, given
reference information about a printer, a printer object factory
might return an instance of Printer
. In the case of an
object factory that is to be used with a DirContext
implementation, the factory is also given some attributes about the
object. If the factory requires more attributes or information, it
can obtain them directly from the naming/directory service by using
the name
/nameCtx
arguments.
If the factory cannot created an object
using the arguments supplied, it should return null
.
For example, when a printer object factory is given data about a
disk drive, it should return null
. The factory should
only thrown an exception if no other object factories should be
tried. Therefore, the factory should be careful about runtime
exceptions that might be thrown from its implementation. For
example, if a printer object factory is given data about a printer
but the data is malformed in some way, it should throw an
exception.
Object factories are used in several places in JNDI, basically to turn any reference information into an object. They are used in federation, URL processing in the initial context, and, as illustrated by the printer example, turning data into a form expected by the application.
A Reference
contains methods
for returning the class name and location of the object factory.
The following methods are found in Reference
.
public class Reference { ... public String getClassName(); public String getFactoryClassName(); public String getFactoryClassLocation(); }If the object read from the directory/naming service is an instance of
Reference
or Referenceable
, its
corresponding object factory can be located using information in
Reference
. The getFactoryClassName()
method retrieves the name of the factory class that implements the
ObjectFactory
interface. This factory must implement
the ObjectFactory
interface and have a public
constructor that takes no arguments.
getFactoryClassLocation()
retrieves the codebase of
the class implementation for the factory, which is a list of
space-separated URLs.
JNDI creates the object by invoking
getObjectInstance()
on the ObjectFactory
instance, by using the Reference
and environment as
arguments. The result is an instance of a class identified by
getClassName()
.
Note that all the classes necessary to instantiate the object returned to the application are made available using mechanisms provided by JNDI. The application doesn't have to install the classes locally.
Figure 3: Example Using Reference to Get Back an Object From the Namespace
Returning to the printer example, suppose
Printer
is an interface for representing a printer and
the BSDPrinter
class is an implementation of that
interface. BSDPrinter
implements the
Referenceable
interface and uses the
Reference
class to store information on how to
construct instances of BSDPrinter
and address
information for communicating with the print server. The
Reference
contains the class name of the object
("Printer"
), the class name of the printer object
factory ("PrinterFactory
") and a URL for loading the
factory's class implementation. Using the factory class name and
implementation location, JNDI first loads the implementation of
PrinterFactory
and creates an instance of
PrinterFactory
. It then invokes
getObjectInstance()
on the factory to create an
instance of Printer
using the reference. For example,
one address in the reference may have an address of type
"bsd
", and contains the print server's host name
("lobby-printserver
"). The PrinterFactory
instance uses the address type ("bsd
") to decide to
create a BSDPrinter
instance and passes the address
contents ("lobby-printserver
") to its constructor. The
resulting BSDPrinter
object is returned as the result
of lookup()
.
From the context implementation's point
of view, all of this is done automatically by its invocation of
NamingManager
/DirectoryManager.getObjectInstance()
.
When the application invokes
print()
on the BSDPrinter
instance
returned by lookup()
, the data is sent to the print
server on the machine "lobby-printserver
" for
printing. The application need not know the details of the
Reference
stored in the namespace, the protocol used
to perform the job, or whether the BSDPrinter
class
was defined locally or loaded over the network. The transformation
of the information stored in the underlying service into an object
that implements the Printer
interface is done
transparently through the cooperation of the service provider
(which stores bindings of printer names to printer address
information), the printer service provider (which provides the
PrinterFactory
and BSDPrinter
classes),
and the JNDI SPI framework (which ties the two together to return
an object that the application can use directly).
A service provider for such an object must do the following:
BSDPrinter
)
that implements Referenceable
or is a subclass of
Reference
. Reference
and its reference addresses
for the object. ObjectFactory
(e.g., PrinterFactory
).
This class's getObjectInstance()
method will create an
instance of the class from step 1 (e.g., BSDPrinter
)
when given the Reference
from step 2. If a Reference
contains an
address of type "URL" but not the factory class name and location,
or if the reference is an array of strings containing URLs, JNDI
will use the URL context factory support described in Section 3.2 to locate the factory,
and then pass the URL string in the address to the factory's
getObjectInstance()
method. See Section 4.1.6 for a description of
how JNDI expects a URL context factory implementation to
behave.
A service provider for such an object must do the following:
BSDPrinter
). ObjectFactory
. This class's
getObjectInstance()
method will create an instance of
the class from step 1 (e.g., BSDPrinter
) when given
the URL from step 2. In addition to extracting factory
information from Reference
s, or using URLs, JNDI also
looks for object factories specified in the
java.naming.factory.object
property, which can be in
the environment or the provider resource file (see Section 2.9.5). The property contains
a colon-separated list of fully-qualified class names of object
factories. Each class must implement the ObjectFactory
interface and have a public constructor that takes no arguments.
For each class in the list, JNDI attempts to load and instantiate
the factory class, and to invoke the
ObjectFactory/DirObjectFactory.getObjectInstance()
method on it using the object and environment arguments supplied.
If the creation is successful, the resulting object is returned;
otherwise, JNDI uses the same procedure on the next class in the
list until the list is exhausted or a factory returns a
non-null
result.
Figure 4: Example using java.naming.factory.object to Get Back an Object from the Namespace
For the printer example, instead of using
a Reference
to represent a printer in the namespace,
some other information is stored. When that information is later
retrieved, the object factories specified
java.naming.factory.object
are tried in turn to
attempt to turn that information into a Printer
instance.
A service provider for such an object must do the following:
BSDPrinter
). Reference
. It can be anything that
will be understood by its corresponding object factory (e.g., some
string containing the server name "printer type=bsd;
host=lobby-printserver
"). ObjectFactory
(e.g., PrinterFactory
).
This class's getObjectInstance()
method will create an
instance of the class from step 1 (e.g., BSDPrinter
)
when given an instance of the class from step 2 (e.g.,
"printer type=bsd; host=lobby-printserver
"). The service provider should automatically
convert between the actual object (e.g., BSDPrinter
)
and the reference information (step 2, e.g., "printer
type=bsd; host=lobby-printserver
") when binding or looking
up the object.
An application that wants to use a
particular factory for generating objects must include the
factory's class name in its java.naming.factory.object
environment property and make the factory's classes and object
classes available.
An object factory builder is a class that creates instances of object factories.
An application can install an object
factory builder to defining its own policy of how to locate and
construct object factory implementations. When a builder has been
installed, it is solely responsible for creating the object
factories. None of the default policies (Reference
,
URL string, or java.naming.factory.object
property)
normally used by JNDI are employed.
Figure 5: Example using an Object Factory Builder to Get Back an Object from the Namespece
A service provider for an object factory builder must do the following:
ObjectFactory
. ObjectFactoryBuilder
. This class's
createObjectFactory()
method will use the constructors
for the ObjectFactory
classes in step 1.An application that wants to use this factory builder must first install it.
NamingManager.setObjectFactoryBuilder(builder);
A context factory is an object
factory that creates instances of Context
. The
implementation of these contexts for a particular naming or
directory service is referred to as a context
implementation. Context implementations are described in
Chapter 2. Like any other
object factory, a context factory can be obtained by using any of
the three mechanisms described above: from a
Reference
, a URL scheme id, or listed in the
java.naming.factory.object
property.
A URL context factory is a special kind
of context factory. It follows these rules when implementing
ObjectFactory.getObjectInstance()
.
refObj
is null
, create a context
for resolving URLs of the scheme associated with this factory. The
resulting context is not tied to a specific URL. For example,
invoking
getObjectInstance(null, null, null, env)
ldap://ldap.wiz.com/o=wiz,c=us
" or
"ldap://ldap.umich.edu/
", ...).
refObj
is a URL string, create the object
identified by the URL. For example, invokinggetObjectInstance("ldap://ldap.wiz.com/o=wiz,c=us", null, null, env);
o=wiz,c=us
" on the LDAP server
ldap.wiz.com
. If this happens to name a context, it
can then be used for resolving (relative) LDAP names (e.g.,
"cn=Jane Smith
"). refObj
is an array of URL strings, the
assumption is that the URLs are equivalent in terms of the context
to which they refer. Verification of whether the URLs are, or need
to be, equivalent is up to the context factory. The order of the
URLs in the array is not significant. The object returned by
getObjectInstance()
is the same as that for the single
URL case-it is an object (perhaps a context) named by the URLs.
refObj
is any other type, the behavior of
getObjectInstance()
is determined by the
implementation.
URL context factories are used by the
InitialContext
class when it is passed a URL to
resolve. URL context factories are also used for creating objects
in the Java programming language from URLs stored in the namespace
(see Section 4.1.2).
JNDI provides a mechanism to transform an
object into a form storable by the underlying context
implementation. That form may be any arbitrary type acceptable to
the underlying context implementation. For example, it may be a
Reference
, a URL, a Serializable
object,
or a set of attributes, or any other data acceptable by the
underlying context implementation. Turning an arbitrary object into
data that can be stored in the namespace is supported through the
use of state factories. A state factory is a class that
implements the StateFactory
interface (or the
DirStateFactory
subinterface):
public interface StateFactory { public Object getStateToBind(Object obj, Name name, Context nameCtx, Hashtable env) throws NamingException; } public interface DirStateFactory { public DirStateFactory.Result getStateToBind(Object obj, Name name, Context nameCtx, Hashtable env, Attributes attrs) throws NamingException; }Given an object (
obj
), optional information about the
name of the object and where it is bound, and optionally some
additional environment information (for example, some identity or
authentication information about the user accessing the namespace),
the factory attempts to create an object suitable for binding.
Typically, the state factory is knowledgeable about the target
naming/directory service and/or context implementation, and knows
which data formats are acceptable. In the case of a state factory
that is to be used with a DirContext
implementation,
the factory is also given some attributes that are to be stored
with the object. If the factory require more information about the
object, it can obtain them directly from the naming/directory
service by using the name
/nameCtx
arguments. For example, a printer state factory for an LDAP
directory might return a set of attributes that represent the
printer.
If the factory cannot return any data
using the arguments supplied, it should return null
.
For example, when a printer state factory is given a disk object,
it should return null
. The factory should only thrown
an exception if no other state factories should be tried.
Therefore, the factory should be careful about exceptions that
might be thrown from its implementation. For example, if a printer
state factory is given a printer object but perhaps contradictory
attributes, it might throw an exception.
Ultimately, a factory's output formats are determined by the underlying naming/directory service. A context implementation for the CORBA Object Services (COS) naming service, for example, can only store CORBA object references into the service; a context implementation for LDAP can only store attributes, although there is a lot of flexibility in how to encode information within those attributes.
A service provider typically supplies a factory for each (common) type of input that it expects, and the application can augment that set with state factories of its own. For example, a service provider for COS naming might have a state factory for converting a Java Remote Method Invocation (RMI) object into a CORBA object reference. A user of that provider might add a state factory for converting a Microsoft COM object reference into a CORBA object reference.
JNDI looks for state factories specified
in the java.naming.factory.state
property, which can
be in the environment or the provider resource file (see Section 2.9.5). The property contains
a colon-separated list of fully-qualified class names of state
factories. Each class must implement the StateFactory
interface and have a public constructor that takes no arguments.
For each class in the list, JNDI attempts to load and instantiate
the factory class, and to invoke the
StateFactory/DirStateFactory.getStateToBind()
method
on it using the object, name, context, environment, and attributes
arguments supplied. If the factory produces a non-null
result, the result is returned; otherwise, JNDI uses the same
procedure on the next class in the list until the list is exhausted
or a factory returns a non-null
result.
The LDAP v3 protocol allows response
controls to accompany any response sent by the server. The control
consists of an OID string identifier and a sequence of ASN.1 BER
encoded bytes. In the absence of any external information or
assistance, the context implementation can only return a plain
implementation of the Control
interface that returns
the OID and bytes.
JNDI provides the following abstract class for dealing with response controls:
public abstract javax.naming.ldap.ControlFactory { ... public static Control getControlInstance(Control ctl, Context ctx, Hashtable env) throws NamingException; public abstract Control getControlInstance(Control ctl) throws NamingException; }When a context implementation receives a response control, it invokes the static
getControl-Instance()
method to
find a control factory that can narrow the control to one that has
more user-friendly access methods. Such a control, for instance,
can decode the ASN.1 BER bytes and provide access methods that
return the information as Java types. If no such control factory
can be found, the original response control is returned. Here is an
example of a hypothetical Time-ResponseControl
which
decodes the time of day.
public class TimeResponseControl implements Control { long time; // Constructor used by ControlFactory public TimeResponseControl(String OID, byte[] berVal) throws NamingException { // check validity of OID time = // extract time from berVal }; // Type-safe and User-friendly method public long getTime() { return time; } // Low-level methods public String getID() { return TIME_OID; } public byte[] getEncodedValue() { return // original berVal } ... }A control factory may be responsible for one or more controls. If the factory cannot return a control using the arguments supplied, it should return
null
. Typically, this involves just
matching the control's OID against the list of OIDs supported by
the factory. The factory should only thrown an exception if no
other control factories should be tried. Therefore, the factory
should be careful about exceptions that might be thrown from its
implementation. For example, if a control factory is given a
control with an OID that it supports, but the byte array has an
encoding error, it should throw an exception.
Here is an example of a control factory:
public class VendorXControlFactory extends ControlFactory { public VendorXControlFactory () { } public Control getControlInstance(Control orig) throws NamingException { if (isOneOfMyControls(orig.getID())) { ... // determine which of ours it is and call its constructor return new TimeResponseControl(orig.getID(), orig.getEncodedValue()); } return null; // not one of ours } }
JNDI looks for response control factories
specified in the java.naming.factory.control
property,
which can be in the environment or the provider resource file (see
Section 2.9.5). The property
contains a colon-separated list of fully-qualified class names of
control factories. Each class must implement the
ControlFactory
interface and have a public constructor
that takes no arguments. For each class in the list, JNDI attempts
to load and instantiate the factory class, and to invoke the
ControlFactory.getControlInstance()
instance method on
it using the control, context, and environment arguments supplied.
If the factory produces a non-null
result, the result
is returned; otherwise, JNDI uses the same procedure on the next
class in the list until the list is exhausted or a factory returns
a non-null
result.
Any object passed as a parameter to a method in a factory is owned by the caller. Therefore, the factory is prohibited from maintaining a pointer to the object beyond the duration of the operation or modifying the object. If the factory needs to save the information contained in a parameter beyond the duration of the operation, it should maintain its own copy.
A factory instance should be reentrant. That is, it should be possible for multiple threads to invoke methods on a single instance of a factory concurrently.
CannotProceedException
may well have been thrown by
one of the context's internal methods when it discovered that the
name being processed is beyond the scope of its naming system. The
process by which the exception is produced is dependent on the
implementation of the context. 3
Note that this is code in the application. In "Continuing an Operation in a
Federation", the code sample presented is code in the
context implementation. 4 You can also use a subclass of Hashtable (e.g.
Properties) for this. 5 The
mention of "URL" in this document refers to a URL string as defined
by RFC 1738 and its related RFCs. It is any string that conforms to
the syntax described therein, and may not always have corresponding
support in the java.net.URL
class or Web browsers. The
URL string is either passed as the String
name
parameter, or as the first component of the Name
parameter.