DSML gives you the power to access your LDAP information as XML
The Directory Service Markup Language adds XML functionality to your directory services
In today’s e-business environment, effective and efficient data management is crucial. As such, two technologies prove vital to proper data management: directory services and XML. Directory services allow you to store and manage data, and are thus a necessary prerequisite for conducting online business. XML provides an effective way to present and transfer data. With that in mind, there’s a clear need to bridge the two technologies. The solution may be the new Directory Service Markup Language (DSML) standard, designed to make directory services more dynamic by employing XML.
DSML, in conjunction with technologies like XSL and EJB, provides a unique way to solve many business problems, such as supply chain management and customer support. The technology was developed out of work done by Bowstreet and completed by the DSML Working Group; the latter submitted the technology to the W3C and OASIS standards groups in December 1999.
Numerous products now support DSML, including Sun’s recently released DSML Service Provider 1.2 technology, released for use with J2EE 1.2, and Exolab’s Castor API, which provides services to extract information from LDAP as DSML, as well as . With the DSML Service Provider, which implements the javax.naming.directory.DirContext
interface, you can access a DSML document, manipulate and update its contents by using the JNDI APIs, and then re-export the contents in DSML format.
In this article, we will discuss how to create dynamic content and manipulate a directory server with DSML. We will do this using Exolab’s Castor, servlets, and XSL. We will not cover information on how to use XSL or servlets, but for more information you can refer to Michael Ball’s earlier article, “XSL Gives Your XML Some Style” (JavaWorld, June 30, 2000).
Download the source code associated with this article.
The DSML specification
The DSML specification provides both a DTD and a schema for reference (see Resources for download information).
The DSML namespace is: http://www.dsml.org/DSML
; it should be referenced in all your DSML documents, as in:
<dsml:dsml xmlsn:dsml:"
A DSML document can describe directory entries, a directory structure, or both. The root node in a DSML document is always dsml
. Children can be directory-schema
and/or directory-entries
.
<dsml:dsml xmlns:dsml=
<dsml:directory-schema>
...
</dsml:directory-schema>
<dsml:directory-entries>
...
</dsml:directory-entries>
</dsml>
A directory-entries
node has one or more directory-entry
nodes as its children. Each of these directory-entry
nodes contains the object class information as well as attributes for the information, as seen below:
<dsml:directory-entries>
<dsml:directory-entry dn="uid=mball, ou=consultant,
o=sark.com,c=us">
<dsml:objectclass>
<dsml:oc-value>top</dsml:oc-value>
<dsml:oc-value>person</dsml:oc-value>
<dsml:oc-value>organizationalPerson</dsml:oc-value>
<dsml:oc-value>inetOrgPerson</dsml:oc-value>
</dsml:objectclass>
<dsml:attr
name="sn"><dsml:value>Ball</dsml:value></dsml:attr>
<dsml:attr
name="uid"><dsml:value>mball</dsml:value></dsml:attr>
<dsml:attr
name="mail"><dsml:value>[email protected]</dsml:value></dsml:attr>
<dsml:attr
name="givenname"><dsml:value>Michael</dsml:value></dsml:attr>
<dsml:attr name="cn"><dsml:value>Michael
Ball</dsml:value></dsml:attr>
</dsml:entry>
</dsml:directory-entries>
If an attribute is multivalued, there would be many <dsml:value>
nodes under the <dsml:attr>
node.
The directory-schema
node contains information about the object classes and the attributes. As an example, take a look at this sample XML pulled from the DSML specification:
<dsml: class
id="person"
superior="#top"
type="structural">
<dsml:name>person</dsml:name>
<dsml:description>...</dsml:description>
<dsml:object-identifier>2.5.6.6</object-identifier>
<dsml:attribute ref="#sn" required="true"/>
<dsml:attribute ref="#cn" required="true"/>
<dsml:attribute ref="#userPassword" required="false"/>
<dsml:attribute ref="#telephoneNumber" required="false"/>
<dsml:attribute ref="#seeAlso" required="false"/>
<dsml:attribute ref="#description" required="false"/>
</dsml:class>
Using DSML with Exolab’s Castor API
Exolab, according to its homepage, works on the development of open source enterprise software projects, of which the Castor API is one. DSML is a subset of Castor. Be aware that the documentation for the DSML portion of Castor is scarce. With that in mind, we’ll try to highlight the basic functionality of the API.
Note: In this article, we used Castor version 0.8.5.
Castor’s DSML API
Castor can import and export directory information. Meanwhile, the DSML specification describes producers (used for exporting) and consumers (used for importing). Both of these services are divided into types 1 and 2 based on the document levels that they can handle. The four document levels are:
- Level 1: A document containing no schema information, just directory entries
- Level 2: A document containing directory entry information and a link to an external schema
- Level 3: A document containing only a directory schema
- Level 4: A document containing a directory schema and directory entries
A type 1 producer can handle the level 1 documents, while a type 2 producer can handle document levels 2, 3, and 4.
Meanwhile, a type 1 consumer must handle all four document levels. A type 2 consumer must handle all four document levels and make use of schema information.
Castor supports type 1 producers and consumers.
Producers (exporting)
Castor can provide level 1 and level 4 documents. The class org.exolab.castor.dsml.Exporter
retrieves directory information from a directory server.
The Exporter
class uses a org.exolab.castor.dsml.SearchDescriptor
to define search parameters. The SearchDescriptor
can be configured by calling setters on it. Table 1 defines some of the important methods available in the SearchDescriptor
class.
The methods available to class SearchDescriptor
void SearchDescriptor() |
Constructor |
void addReturnAttr(java.lang.String attrName) |
Causes exporter to only return attributes added to the SearchDescriptor |
void setBaseDN(java.lang.String baseDN) |
Sets the base domain name to search on |
void setFilter(java.lang.String filter) |
Sets a filter to search on (i.e., uid=lpoe ) |
void setScope(int scope) |
1= , 2= , 3= |
java.lang.String getBaseDN() |
Gets the base domain name to search on |
java.lang.String getFilter() |
Gets the filter used to search |
java.lang.String[] getReturnAttrs() |
Gets an array of the attributes to retrieve |
int getScope() |
Gets the scope |
java.util.Enumeration listReturnAttrs() |
Gets an Enumeration of the attributes to retrieve |
To use XML to define the search configuration, Exporter
employs the following methods:
void readSearchDescriptor(java.io.InputStream input)
void readSearchDescriptor(java.io.Reader input)
The XML used would look like:
<dsml>
<search base="o=javaworld.com" scope="subtree"
filter="(uid=stigger)">
</search>
</dsml>
Exporter
uses the SearchDescriptor
to determine what information to get from the directory server. To invoke exporting, Exporter
includes two method calls:
void export(java.io.OutputStream output, boolean serverSchema, boolean importPolicy)
void export(java.io.Writer output, boolean serverSchema, boolean importPolicy)
Both methods require two flags, although we yielded the same result regardless of the their settings (no documentation on the flags’ meaning was forthcoming).
Consumers (importing)
Castor can consume level 1 documents. The org.exolab.castor.dsml.Importer
class puts directory information into a directory server.
Importer
uses the base constructor and defines three methods for importing DSML:
void importDocument(java.io.InputStream stream)
void importDocument(org.xml.sax.Parser parser, org.xml.sax.InputSource input)
void importDocument(java.io.Reader reader)
These methods take the DSML that you want to import. In addition to the DSML, you can also define an ImportDescriptor
. The ImportDescriptor
defines the location to where you are importing and configures restrictions on deleting or updating attributes. The following methods, which take XML as input, are used to set up the ImportDescriptor
:
void readImportDescriptor(java.io.InputStream input)
void readImportDescriptor(java.io.Reader input)
The XML used with the readImportDescriptor()
method would look something like this:
<dsml>
<import-policies>
<import-policy dn="o=javaworld.com" delete-empty="true"
replace-attr="true" refresh-only="false"/>
</import-policies>
</dsml>
The example
In a directory server, it’s useful to have users separated into groups. As such, in our example we will create a dynamic way to distribute our users into groups through a Web interface. By retrieving a list of all the users in our organization, we can select the users we want to be in a group and load the new group into our directory server.
This example will illustrate two uses of DSML: data extraction for presentation purposes and directory-service entry creation with XSL and Castor.
To accomplish our task, we’ll employ a servlet that uses the Castor API to retrieve data from a directory server. Then we will transform the resulting DSML into HTML using XSL.
Retrieve a list of users
The servlet below — DisplayUser.java
— retrieves DSML from a DirectoryServer
using Castor’s Exporter
class. It then strips the namespace off the DSML because the Xalan processor cannot handle namespaces in source XML. Finally, the servlet uses XSL to style the DSML in order to create HTML. Figure 1 shows a static HTML page used to perform the search of a directory server.
Here’s the DisplayUser.java
servlet’s code:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.naming.directory.InitialDirContext;
import javax.naming.Context;
import netscape.ldap.LDAPConnection;
import java.util.Hashtable;
import org.exolab.castor.dsml.Exporter;
import org.exolab.castor.dsml.jndi.JNDIExporter;
import org.exolab.castor.dsml.SearchDescriptor;
import RemoveNamespace;
public class DisplayUser extends HttpServlet
{
InitialDirContext ctx;
public void init()
{
LDAPConnection conn;
Hashtable env;
try
{
//connect to LDAP Server
env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.PROVIDER_URL, "ldap://localhost:389");
env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
env.put(Context.SECURITY_CREDENTIALS, "password");
ctx = new InitialDirContext(env);
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
public void doPost(HttpServletRequest req, HttpServletResponse res)
{
try
{
RemoveNamespace remover = new RemoveNamespace();
Search search = new Search(ctx);
StringBuffer xsl = new StringBuffer();
String results = "";
String dsml = "";
//get parameters
String attributeName = req.getParameter("attributeName");
String searchValue = req.getParameter("searchValue");
//search directory
results = search.doSearch(attributeName,searchValue);
//remove the namespace from dsml
dsml = remover.remove(results);
//style search results
XSLUtil xslUtil = new XSLUtil();
File file = new File("DisplayUser.xsl");
String html = xslUtil.styleXSL(file, dsml);
PrintWriter out = res.getWriter();
res.setContentType("text/html");
out.write(html.toString());
}
catch(Exception e)
{
e.printStackTrace();
}
}
public String doSearch(String attribute, String searchValue)
{
Exporter exporter = new JNDIExporter(ctx);
SearchDescriptor sd = new SearchDescriptor();
StringWriter writer = new StringWriter();
try
{
//configure the search descriptor
//set the domain name to search
sd.setBaseDN("o=javaworld.com");
//set the scope of the search
sd.setScope(3); //scope 1 = base, 2 = onelevel, 3 = subtree
//setFilter for Directory Server
//in our case the attribute is departmentnumber
sd.setFilter(attribute+"="+searchValue);
//assign search descriptor
exporter.setSearchDescriptor(sd);
//perform the search of the Directory Server
exporter.export(writer,false,true);
}
catch(Exception e)
{
e.printStackTrace();
}
//return String (dsml) of search results
return writer.toString();
}
}
Next, let’s take a look at the XSL code used to transform the DSML pulled from LDAP into HTML:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="
xmlns:dsml="
<xsl:template match="/">
<html>
<head><title>Search Results</title></head>
<body>
<form action="CreateGroup" method="post">
<table class="legacyTable" border="1" width="100%">
<tr>
<th> Employee Name</th>
<th> User ID</th>
<th>E-Mail</th>
<th>Phone</th>
<th>Fax</th>
<th>Update</th>
</tr>
<xsl:for-each select="dsml/directory-entries/entry">
<tr>
<td>
<xsl:value-of select="attr[@name="cn"]/value"/>
</td>
<td>
<xsl:value-of select="attr[@name="uid"]/value"/>
</td>
<td>
<xsl:value-of select="attr[@name="mail"]/value"/>
</td>
<td>
<xsl:value-of select="attr[@name="telephonenumber"]/value"/>
</td>
<td>
<xsl:value-of
select="attr[@name="facsimiletelephonenumber"]/value"/>
</td>
<td>
<xsl:element name="input">
<xsl:attribute name="type">checkbox</xsl:attribute>
<xsl:attribute name="name">member</xsl:attribute>
<xsl:attribute name="value"><xsl:value-of
select="@dn"/></xsl:attribute>
</xsl:element>
</td>
</tr>
</xsl:for-each>
</table>
<hr width="100%" noshade="true"></hr>
<table class="legacyTable">
<tr>
<td>
Group Name:
<input type="text" size="20" name="GroupName"/>
</td>
<td>
<input type="submit" value="Submit"/>
</td>
<td>
<input type="reset" value="Reset"/>
</td>
</tr>
</table>
</form>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
In Figure 2, we see the HTML that results after the XSL styles the DSML.
Create a group in DSML
Next, the CreateGroup.java
servlet will take the HTTPRequest
and format the parameters into XML. For example:
CreateGroup?member=uid=lpoe,ou=People&member=uid=mball,ou=People&GroupName=Accountants
would be translated into:
<data>
<member>uid=lpoe,ou=People</member>
<member>uid=mball,ou=People</member>
<GroupName>Developers</GroupName>
</data>
The XSL stylesheet below then takes the XML and styles it into DSML:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl=">
<xsl:template match="/">
<xsl:element name="dsml">
<xsl:element name="directory-entries">
<xsl:element name="entry">
<xsl:attribute name="dn">cn=<xsl:value-of
select="data/GroupName"/>,ou=Groups,o=aici.com</xsl:attribute>
<xsl:element name="objectclass"><xsl:element
name="oc-value">top</xsl:element><xsl:element
name="oc-value">groupofuniquenames</xsl:element></xsl:element>
<xsl:element name="attr">
<xsl:attribute name="name">cn</xsl:attribute>
<xsl:element name="value">
<xsl:value-of select="data/GroupName"/>
</xsl:element>
</xsl:element>
<xsl:element name="attr">
<xsl:attribute name="name">uniquemember</xsl:attribute>
<xsl:for-each select="data/member">
<xsl:element name="value"><xsl:value-of
select="."/>,o=javaworld.com</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The XSL stylesheet above creates the following DSML:
<?xml version="1.0" encoding="UTF-8"?>
<dsml>
<directory-entries>
<entry dn="cn=Developers,ou=Groups,o=aici.com">
<objectclass>
<oc-value>top</oc-value>
<oc-value>groupofuniquenames</oc-value>
</objectclass>
<attr name="cn">
<value>Developers</value></attr>
<attr name="uniquemember">
<value>uid=LDPoe,ou=People,o=aici.com</value>
<value>uid=MWBall,ou=People,o=aici.com</value>
</attr>
</entry>
</directory-entries>
</dsml>
Now that we’ve created the DSML, we can load it into the directory server using the Importer
class:
public String doCreate(String dsml)
{
Importer importer = new JNDIImporter(ctx);
PrintImportListener printer;
StringWriter out = new StringWriter();
try
{
StringWriter writer = new StringWriter();
//set writer
printer = new PrintImportListener( new PrintWriter( out ) );
//set event listener
importer.setImportEventListener( printer );
//set the import policy
importer.readImportDescriptor(new FileReader("import.xml"));
//insert records into LDAP
importer.importDocument(new StringReader(dsml));
}
catch(Exception e)
{
e.printStackTrace();
}
return out.toString();
}
We can now confirm that the group entry, as well as its two new members, was added to LDAP, as illustrated in Figure 3.
Taking from the example general concepts such as data extraction and object generation, we can open up a world of opportunities to use and manage the data stored in your directory-service.
Conclusion
Directory services are powerful tools common in most of today’s architectures. They store information about people, hardware, and even objects. But as directory-service use becomes more pervasive, a standard way to manipulate the information inside of them and transfer data between them becomes necessary. By taking advantage of the power of XML, DSML provides a powerful interface into directory servers. As XML tools become more and more common, using them to access DSML will become easier, and your directory servers will become more useful and dynamic.