Novell is now a part of Micro Focus

Programming NetWare with Java

Articles and Tips: article

KRISTOPHER MAGNUSSON
Lead Technical Writer
Java Technology Group

01 Jan 1998


The Java Naming and Directory Interface (JNDI) provides a number of benefits to NetWare users. Since the Novell SDK for Java is now installed by default on all new NetWare servers, developers can expect to run and debug JNDI programs directly on the server in a GUI with no extra hassle. This article first of all reviews JNDI architecture; it then analyzes a JNDI application that shows how to program several fundamental operations; lastly, it deconstructs the application.

Introduction

Java is an excellent platform for creating network-aware applications. But when it comes to directory services, the core API has no facilities at all.

Although the first two major releases of Java had APIs for accessing certain naming resources--files, DNS names, and URLs--it wasn't directory aware. So over the course of a year, Novell, JavaSoft, and other Java allies created the Java Naming and Directory Interface (JNDI) to remedy that problem. JNDI is scheduled for inclusion in the Java core API with release 1.2.

JNDI brings all types of naming systems--the NetWare file system, the bindery, even NDS--under its umbrella of a few straightforward Java interfaces. Further, JNDI allows you to program these naming systems with a few common interfaces and classes.

With JNDI, no NLMs or special compilers and debuggers are required--your client- and server-side code is now the same, and you can use on both client and server the same Java Development Kits to which you have grown accustomed. In fact, since the Novell SDK for Java is now installed by default on all new NetWare servers, you can expect to run and debug your JNDI programs directly on the server in a GUI with no extra hassle.

Currently, JNDI is a beta API. Therefore the information presented here may differ from versions of JNDI and its service providers released after the December release of Novell's JNDI packages.

JNDI Architecture

JNDI is made up of two layers:

  • the application interface. Includes the classes and pure interfaces that NetWare developers will use in their Java applications.

  • the service provider interface. Includes the implementations of the pure interfaces for particular naming systems. These implementations call methods in the underlying native interfaces of NDS, NetWare, the file system, etc.

Further, the service provider interface connects naming systems to each other in a logical manner. This connection is called federation. It is transparent to us and our applications.

Application Interfaces and Classes

As it turns out, naming system programmers only need a few interfaces and classes to abstract all the contexts and attributes under the sun at Novell and elsewhere. Most naming system and directory APIs have similar operations that do things such as "give me all of an object's subordinates," "create a new object," "delete an existing object," and so forth. JNDI wraps up these operations in a single interface, Context.

java.naming.Context. The Context interface contains JNDI's general naming mechanism. It represents the notion of a context.

The formal definition of a context is an object whose state is a set of bindings, most importantly a name-to-object binding. The general notion of "an object whose state is a set of bindings" is a context with a lowercase "c." This differentiates it from the Java interface named after it, Context.

But this is all directory-speak for the simpler notion that anything that can have subordinates is a context. With this notion, JNDI provides a huge benefit to us. Everything we see in JNDI is a Context, without regard to its underlying semantics.


Method
Purpose

lookup( )

Looks for a particular context or set ofcontexts. Returns a java.lang.Object,which you must narrow to the appropriate class. You must specify a String valuefor the Object you want.

list( )

Lists subcontexts. Returns an NamingEnumeration of NameClassPairs,which contain the name of each subcontext and its class. This method works muchlike the dir or ls commands, which list the files and directories subordinate to thecurrent directory.

bind( ), unbind( )

Creates or deletescontexts.

Applying the notion that "anything that can have subordinates is a context" to other aspects of naming leads us to the conclusion that JNDI can represent any hierarchical naming system. ZIP files, stock quotes, even Java packages and classes neatly fit into the Context interface.

This is exactly the kind of programming ease Novell and JavaSoft sought in designing JNDI. With JNDI you can access any naming system--no matter how different its semantics--using a single interface.

java.naming.directory.DirContext. You can think of a DirContext as a Context that has attributes. And you can treat it just like a Context, since DirContext actually extends Context.

DirContext overloads certain Context methods with new signatures. Most enable you to specify an AttributeSet as a parameter to Context methods. These enable you to perform the same Context methods using DirContext semantics, such as specifying attributes in the method signature.

