| < Day Day Up > |
|
Unlike the C API, the Java API still has no RFC standard. However, work is under way, and there is a draft standard to define the Java API. The draft (draft-ietf-ldapext-ldap-java-api-18.txt, "The Java LDAP Application Program Interface") is available from the IETF Web site.
There are several sources of Java libraries for LDAP. In the following examples, we will be using the Java SDK from Netscape. Other vendors such as Novell also provide SDKs.
Like the previously discussed languages, Java provides an SDK, but it also offers several interesting ancillary technologies. A discussion of all the possibilities of Java is a subject worthy of its own book. Unfortunately, we have to limit the discussion to the Java SDK. Nevertheless, we will briefly review some interesting Java technologies at the end of this section.
As in the preceding sections for the other languages, we first view some simple applications to get an idea of what a class looks like when using the LDAP API. Then we take a look at the classes Java offers. A few further examples show how to implement the most important functionalities, such as search, update, and delete entries and how to display the results set of a search.
As with all the LDAP libraries, we have to use the well-known paradigm of
Connecting to the server that LDAP is running on
Binding to the LDAP server
Executing the requested operations
Unbinding from the LDAP server
Like the other languages, Java can send requests synchronously and asynchronously.
At this point, we have some experience with LDAP and programming language support. So we can skip the basics and begin this chapter with a complete program showing how to use the LDAP support offered by Java. Exhibit 55 shows a complete Java program that connects to an LDAP server and executes a simple search operation. The syntax to make the program work is:
search1.java <Host> <Port> <SearchBase> <Filter>
01 import netscape.ldap.* ; 02 import java.io.* ; 03 import java.util.* ; 04 05 public class search1 { 06 07 public static void main(String[] args) { 08 09 /* Get variables from command line */ 10 String host = args[0] ; 11 int port = Integer.parseInt(args[1]); 12 String base = args[2] ; 13 String filter = args[3] ; 14 15 int scope = LDAPConnection.SCOPE_SUB ; 16 int version = 3 ; 17 18 LDAPConnection ld = null ; 19 20 try { 21 ld = new LDAPConnection(); 22 23 ld.connect(version,host,port,null,null); 24 25 LDAPSearchResults res = 26 ld.search(base,scope,filter,null,false); 27 28 while (res.hasMoreElements()) { 29 LDAPEntry findEntry = null ; 30 findEntry = (LDAPEntry) res.next(); 31 System.out.println("DN: " + findEntry.getDN()) ; 32 LDAPAttributeSet attributeSet = 33 findEntry.getAttributeSet(); 34 35 for (int i=0; i<attributeSet.size(); i++) { 36 LDAPAttribute attribute = 37 (LDAPAttribute) attributeSet.elementAt(i); 38 String attrName = attribute.getName() ; 39 Enumeration enumVals = 40 attribute.getStringValues(); 41 if (enumVals != null) { 42 while (enumVals.hasMoreElements()) { 43 String nextValue = 44 (String) enumVals.nextElement(); 45 System.out.println(nextValue) ; 46 } 47 } 48 } 49 } 50 51 if (ld!= null) { 52 ld.disconnect(); 53 } 54 } 55 56 catch(LDAPException e) { 57 System.out.println(e.toString()); 58 } 59 60 } 61 }
Note that the program as presented in Exhibit 55 does no error checking for missing arguments. In a real-world application, you would obviously insert error checking to avoid strange error messages from the Java interpreter, messages that could confuse the end user. We bypassed the issue of error checking to maintain our focus on LDAP.
The "try" part of the program (line 20) tries to open a connection to the LDAP server using the connect method. (We will see the connect method later in more detail.) Once the connection is opened, a search is executed using the search method, which delivers an object of type LDAPSearchResults. This object has the method hasMoreElements, which checks whether all elements in the results set have been visited (line 28), and the method "next," which delivers the next element in the results set (line 30). The "next" method delivers the single entry as an object. The getDN method (line 31) delivers the distinguished name of the entry, and the getAttributeSet (line 33) delivers the object attributeSet, which has "get" and "set" methods to access the single attributes.
We will take a closer look at single objects and methods in the following sections. This section, however, is not intended to be an exhaustive tutorial on Java-LDAP programming. Refer instead to the documentation shipped with the Java development for LDAP. Most of them include tutorials and numerous examples.
First of all, we have to create an LDAPConnection object. This object is used later on to enable all operations such as authentication, searching of entries, modification of entries, and deletion of entries.
LDAPConnection ld = new LDAPConnection();
Then we connect to the server that LDAP is running on:
ld.connect(host,port);
Once connected, we bind to the LDAP server:
ld.authenticate(BaseDN,password);
There are two ways to bind (authenticate):
Bind anonymously, where BaseDN and password are null:
ld.authenticate(null,null); ld.authenticate ("", "");
Bind with a user DN and password:
ld.authenticate("uid=JKirk,ou=IT,o=LdapAbc.org", password1) ;
As shown in the example, you can combine the two steps of connecting and binding into a single step:
ld.connect(host,port,BaseDN,password);
As explained previously, there are servers running LDAP (v2) and servers running LDAP (v3). The SDK assumes LDAP (v2) as the default. In the example, we explicitly specified to use LDAP (v3). You can ask the directory server which version it supports and set the version to take advantage of the extended functionality of version 3:
ld.connect(version,host,port,BaseDN,password);
There is not much to say about the unbind operation. The unbind works on the LDAPConnection object, releasing the resources allocated for the connection.
ld.disconnect();
The Java API helps to keep network traffic low when you need more than one connection to the LDAP server in one application. Instead of creating a brand-new connection, you can share the existing one by using the clone() method. Exhibit 56 shows a code snippet that clones a connection. The clone() method creates a logical new connection independent of the existing one using the same network resources. However, this can be done only if you use the same host, the same port, and the same user credentials.
LDAPConnection ld = new LDAPConnection(); ld.connect(host,port); ld.authenticate(BaseDN,password); LDAPConnection ld2 = ld.clone();
The syntax for the search operation is as follows:
public LDAPSearchResults search(String base, int scope, String filter, String[] attrs, boolean typesOnly) throws LDAPException
The search operation needs the following five parameters:
base: Base distinguished name the query is starting from
scope: Search scope of the query. Values can be:
LDAPConnection.SCOPE_SUB
LDAPConnection.SCOPE_BASE
LDAPConnection.SCOPE_ONE
Filter: Query filter
Attributes: Attributes to be returned by the query
Typesonly: Set to "true," the query returns only the attribute names, not the values.
Exhibit 57 shows a code snippet executing a search. This example shows a search that returns four attributes. The typesOnly parameter is set to false because we are only interested in the values of the attributes.
String base = "ou=IT, o=LdapAbc.org"; String host = 389 ; String host = www.ldapabc.org; String attrs = {"sn", "cn","commonName", "mail","uid"}; ld = new LDAPConnection(); ld.connect(version,host,port,null,null); LDAPSearchResults res = ld.search(base,scope,filter,attrs, false);
The compare method searches the directory to see if the entry distinguished name (DN) has the attribute with the specified value. The method returns a value of "true" if it does and "false" if it does not. The syntax is as follows:
public boolean compare(String dn, LDAPAttribute attr) throws LDAPException
The example in Exhibit 58 verifies whether the entry with the distinguished name:
"uid=TMorris, ou=Marketing, o=LdapAbc.org" ;
String dn = "uid=TMorris, ou=Marketing, o=LdapAbc.org" ; String attrname = "mail"; String attrval = "TMorris@yahoo.com" ; ld = new LDAPConnection(); ld.connect(version,host,port,null,null); LDAPAttribute attr = new LDAPAttribute(attrname, attrval); if (ld.compare(dn,attr)) { System.out.println("DN: "+ dn + " contains \n") ; System.out.println(attr1 + ": " + attrval); }
has the e-mail:
<TMorris@yahoo.com>
If the search method gets a hit, it generates an object class "LDAPSearchResults." This object makes it possible to retrieve single entries, search referrals, find the number of entries returned, sort the entries in the results sets, etc. The "LDAPSearchResults" object also creates yet another object class "LDAPEntry" that is useful in evaluating the search. The "LDAPEntry" object, in turn, creates a third object class "LDAPAttributeSet" via the method "getAttributeSet." The example in Exhibit 59 shows how these three objects interact to give you the complete results. Of course, the user must have the requisite access rights to obtain the requested information.
LDAPSearchResults res = ld.search(MY_SEARCHBASE, ld.SCOPE_ONE, MY_FILTER, null, false); /* Loop on results until finished */ while (res.hasMore()) { /* Next directory entry */ LDAPEntry findEntry = res.next (); System.out.println(findEntry.getDN()); /* Get the attributes of the entry */ LDAPAttributeSet findAttrs = findEntry.getAttributeSet(); Iterator enumAttrs = findAttrs.iterator(); System.out.println("Attributes: "); /* Loop on attributes */ while (enumAttrs.hasNext()) { LDAPAttribute anAttr = (LDAPAttribute)enumAttrs.next(); String attrName = anAttr.getName(); System.out.println(" " + attrName); Enumeration enumVals = anAttr.getStringValues(); while (enumVals.hasMoreElements()) { String aVal = (String)enumVals.nextElement(); System.out.println(" " + aVal); } } }
In the search method, you can limit the attributes returned in the results set by giving as a parameter an array containing the attributes you need. You also can specify further constraints on the search. Every object of the type "LDAPConnection" has an associated object of the type "LDAPConstraints." This object specifies the maximal number of results, the maximum time allowed for the search process, whether the server should dereference aliases, whether the server should automatically follow referrals, and other parameters. The code snippet in Exhibit 60 shows how to set up a number of constraints.
ld = new LDAPConnection(); ld.connect(version,host,port,null,null); String attributes = {"sn", "cn","commonName", "mail","uid"}; int timelimit = 30 ; int maxresults = 10 ; LDAPSearchConstraints constraints = ld.getSearchConstraints(); Constraints. setServerTimeLimit(timelimit); Constraints. setMaxResults(maxresults); LDAPSearchResults res = ld.search(MY_SEARCHBASE, ld.SCOPE_ONE, filter, attributes, false, Constraints);
With this example, we conclude our discussion of the search and compare methods. There are many more methods included in the software development kit. To explore them all you can use the Internet draft provided by IETF and study the documentation shipped with the SDK you are using. The Netscape development kit we have used in our examples ships with a complete reference manual, well-documented examples, and an instructive programmers guide.
The last group of methods required by the functional model are the update functions. These include:
add: Add a new entry into the directory.
delete: Delete an entry from the directory.
modify: Modify an entry in the directory. Here we could:
Add an attribute
Modify an attribute
Delete an attribute
modifyDN: Modify the distinguished name of an entry. Again LDAP (v2) and LDAP (v3) are different in this aspect. LDAP (v3) allows you to move the entry inside the DIT and lets you specify the new parent entry.
The add method takes one argument only, the entry to be added in the form of an LDAPEntry object. You build the entry with the constructor using the distinguished name of the new entry and the attribute set the new entry should contain. The example in Exhibit 61 shows the process. The example is self-explanatory, but let us review the steps necessary to add a new entry to the directory. Once we have an LDAPConnection object and we are connected with the directory server, we have to:
Construct the individual attributes.
Bundle the attributes into an attribute set.
Combine the attribute set with the distinguished name and construct the entry.
Add the entry to the directory.
try { // construct an LDAPConnection object and bind to // the directory server ld = new LDAPConnection(); ld.connect(version,host,port,bindDn,pass); // The distinguished name: String dn = "uid=TMorris, ou=Marketing, o=LdapAbc.org" ; // Construct the single Attributes String objectclasses[] = {"top", "person", "organizationalPerson", "inetOrgPerson" }; LDAPAttribute attr0 = new LDAPAttribute("objectclass", objectclasses); LDAPAttribute attr1 = new LDAPAttribute("sn","Morris"); LDAPAttribute attr2 = new LDAPAttribute("givenName","Thomas"); LDAPAttribute attr3 = new LDAPAttribute("cn","Thomas Morris"); LDAPAttribute attr4 = new LDAPAttribute("uid","TMorris"); LDAPAttribute attr5 = new LDAPAttribute("mail", "TMorris@LdapAbc.org"); // construct the LDAPAttribute set LDAPAttributeSet attrSet = new LDAPAttributeSet(); // populate the LDAPAttribute set attrSet.add(attr0); attrSet.add(attr1); attrSet.add(attr2); attrSet.add(attr3); attrSet.add(attr4); attrSet.add(attr5); // create the new Entry LDAPEntry newEntry = new LDAPEntry(dn, attrSet); // add finally the new entry into the directory ld.add(newEntry); if (ld!= null) { // we are done, let us release the server resources . . . ld.disconnect(); } } catch(LDAPException e) { System.out.println(e.toString()); }
The delete method is much easier than the add method. It takes one argument only, the distinguished name to be deleted. Exhibit 62 shows a code snippet demonstrating how the delete method is used. Again, in this example we assume that we are just connected to the directory server and therefore have the LDAPConnection object on which to operate the delete method. Note that the delete method delivers a result of type "void." If there is some problem in deleting the entry, the method throws an exception that we have to catch and handle.
try { ld = new LDAPConnection(); ld.connect(version,host,port,bindDn,pass); String dn = "uid=TMorris, ou=Marketing, o=LdapAbc.org" ; ld.delete(dn); if (ld!= null) { ld.disconnect(); } }
The modify method can do three operations: it can add a new attribute; it can delete an attribute; and it can modify an attribute. The "modify method" call takes two arguments: (1) the distinguished name you want to operate on and (2) a modification set containing the operations you wish to be executed upon the specified entry. The modification set is an object implementing the class LDAPModificationSet. The example in Exhibit 63 shows all three actions. There is no "deeper logic" in the actions executed, however. Exhibit 63 only shows the underlying syntax. Again, the example is self-explanatory. Regarding the delete action, in this example the telephonenumber is deleted, regardless of it value (or values). If we wanted to delete only a particular telephonenumber, we would have to write it as:
LDAPAttribute attr1 = new LDAPAttribute("telephonenumber", "0049-89-1729 9329"); //set delete action modSet.add(LDAPModification.DELETE,attr1);
try { // distinguished name of the entry we operate on: String dn = "uid=TMorris, ou=Marketing, o=LdapAbc.org" ; // instantiate the LDAPModificationSet class: LDAPModificationSet modSet = new LDAPModificationSet(); // construct attribute + value LDAPAttribute attr0 = new LDAPAttribute("mail", "TMorris@hotmail.com"); // set modify action modSet.add(LDAPModification.REPLACE,attr0); // construct attribute + value LDAPAttribute attr1 = new LDAPAttribute("telephonenumber", "0049-89-1729 9329"); // set add action modSet.add(LDAPModification.ADD,attr1); // construct attribute LDAPAttribute attr2 = new LDAPAttribute("telephonenumber"); // set delete action modSet.add(LDAPModification.DELETE,attr1); ld.modify(dn,modSet); }
Should the attribute to be deleted not have the value specified in the attribute constructor, the modify method throws an exception at the moment of execution.
The rename method changes the distinguished (or relative distinguished) name of an entry. If you change only the relative distinguished name (both LDAP [v2]/LDAP [v3]), three parameters are passed to the method. If you change the distinguished name (LDAP [v3] only) moving the entry inside the directory information tree, four parameters are passed. Let us see an example where we change the RDN only. Let us suppose we inserted another entry for a person named Theodore Morris, and now we wish to change the uid for TMorris. In this case, TMorris should now become T1Morris. Exhibit 64 shows how to rename an entry leaving it in the same hierarchical position. The last parameter (Boolean) determines whether the old entry should be deleted. "True" stands for yes, and "false" retains the old entry. The code snippet in Exhibit 65 moves Morris into the Human Resources division, thereby changing the entry's DN and its ancestor.
try { // distinguished name of the entry we operate on: String dn = "uid=TMorris, ou=Marketing, o=LdapAbc.org" ; // new distinguished name String newdn = "uid=T1Morris, ou=Marketing, o=LdapAbc.org" ; ld.rename(dn, newdn, true) ; }
try { // distinguished name of the entry we operate on: String dn = "uid=TMorris, ou=Marketing, o=LdapAbc.org" ; // new distinguished name String newdn = "uid=T1Morris, ou=Marketing, o=LdapAbc.org" ; // The new parent is now HR String newparent = "ou=Human Resources, o=LdapAbc.org" ; ld.rename(dn, newdn, newparent, true) ; }
The LDAP URL format is defined in RFC 2255, "The LDAP URL Format." In this section, we will see only a few example of its use with the Java programming language. Chapter 4 provides additional details.
Java has a class for LDAP URLs, the LDAPUrl class. The class has an overloaded constructor, and you have the choice of specifying as parameter the LDAP URL or giving the single components that form the LDAP URL. The examples in Exhibits 66 and 67 demonstrate the use of the LDAPUrl class. Both examples assume that you already have an active connection object named "ld." Exhibit 66 shows the constructor using the LDAP URL. Exhibit 67 shows the other possibility, where you indicate the single components of the LDAP URL. Note that the LDAP URL can be used only to make a search and not to update the database. Thus, LDAP URLs cannot be used for adding, deleting, or modifying entries.
try { LDAPUrl url = new LDAPUrl ( "ldap://www.LdapAbc.org:389/ou=IT, o=Ldap Abc.org?cn,mail?(sn=S*)") ; } catch (java.net.MalformedURLException) { System.out.println (e.toString()); } try { LDAPSearchResults res = ld.search(url) ; } catch (LDAPException e) { System.out.println (e.toString()); }
String[] attrs = { "cn", "mail" } ; String host = "www.LdapAbc.org" ; int port = 389 ; String baseDN = "ou=IT, o=LdapAbc.org" ; String scope = LDAPConnection.SCOPE_SUB ; String filter = "(sn=S*)" ; // Now we construct the LDAPUrl object: try { LDAPUrl = new LDAPUrl (host, port, baseDN, attrs, scope, filter) ; } catch (java.net.MalformedURLException) { System.out.println (e.toString()); }
The LDAPUrl class has a number of other methods to extract the single components and to achieve encoding and decoding. Encoding is necessary if you have to use special characters not allowed in URLs. If you need more information about the exact URL format, have a look at Chapter 4 and RFC 1738, "Uniform Resource Locators (URLs)." For more details about LDAP URLs, such as the exact syntax of the methods, refer to the documentation delivered with the Java software development kit you are using.
The Java naming and directory interfaces (JNDI) are an alternative to the Java SDK to access a directory using Java. JNDI has been developed by Sun Microsystems in collaboration with Netscape, Novell, IBM, and others. JNDI is a generic interface to access different naming services. A naming service maps names to objects. This allows the user to access an object by its user-friendly name instead of using the name provided by the computer.
The domain name system (DNS), widely used on the Internet, is an example of a naming service. Human beings find it is far easier to remember "www.apache.org" than the IP name "63.251.56.142." A further example is the file system. The computer uses numbers to identify files (called "inodes" in UNIX), but humans prefer recognizable names. A naming system lets human users access a file by its name instead of its inode. In this sense, directory services is a special case of a naming service. Directory services associates a name (the distinguished name) with an object (the entry), thus making it available to the user.
JNDI uses one interface to access a number of different naming and directory services. It does this via so-called service providers. Exhibit 68 shows the architecture of JNDI. The whole API consists of two interfaces: (1) the JNDI interface that gives the name to the API, and (2) the SPI (service provider interface). The application developer uses the JNDI interface, and the service providers use the SPI. The naming manager bridges the two interfaces.
Exhibit 68: JNDI Architecture
The API plus some of the service providers in the schema are included from the Java 2 SDK version 1.3. The following service providers are contained in this Java SDK:
LDAP
Common object request broker architecture (CORBA) common object services (COS) name service
Java remote method invocation (RMI) registry
You can obtain other service providers from a software vendor, for example, NDS from Novell. If you are planning to write a service provider, you can download the instructions from the JNDI site at http://java.sun.com.
We will have a little closer look at JNDI, but first let us see a working example in Exhibit 69. First, we create a hash table to hold the parameters we will need later on. The hash table holds information such as the name of the service provider, the LDAP URL of the directory, etc. We fill the hash table using the "put" method.
import java.util.Hashtable; import javax.naming.Context; import javax.naming.directory.InitialDirContext; import javax.naming.directory.DirContext; import javax.naming.directory.Attributes; import javax.naming.NamingException; class PrintEntry { public static void main(String[] args) { Hashtable hashT = new Hashtable(11); // configure the service provider hashT.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); // the Ldap Server + BaseDn in Url format hashT.put(Context.PROVIDER_URL, "ldap://localhost:389/o=LdapAbc.org"); try { // Create the directory context: DirContext DirContext = new InitialDirContext(hashT); // Get the attributes: char DN = "uid=TParker, ou=IT, o=LdapAbc.org" ; Attributes attrs = DirContext.getAttributes(DN); for (NamingEnumeration ae = attrs.getAll(); ae.hasMore();) { Attribute attr = (Attribute)ae.next(); System.out.println("attribute: " + attr.getID()); // Print each value for (NamingEnumeration e = attr.getAll(); e.hasMore(); System.out.println("value: " + e.next()) ) ; } DirContext.close(); } catch (NamingException e) { System.err.println("Error: " + e); }
The next object to be created is the context. Recall that a service associates names with objects. The association between a name and an object is called "binding." A set of bindings is called a "context." The context is represented using the javax.naming.Context interface. The context is then used to get further information from the directory. In our example, we got the attributes from the entry object.
Once you have the context object, you can get an attribute set using the getAttributes method with the distinguished name as a parameter. From the parameter set, you can choose the single parameters with the "get" method.
The JNDI API offers much more functionality, including the ability to register callbacks to an event-processing system. This allows you to program the software to take some action if a determined event succeeds. For example, you could launch a procedure if the data in a certain entry changes.
For more information about JNDI, see the documentation shipped with the software. Software, reference manuals, tutorials, and examples are available from http://java.sun.com.
Enterprise JavaBeans — a set of rules to write Java programs — is an interesting and useful technology. Nevertheless, this topic is so large that it is covered in several books, articles, and tutorials available online from Sun Microsystems at their Web site — http://java.sun.com.
So what, exactly, are JavaBeans? They are simply normal Java objects that obey certain rules. These objects behave like other Java objects. There is really nothing special about them. Beans allow other programs to understand the methods and variables they offer. The capacity to understand the structure of an object is called "reflection." Reflection allows Java programs to inspect Java objects at run time. From the application programmer's view, this means that she can use a JavaBean without knowing about its inner structure. At run time, the application can look to see which methods the Bean offers to access the single components. So the application programmer does not even rely on the documentation delivered with the Bean because she can get all information from the class itself.
A JavaBean is nothing but a layer of standardization. This allows you to use graphical tools to work with JavaBeans and to put together more JavaBeans to build an application without writing a single line of code. The standardization layer is called a "design pattern." We will not delve into the details of JavaBeans, but let us see an example of a design pattern for properties.
To inspect the properties of a JavaBean, you need the following methods:
Method for getting the current value of the property
Method for setting the value of the property
Let us see an example. If you wanted to write a bean for a connection to the LDAP server, you would need the following methods to get and set the distinguished name:
public char getDN() ; public void setDN();
For a more complex task, you would need the following methods to get and set an entire entry:
public LDAPEntry getUser(UsrId) ; public void setUser(UsrId) ;
Using this syntax, a program can automatically inspect the bean and act accordingly.
A further advantage of JavaBeans is their ability to serialize objects. This means that you can save entire objects, including all of their actual variables. Later on, you can restore this object, allowing it to spring to life and continue behaving as if no interruption had ever occurred.
As noted previously, Beans allow you to put together a number of components (i.e., Beans) with graphical tools. Sun delivers the Bean development kit available for free from the site where you get all Java software (http://java.sun.com). There are entire Java development environments that support JavaBeans, including Sun's Forte for Java, Borland/Oracle's JBuilder, IBM's Visual Age, and many others.
Java offers a rich choice of APIs and entire development environments to help you in programming applications that access directories. In this section, we have seen the "standard" Java SDK, the JNDI API, and JavaBeans. Unfortunately, the scope of this book prevents us from discussing these subjects at greater length. Our objective is to show you what you can achieve with this technology and direct you to Web sites where you can obtain the software, documentation, tutorials, and other useful information.
This section is far from being complete because it has not yet mentioned other possibilities that could be appealing for applications that deal with directories. Server-side Java or application servers using Java server pages and many other technologies are available to power application development. If you want to keep up with the latest news, you should consult the following sites:
http://java.sun.com: A source for all Java APIs and software development kits. The site not only contains the software, but also excellent reference manuals, tutorials, and examples. Nearly all of the software and the documentation is available for free.
http://www.apache.org: Contains a lot of application servers written in Java plus a Java module that integrates the Java application server with the Apache Web server
http://www.openldap.org: The open-source implementation of the LDAP (v3) standard LDAP server. Many of the people involved in the OpenLDAP project are also authors of the standards proposed in the RFCs.
http://www.netscape.com: The commercial counterpart of the OpenLDAP project
http://developer.novell.com/ndk/: A source for the alternative Java development kit. Like the Netscape browser, this is also available for free.
http://www.ietf.org: The site that contains the standards
These are not the only sites offering good documentation about Java and LDAP-related software, but they are good starting points.
| < Day Day Up > |
|