Table 2: DirContext Methods


Method
Purpose

search( )

Returns a particular context or set of contextsusing a search and scope criterion.You can specify particular attrID s when searching for specific DirContexts.

getSchemaClassDefinition( )

Returns information about what mandatoryattributes a particular DirContext must contain. Also, retrieves information about the contexts a directory contextcan contain or be contained by.

getSchema( )

Returns the entire set of metainformationabout all DirContext s in a namingsystem.

getAttributes( )

Retrieves the set of attributes of a particulardirectory context. Returns a NamingEnumeration, which you can walk through to find a particular attrID.

In addition to overloading existing methods, DirContext adds some methods of its own. These enable you to perform methods specific to directory contexts, such as working with schemae and attributes.

A typical DirContext is an NDS user object. It is a context, but it also has specific attributes that make it meaningful in the NDS naming system. These attributes include a user name, inherited rights, and so forth.

Other attributes are described in a DirContext's schema. These include containment rules, such as what the parent of an NDS user object should be. The schema also includes attributes it must have in order to contain meaningful information.

java.naming.directory.Attributes. A set of Attributes contains the individual Attribute s that belong to a DirContext. You access this class using the getAttributes( ) method.

An NDS user object's attribute set might include the user object's account name, generational qualifier, telephone extension, and login script.


Method
Purpose

getAll( )

Returns all attributes in theset.

getIDs( )

Returns all attribute IDs in theset.

put( )

Modifies the value of a particularattribute.

java.naming.directory.Attribute. An Attribute contains two instance variables, an attrID and its values. You access these using the getId( ) and getValues( ) methods.

You typically print out its attrID, which is a String object. But in contrast to its single String value, an attribute ID can have many values, each of different classes. This makes printing out attribute values problematic, since it's not certain that each value has a printable string representation.

Therefore the best thing to do with the many values of an attribute ID is to look for a value that belongs to a particular class. Once you have the value of the particular class, you can perform certain operations specific to the semantics of its class.

More on the Package Namespace

To better remember which classes perform particular methods, JNDI is grouped into the following packages:

java.naming. Contains Context and its support classes. You will need to import this package any time you want to work with JNDI.

java.naming.directory. Contains the DirContext interface and the Attribute classes. Import this package when you want to work with DirContext s.

java.naming.spi. Contains the Service Provider Interface (SPI), which is the programmatic glue for connecting naming systems to each other. You will never need to import this package unless you have to write a new service provider for JNDI. You may indeed have to do this at some point, but discussing exactly how to work with the SPI is an advanced topic.

com.novell.service. Though not strictly a part of JNDI, this package contains Novell's implementations of the NetWare naming systems--NDS, NetWare file systems, NCP extensions, and so forth--in the form of service providers. JNDI architects recommend you import only the particular service providers you need in your application.

The following lists all the service providers Novell ships in this package, as well as their package names.


Name(com.novell.service.*)
Naming system

bindery

Bindery

file

NetWare file system

ldap

Lighweight Directory AccessProtocol

ncpext

NCP extension

nds

NDS

nw

All NetWare namingsystems

qms

NetWare queues

server

NetWare servers

Service Providers and Polymorphism

We've seen that Context and DirContext represent objects that can have subordinates and attributes. But an NDS organizational unit and a NetWare volume are wildly different. How can the same methods return results that make sense when the underlying objects performing the methods differ greatly?

Performing the same methods on different objects works using polymorphism, a core principle of object orientation. Polymorphism enables different objects that have a method with the same name to perform the method according to the semantics of the individual objects.

For example, in JNDI, the mechanism for implementing polymorphism is the Context interface itself. NDS organizational units and NetWare volumes must implement Context in ways meaningful to themselves.

As an example, let's look at list( ), which lists the subordinates of a Context.

  • Calling list( ) on the hypothetical context OU=ENGINEERING, an NDS organizational unit, will return OU=JAVA, OU=CLIB, and OU=NETBASIC, which are also organizational units.

  • Calling list( ) on the hypothetical context SYS:\VOL1, a NetWare volume, will return Homes and Applications, which are directories. It will also return UsagePolicy.wri, which is a file that is an MS WordPad document.

Each of these list( ) methods list subordinates of a context. But they are implemented differently for the context's naming system. In other words, list( ) on an NDS organizational unit returns subordinates meaningful in NDS, and list( ) on a NetWare volume returns subordinates meaningful in the file system.

This can happen because the authors of the NDS and file system service providers know that a list( ) is supposed to list the subcontexts of a Context. Therefore the authors program into the service providers the appropriate native calls to list the subcontexts for their own naming systems.

Federation

Chances are good you already know an NDS volume object references a physical volume. So it may not come as a surprise that performing lookup( ) on an NDS volume DirContext returns the actual physical volume. The conceptual problem is the NDS volume is in the NDS naming system, and the physical volume is in the file system naming system. How can we federate from NDS to the file system? We don't--JNDI does.

Under the hood, service providers handle all the federation for us. When we perform a list( ) on an NDS volume object, JNDI asks the service provider for the next naming system. This causes the service provider to perform federation methods. These methods decompose the name of the Context into indivisible, atomic names, and thereby determine what next naming system the service provider should return to the application.

Service provider authors understand implicitly that Context s of certain classes must federate. So when implementing the list( ) method on a Context in a particular naming system, they program into the provider what those next naming systems are.

In the case of the NDS server object, the next naming system is the file system. But in the vase of the NetWare naming system, there are two next naming systems, Trees and Servers, each of which has explicit bindings to all known trees and servers. The types of the next naming system are therefore dependent on the semantics of the naming system.

This notion of federation is what enables JNDI applications to navigate NetWare's global namespace. Without federation, you would have to program federation code explicitly. With federation, this burden is lifted from your shoulders.

A JNDI Application

How should a tutorial application demonstrate fundamental JNDI programming? A good approach is to demonstrate the fundamental operations you will perform to access NDS in Java. This is a common JNDI task.

These operations include:

  • entering the naming system at the appropriate place

  • finding a particular context by name

  • displaying the context's attributes

  • displaying the context's schema class definition.

The following application, SimpleBrowser, demonstrates how to program these fundamental operations.

Figure 1: SimpleBrowser.java: The code listing

import java.io.*;

import java.lang.*;

import java.util.*;



import com.novell.service.*;



import com.sun.java.naming.*;

import com.sun.java.naming.directory.*;



public class SimpleBrowser extends Object {



     static String contextName, serviceUrl;



     public SimpleBrowser(String[] argv) {



          System.out.println("\r\nConstructing a new instance of SimpleBrowser.");



          serviceUrl = argv[0];

          contextName = argv[1];



     }



     public Context createInitialContext() {



          System.out.println("\r\nCreating an initial context.");

          Properties props = new Properties();

          props.put("java.naming.factory.initial",

               "com.novell.service.nds.NdsInitialContextFactory");

          props.put("java.service.url", serviceUrl);

          return (new InitialDirContext(props));

     }



     public DirContext getContextWithName(String _name) throws NamingException {



          Context theContext = createInitialContext();



          System.out.println("\r\nListing subordinates of the initial context.");



          try {

               NamingEnumeration subordinates = theContext.list("");

               NameClassPair pair;



               while (subordinates.hasMoreElements()) {



                    pair = (NameClassPair)subordinates.next();



                    System.out.println("\r\n" + pair.getName());



                    if (pair.getName().equalsIgnoreCase(_name)) {



                         try {



                              Object testContext = theContext.lookup(_name);



                              System.out.println("\r\nContext found.");

                              System.out.println("Its class is " 

                                   + testContext.getClass().getName());



                              if (testContext instanceof DirContext) {

                                   return (((DirContext)testContext));

                              }

                         }



                         catch (NoInitialContextException e) {

                              System.out.println("Received " + e +

                                   ". Usually this occurs when you have not specified the

                                        appropriate service URL.");

                         }



                         catch (Exception e) {

                           System.out.println("Received " + e + " while performing lookup.");

                         }

                    }

               }

          }



          catch (Exception e) {

               System.out.println("Received " + e + " while performing list.");

          }



          return null;

     }



     public void displayAttributes(DirContext _context) throws NamingException {



          Attributes attributes = _context.getAttributes("", null);



          NamingEnumeration attributeIds = attributes.getIDs();



          while (attributeIds.hasMoreElements()) {

               System.out.println((String)attributeIds.next());

          }

     }



     public void displaySchemaClassDef(DirContext _context) 



          throws NamingException {



          System.out.println("\r\nIts schema class definition is:");

          DirContext schemaClassDef = _context.getSchemaClassDefinition("");

          displayAttributes(schemaClassDef);

     }



     public static void main(String[] argv) {



          SimpleBrowser myself = new SimpleBrowser(argv);



          try {

               DirContext currentContext = myself.getContextWithName(contextName);



               if (currentContext != null) {

                    System.out.println("\r\nIts attribute IDs are:");

                    myself.displayAttributes(currentContext);

                    myself.displaySchemaClassDef(currentContext);

               }



               else {

                    System.out.println("\r\nDidn't find a DirContext called " 

                         + contextName );

               }

          }



          catch (Exception e) {

               System.out.println("Received " + e +

                    " while searching for DirContext "

                    + contextName + ".");

          }

     }

}

SimpleBrowser.java has four methods, not counting the constructor and main( ).

  • createInitialContext( ): Returns an initial context in the NDS naming system.

  • getContextByName( ): Browses the naming system for a particular directory context and returns it.

  • displayAttributes( ): Lists the attribute IDs of the directory context.

  • displaySchemaClassDef( ): Lists the schema class definition of the directory context.

Executing SimpleBrowser

SimpleBrowser requires that you have already installed the following software components:

  • NetWare Client 32 for Windows 95 or NT

  • NetWare SDK Release 13

  • OSA December release

Before you can execute SimpleBrowser, you must compile it. This means you must have previously installed the JNDI client package that shipped with the OSA December release.

When executing the application, you must specify which NDS context is the service host. Because of the design of the application, the service host must be a tree. You can find a tree on your intranet by examining the NDS trees in your computer's Network Neighborhood. (This only works on Win32 platforms.) Choose a tree and remember its name.

You also need to tell the application which context you really want to examine. You can find a context directly subordinate to the service host by listing the subordinates of the tree you just chose. Any of these subordinates is suitable.

We could have hard coded this information into SimpleBrowser, but doing so would force us to recompile the code every time we wanted to specify different information. We also could have architected the application to read the information from a file, but specifying the information as parameters provides us with direct feedback. This removes the step of editing a file, then reexecuting the application, thereby providing a direct experience with the application.

The following is a sample session with SimpleBrowser.java. In this session, the application is executing on an NT box located on Novell's intranet. The application looks for the directory context Novell, and browses the NDS naming system at the service host NOVELL_INC, which is an NDS tree. Note that this same code will execute on a NetWare server with the JNDI client installed.

Figure 2: SimpleBrowser.java: A sample session

C>java SimpleBrowser NOVELL_INC Novell



Constructing a new instance of SimpleBrowser.



Creating an initial context.



Listing subordinates of the initial context.



Novell



Context found.

Its class is com.novell.service.nds.naming.OrganizationDirContext



Its attribute IDs are:

Replica

Replica Up To

Revision

Partition Status

Base Class

CA Public Key

Object Flags

Partition Control



Its schema class definition is:

Object Super Classes

Object Containment Classes

Flags

Object Optional Attributes

Object Naming Attributes

Object Mandatory Attributes

ASN1Name

What Does this Output Mean?

First, the application reports that it is constructing a new instance of itself, that it is creating an initial context, and that it is listing the subordinates of the initial context. In this session, the only context subordinate to NOVELL_INC is Novell. This is the context we are looking for.

Next, the application reports that has found the context we specified, and that its class is com.novell.service.nds.OrganizationDirContext, which is part of Novell's NDS service provider. The NDS provider includes classes for each distinct type of NDS object. You can find a complete list of these classes in the HTML-based Service Provider Reference that ships with every JNDI client installation.

After reporting Novell's provider class, the application reports the context's attribute IDs. These attribute IDs are the same attributes listed when you examine the object in NWAdmin. Each of these attribute IDs can have multiple values. The application doesn't list the values because in many cases, a value is an object that doesn't have a nice toString( ) representation. Rather than print to stdout escape characters that will likely make your computer beep gratuitously, the application ignores the values altogether.

The final part of the output is the context's schema class definition. This definition describes metainformation about the context. This metainformation includes what objects can contain it, what objects can be subordinate to it, what its flags are, and its mandatory and extended (optional) attributes. This definition is useful when determining what kind of subordinate objects you can create , as well as the attributes you must set if you create a new object of this class, and the attributes you can optionally set.

Initial contexts

Starting at the beginning, we ask ourselves in which naming system we want to browse.

Browsing requires that you choose as a starting place what JNDI terms an initial context factory, which is a special object that creates an instance of the initial context you want. (For those who want to become buzzword-compliant, Factory is a well-known pattern. A Factory object's role in life is to create instances of other objects.)

The factory creates an initial context that is the starting point that we are looking for. In our case, it is the very first context we will access in the NDS naming system.

Initial is a relative term, especially in JNDI, which holds absolutely no notion of an absolute root of a naming system. Because of this relativity, starting out in the NDS naming system requires that we specify the service host, the NDS tree that contains the context we really want.

Deconstructing the Application

We already learned the important methods SimpleBrowser contains. But what are the subtleties of JNDI we need to know to make it work?

Declaring SimpleBrowser and its Variables

As with all Java classes, the first thing we must do is declare the class, its class variables, and its constructor. The only class variables we need to set are the names of the service host and context.

public class SimpleBrowser extends Object {



     static String contextName, serviceUrl;

Other methods in the object must work with the name of the context we want to browse, as well as the service host. So we specify these as class variables.

The constructor assigns the command-line arguments to the class variables:

public SimpleBrowser(String[] argv) {



     System.out.println("\r\nConstructing a new instance of SimpleBrowser.");



     serviceUrl = argv[0];

     contextName = argv[1];



}

We use class variables because several methods in the class require these names to operate, and because if we want to reuse the code later on in our own programs, we most likely will construct a number of SimpleBrowser s, each with information specific to itself.

Notice the application does not perform any checks to see if the context name and the service URL name are valid. It depends on the person using the application (you, me) to specify the appropriate context name and service URL.

Creating the Initial Context

We specified the service host when we launched SimpleBrowser. Now we need to use that information in creating an initial context.

The instance method createInitialContext( ) creates an initial context by setting the value of the java.naming.factory.initial to the NDS naming system. Then it uses the name of the URL as the NDS tree we want to browse.

public Context createInitialContext() {



          System.out.println("\r\nCreating an initial context.");

          Properties props = new Properties();

          props.put("java.naming.factory.initial",

               "com.novell.service.nds.NdsInitialContextFactory");

          props.put("java.service.url", serviceUrl);

Once the method has set the appropriate new Properties, it merges them with the existing set.

return (new InitialDirContext(props));

    }

The Properties key/value pair java.naming.factory.initial is how the application determines where in the global namespace it should begin browsing. Each JNDI service provider includes an initial context for the naming system it represents. You can refer to the documentation of the service provider for the appropriate initial context factories.

Browsing--A Core JNDI Operation

Probably the most common operation you will perform with JNDI is browsing a namespace. Browsing implies examining a Context's subordinates for some property we are interested in, such as a name.

The Browsing Operation Generalized. In general, to browse using JNDI, combine the list( ) and the lookup( ) methods in the following way:

1. list( ) the subcontexts of the current context (the initial context, in our case).

2. Walk the list, and use equals( ) to check if one of the list is the context we want.

3. lookup( ) the current context using the name of the context we want as our subcontext.

How Programs Should Browse. In SimpleBrowser, our browsing operation takes place in the method getContextWithName( ). Its signature requires we pass in the name of the context we want to examine.

public DirContext getContextWithName(String _name) throws NamingException {

The first operation this method performs is to return the initial context.

Context theContext = createInitialContext( );

We already verified that theContext is our top-most, top-level object in the NDS naming system in our operating system file viewer, so we don't need to perform any extensive checking here.

The next operation this method performs is to print that it is attempting to perform a list( ) method, then to perform the method and list the subordinates of the initial context.

System.out.println("\r\nListing subordinates of the initial context."); 

          try {

               NamingEnumeration subordinates = theContext.list("");

This operation is half of the browse operation--the list( ) returns an NamingEnumeration of the subordinates are of our current context much like "dir" or "ls" lists the files and directories subordinate to the current directory.

Performing a list( ) method requires a try{ } catch { } clause, since it can throw an exception. Although the try { } statement is at the beginning of the method, the method catches this exception at the very end of our browsing code.

list( ) and NamingEnumerations. JNDI always returns a NamingEnumeration from a successful list( ) operation on a Context. The NamingEnumeration is a subclass of java.lang.Enumeration. You can use NamingEnumeration methods to access individual elements.

NameClassPair pair;

The NamingEnumeration returned by a list( ) operation can only consist of NameClassPair s. A NameClassPair consists of the name of a subordinate object, as well as its class.

We declare this outside the while{ } loop because it's wasteful to create a new object for each pass through the loop just to return a single object.

while (subordinates.hasMoreElements( )) {

     pair = (NameClassPair)subordinates.next();

Note that we cast the next( ) element as a NameClassPair. This is because NamingEnumeration is an enumeration for all kinds of naming elements, not just those particular to list( ).

You will often use the while (enumeration.hasMoreElements( )) { elements.next( ) } loop to access individual elements. It's a nice convenience. When you have examined all the elements, the while( ) condition is no longer met, and the program drops out of the loop, with no extra effort required.

System.out.println("\r\n" + pair.getName());

Checking for a Context by Name. Since our application runs in the command line, we use the Java equivalent of stdout to print out the names of the Context s subordinate to the current context. An AWT-based application might add the names to a List object instead.

if (pair.getName().equalsIgnoreCase(_name)) {

This statement checks to see if one of the NameClassPair s has the name we specified as a parameter. It doesn't care about the Context's class, since it is performing a basic browse operation. But what if we wanted the method to search for a Context of a particular class as well as name? (Remember that Context is just an interface, and that it can represent any class in a provider.) The method could append && (pair.getClass( ).getName( ).equals(_class)) to theNamingEnumeration check, thereby checking for a Context of a particular class.

Why do we check using the Java equalsIgnoreCase( ) method, and not the "is equal to" ("=") operator? The reason: using the standard if (pair.getName( ) = _name) statement will never return TRUE, since the "is equal to" ("=") operator doesn't work with String values.

Looking up a Context. If the method has the Context we are looking for, then it tries to perform the second half of the browse operation, the lookup( ).

try {

     Object testContext = theContext.lookup(_name);

When we look up the Context, we assign it to an instance variable local to the scope of the try{ } statement. Then later, we check its class. We could do this in one operation, but for pedagogy's sake, we separate the operations so the syntax is clearer.

Note that lookup( ) returns a java.lang.Object, not a Context. This allows lookup( ) to return an object not part of JNDI. This is especially useful when looking up an object that is a leaf node that does not implement JNDI interfaces.

This is the second try { } catch { } statement in the application. It is required when performing a lookup( ) method because lookup( ) can generate an Exception.

Performing Checks on the Context. The next two lines of code inform us that the application found the Context, as well as the Context's class name.

System.out.println("\r\nContext found.");

System.out.println("Its class is " + 

      testContext.getClass().getName());

The next if{ } statement detects whether the object implements the DirContext interface. If the object does, then the method returns the object to the part of the program has requested it.

if (testContext instanceof DirContext) {

           return (((DirContext)testContext));

      }

}

Exception Handling. Note that the application catches two Exceptions.

  • NoInitialContextException: This can only occur if we have specified an inappropriate service host. If you compile and run the application, and it reports a NoInitialContextException, make sure you have spelled the name properly--it is case-sensitive.

  • A generic Exception: The method catches this just in case something really unexpected happens, such as the network crashed, or perhaps you stepped on the network cabling under the desk.

Here's an interesting question. Since the method already knows that the name we are looking up is valid--after all, it came from a list( ) operation on a Context we already have--wouldn't omitting the try { } statement have no ill effects? The answer is no. Because Java enforces exception handling, omitting it would cause our program to fail to compile. Therefore we catch the generic Exception at the very end of our method.

catch (NoInitialContextException e) {

                    System.out.println("Received " + e +

                         ". Usually this occurs when you have not specified " +

                         "the appropriate service URL.");

               }

               catch (Exception e) {

                    System.out.println("Received " + e + " while performing lookup.");

               }

          }

     }

}

The last catch{ } belongs to the first try{ }, which was necessary in order to perform the list( ) operation on the initial context. The catch{ } simply catches any exceptions generated.

catch (Exception e) {

     System.out.println("Received " + e + " while performing list.");

}

At this point, the method didn't generate any exceptions, but it didn't find the object, either. So the method should return null so that whatever part of the application called it knows that it didn't find anything.

return null;    

}

Examining Attribute IDs

Earlier, we discussed that DirContext s have Attribute s, and that Attribute s come in a set of Attributes. We use subclasses of java.util.Enumeration to access individual Attribute s in the set of Attributes.

We also discussed that an Attribute has two components, an attrId, which identifies the name of the attribute, and a set of value s, which are java.lang.Object s. The Attribute is a mere container for an attrId and its value s, so we do not need to do anything with it except get itsNamingEnumeration of its attrId s.

public void displayAttributes(DirContext _context) 

     throws NamingException {



     Attributes attributes = _context.getAttributes("", null);

Once the program assigns to the method variable attributes the Attributes of the DirContext, the next step is to access the StringEnumeration of the attributeId s. Since the StringEnumeration contains String s, printing an attrId results in readable text.

But since value s contain java.lang.Object s, and printing an Object does not necessarily result in readable text, we will not do anything with the set of value s.

Next the method performs the while (enumeration.hasMoreElements( )) { elements.next( ) } statement on attributeIds, which of course is an instance of NamingEnumeration, and thereby must be narrowed to the appropriate class.

NamingEnumeration attributeIds = attributes.getIDs();

Inside the while loop, the method prints the toString( ) representation of the attrId s.

while (attributeIds.hasMoreElements()) {

          System.out.println((String)attributeIds.next());

     }

}

Examining the Schema Class Definition

So far, SimpleBrowser knows how to browse one level into NDS, find a DirContext with a specific name, and print the DirContext's attributeIds. There is another layer of information it can learn about the DirContext. That is its schema class definition.

Working with schemae. When we examined the output of running SimpleBrowser, we learned that the DirContext's schema class definition describes metainformation about the context, such as its containment rules and its mandatory and extended attributes. This definition is useful when working with its schema, but in an introductory tutorial, it makes sense just to print out its definition so we can get some hands-on experience with it.

How do we get at the schema class definition? The instance method displaySchemaClassDef( ) will do this for us. We declare the method with a DirContext parameter and because only DirContext s can have schema; Context s cannot.

public void displaySchemaClassDef(DirContext _context) 

     throws NamingException {

Once the instance method was declared, in its body the method prints a status message, then assigns to a DirContext the schema class definition of the DirContext we passed in.

System.out.println("\r\nIts schema class definition is:");

DirContext schemaClassDef = _context.getSchemaClassDefinition("");

Schemae and DirContexts. Here's something interesting. The schema class definition is a DirContext? Yes, it is, and for good reason. It has to do with the general applicability of JNDI to hierarchical naming systems.

At the beginning of this tutorial, we learned that JNDI can represent any type of hierarchical naming system. A DirContext's schema is just a hierarchical naming system that happens to contain metainformation for a particular DirContext, so a DirContext is a logical and elegant choice for containing the schema class definition.

JNDI stores the schema class definition metainformation--containment rules, attributes--as attributes of a DirContext. So to access this metainformation, the current method performs SimpleBrowser's displayAttributes( ) method on our DirContext that contains the schema class definition.

displayAttributes(schemaClassDef);

}

Putting It All Together

All the activity in SimpleBrowser is initiated in the main( ) method, just as it is in any other Java application. The main( ) method contains the code that must be executed in a particular order. In this case, main( ) executes the following:

1. Creates a new instance of itself. The main( ) method creates a new instance of itself, rather than calling its own methods directly by specifying this object. It's easier to create an instance of itself than to worry about which instance variables need to be static and which don't.

Proper object-oriented application design suggests that we use a Controller class to create and managed instances of SimpleBrowser. However, using a Controller would require an additional class, which would only make this tutorial bulkier.

2. Instructs the new instance of itself to find the DirContext with the name we specified when we launched the application. If the method generates an exception, then it prints to standard out the exception name as part of a user message.

3. Checks to see if the DirContext returned is null. If it is, it prints a message telling us that it didn't find the DirContext we are looking for, then stops executing. If the DirContext is not null, execution continues.

4. Prints a message telling us what information it is reporting.

5. Prints the attribute IDs of the DirContext.

6. Prints the schema class definition of the DirContext.

Declaring main( ). Every application's main( ) method must be public and static. Also, it must allow us to pass in arguments to the application. The method accepts these arguments using an array of String s.

public static void main(String[] argv) {

In this step, the method creates a new instance of itself.

SimpleBrowser myself = new SimpleBrowser(argv);

This try{ } statement encloses all the methods we perform on a DirContext. It ensures that execution proceeds normally if the getContextWithName( ) method generates an exception. Without it, the program will fail to compile.

try {

     DirContext currentContext = myself.getContextWithName(contextName);

The main( ) method must use an if { } statement to check the state of the DirContext to ensure that it does not perform any methods on a null DirContext.

Remember that the getContextWithName( ) method returns null if the method didn't generate an exception, but if it didn't find the specified DirContext, either. So if the method returns a null DirContext, it's pointless to continue execution. We check for a null DirContext here.

if (currentContext != null) {

Now that the method has a valid DirContext, it can print its attribute IDs and schema class definition.

myself.displayAttributes(currentContext);

     myself.displaySchemaClassDef(currentContext);

}

When the main( ) method used an if{ } statement to check the state of the DirContext to determine whether it was null, it used the else { } statement to print a message telling us that it couldn't find the appropriate DirContext.

else {

          System.out.println("\r\nDidn't find a DirContext called " +

                "contextName );

     }

}

When the main( ) method used a try { } statement to ensure that execution would proceed normally should the getContextWithName( ) method generate an exception, it also used a corresponding catch{ } statement to return the exception name.

catch (Exception e) {

         System.out.println("Received " + e +

              " while searching for DirContext " 

              + contextName + ".");

    }

}

The catch { } statement is the end of SimpleBrowser's main( ) method. Once it has printed the class definition, the attribute IDs, and the schema class definition, the application's job is done, and execution can stop.

Execution Post-Mortem. So what has happened here? We have successfully browsed NDS without a single proprietary method. We learned meaningful information about an NDS context in only a few lines of Java code. Before JNDI, we would have had to program to NDS's specialized C API in order to learn this information.

Where're My Sunglasses?

Freeing applications from Novell's proprietary API greatly expands your ability to use NetWare's robust resources in network-based applications. It also expands NetWare's reach in the marketplace. NetWare has had a reputation as difficult to program. Now NDS programming is as easy as programming the simplest naming system.

Currently, SimpleBrowser and any other Java application relies on the JNDI providers shipped in the NetWare SDK for Java. These providers rely on the Java Native Interface (JNI) communicating with C functions in Client 32. SimpleBrowser runs on any platform that has a Java virtual machine and Client 32, but it can't run on a platform without Client 32. This is a limitation.

But limitations are meant to be exceeded. Engineers in the Java Technology Group are working feverishly on enabling JNDI to function using CORBA as well. This means that Java-based and CORBA-aware applications will be able to access NetWare from any node on a network, freeing NetWare from its proprietary client. Removing the client will be optional--Client 32 is robust and works well--but no longer will developers be limited to platforms with native clients.

In all, JNDI holds a great promise--to bring the power and elegance of NetWare and NDS to the forefront of the industry, to enable the next generation of network services, and to leverage the power of NetWare and NDS to increase customer productivity and satisfaction.

Here's to a bright future for JNDI and NetWare!

Kristopher Magnusson is the author of Java Naming and Directory Programming , to be published by O'Reilly & Associates in 1998.

* Originally published in Novell AppNotes


Disclaimer

The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.

© Copyright Micro Focus or one of its affiliates