Novell is now a part of Micro Focus

How to Write a ConsoleOne NDS Management Snap-In Using the eCommerce Bean for LDAP

Articles and Tips: article

J. Jeffrey Hanson
Senior Architect
Zareus, Inc.
jhanson583@aol.com

01 Jan 2002


This AppNote demonstrates how to write a snap-in for ConsoleOne using Novell's eCommerce LDAP Beans to expose directory services functionality in the ConsoleOne framework.


Topics

ConsoleOne, eCommerce Beans, LDAP

Products

ConsoleOne

Audience

developers

Level

intermediate

Prerequisite Skills

familiarity with eCommerce Beans and ConsoleOne

Operating System

NetWare

Tools

eCommerce Beans for LDAP

Sample Code

yes

Introduction

Novell's eCommerce LDAP Beans are easy-to-use Java components enabling developers to integrate Web applications with directory services. These components are based on open protocols, have no dependencies on native code and use the Model-View-Controller (MVC) and Command design patterns. These beans enable authentication and read/write directory services access along with contextless login and SSL security.

Novell's ConsoleOne is a cross-platform, Java-based shell for managing network resources. ConsoleOne is built on a snap-in framework that is exposed to third-party developers allowing them to build custom snap-ins to manage network resources. Snap-ins provide content access and management functionality to the shell.

eCommerce LDAP Beans Programming Model

Novell's eCommerce LDAP beans are built to conform to the Command design pattern. This simply means that each bean encapsulates the functionality for one type of command, such as: browsing, renaming, moving, copying, etc. Each bean exposes input properties that must be set prior to calling the execute method on the bean. After the execute method is called, the results of the method call can be retrieved using the output properties of the bean. For example, the com.novell.ecb.ldap.AuthenticateLdap class exposes a bean that provides authentication capabilities to a servlet or an application. Once an instance of this is created, the programmer must set the following input properties: URL, DN (Distinguished Name), and Password. After setting the input properties, the execute method can be called. At this point, the output property, LdapConnection, can be retrieved.

ConsoleOne Programming Model

Snap-Ins

Snap-ins are hosted by the ConsoleOne shell. Snap-in interfaces are provided enabling developers to extend ConsoleOne's user-interface, expose namespaces, and to customize user preferences. ConsoleOne defines several types of snap-ins; Namespace, View, Menu Items, Context Menu, Property Page, Status Bar Item, Toolbar Item, Service, and Map ObjectEntry. Novell provides-as part of the ConsoleOne framework-the Console Snap-In. The Console Snap-In provides basic functionality for the ConsoleOne user interface and allows developers to build on this basic functionality making it very simple to write custom snap-ins that expose the features for a particular resource.

Scope

The snap-in scope defines the events and entities that determine when the snap-in is activated. For example, a snap-in with a scope of GlobalContainerTypesScope is activated when a container object in any namespace is selected.

All scope classes must implement the Scope interface provided by ConsoleOne. Snap-ins are registered using their classes that implement this interface.

There are two scope categories, Global and Namespace. A snap-in defined with Global scope applies to objects within any namespace in ConsoleOne. A snap-in with Namespace scope applies only to objects in the namespace specified for the snap-in.

Namespaces

As a framework for the management and administration of networks, ConsoleOne provides the capability to display network objects or resources in an organized fashion. These network objects and resources are typically represented within logical namespaces (NDS, LDAP, NetWare File Systems, NT Domains, and so forth) or physical namespaces (internetwork or segment maps, inventory databases, etc.). These namespaces provide context, that is, the logical relationship-such as location in an organizational structure, and/or the physical relationship, such as physical wire segmentation, routing, and so forth-of these network objects and resources.

A namespace, therefore, defines a set of objects, how they are named, and the relationship, structure, hierarchy, or containment of those objects. In addition, an object within one namespace can provide a link to another namespace. In NDS, for example, a NetWare volume, as an NDS object class, represents a volume within the NetWare file system. NDS, as a namespace, uses the NDS Volume object as a bridge into the NetWare file system, which is another namespace.

In order to manage and administer network objects or resources, they must first be described by a namespace snap-in. It is through the namespace that the features and functions of the represented objects and resources are exposed and presented to the end user.

In order for a set of network objects or resources to be managed or administered through ConsoleOne, they must be made accessible through ConsoleOne. The namespace is the method by which ConsoleOne is able to enumerate and display the objects or resources of a network or computing environment. Novell provides NetWare and NDS namespaces. Through the NDS namespace, the end user will be able to view and manage NDS objects, and through the NetWare namespace, the end user will be able to view and manage NetWare objects.

Not all developers must write a namespace snap-in. For example, developers who extend the NDS schema with new objects need not write a new namespace snap-in. The NDS namespace will enumerate the new object types based on the NDS schema. However, they might write other snap-in methods to provide property-editing capability for those new object types.

Services that are built around NDS, or that extend the capability of NDS into other environments, might create a new namespace snap-in. Another example might be a file system that could be managed through ConsoleOne. In order for ConsoleOne to enumerate and display the objects represented by that file system, a namespace snap-in must be written.

Views

Views provide a related presentation of a namespace object. A view can simply be a listing of the subordinates of the selected object, or it can be more detailed, such as a table or graph. What the view presents and how it is presented is largely determined by the namespace and what it represents.

One or more views can be associated with any namespace or a particular object type within a namespace. As an example, NDS is typically represented in both a hierarchical and a list fashion. A view snap-in could provide an alternative logical view for a specific class of objects. Other examples include: a topographic view of the network, a detailed view of an object, or an in-place file viewer.

When a namespace object is selected, ConsoleOne examines the set of participating snap-ins registered with the namespace or object type, and determines whether or not multiple views are available. If multiple views are available, the view menu then lists the views from which the user can select. When a view becomes active (either by menu, activation by a namespace, or object selection), then additional menu, toolbar, and status bar items also become active because they are snap-ins registered to the view.

The ConsoleOne console view provides limited display capability. As such, it might not provide the presentation that is most appropriate for a given namespace. Novell provides views that are specifically designed for a basic set of day-to-day tasks, such as managing NDS objects and managing the replicas and partitions of NDS.

A view should provide a presentation that makes the namespace and objects it represents easily accessible, and thus supports the tasks and functions related to those objects or namespace.

A view makes it possible to structure the tasks based on the presentation. In some cases, a task is better suited for a particular view. For example, managing a large number of objects or object types is difficult in a standard list-type view. A view that better utilizes the available screen space might be a wrapped list, which would be better suited to managing large numbers of objects. A map-type view is typically more suited to managing the topology of the network because it generally establishes a better relationship between the nodes on a LAN.

ConsoleOne Snap-In Registration

At startup, ConsoleOne looks for snap-ins inside of a predetermined directory. Each snap-in must implement the Registration interface in order for ConsoleOne to properly register it. The Registration interface consists of one method named, "getRegistration."

The getRegistration method returns an array of RegistrationItem objects. Each RegistrationItem object contains information identifying the snap-in type, where to find it, and when ConsoleOne should load the snap-in.

Registration of a snap-in can be done either by using a separate class or by using a manifest file.

The following code snippet demonstrates a registration class that will register four different snap-ins for our LDAP snap-in.

public class MySnapinRegistrar implements Registration 
{ 
 public RegistrationItem[] getRegistration() 
 { 
       RegistrationItem[] ri = new RegistrationItem[4]; 
        ri[0] =
          new RegistrationItem(new NamespaceScope(Shell.SNAPIN_PAGE,
                                       "LDAPSample"),
                                       "com.sample.snapins.LDAPPage"); 
     ri[1] =
          new RegistrationItem(new NamespaceScope(Shell.SNAPIN_STATUSBARITEM,
                                      "LDAPSample"),
                                       "com.sample.snapins.LDAPStatusBar"); 
        ri[2] =
          new RegistrationItem(new NamespaceScope(Shell.SNAPIN_MENU,
                                       "LDAPSample"),
                                       "com.sample.snapins.LDAPMenu"); 
     ri[3] =
          new RegistrationItem(new NamespaceScope(Shell.SNAPIN_TOOLBARITEM,
                                        "LDAPSample"),
                                       "com.sample.snapins.LDAPToolBar"); 
      return ri; 
  } 
}

Writing the Snap-In

Main Snap-In Methods

Each snap-in class must implement the Snapin interface which defines four methods: getSnapinName, getSnapinDescription, initSnapin, and shutdownSnapin.

The getSnapinName method for our snap-in needs to return a descriptive name, as follows:

public String getSnapinName() 
{ 
    return "LDAP Snapin"; 
}

The getSnapinDescription method for our snap-in needs to return a concise description, as follows:

public String getSnapinDescription() 
{ 
 return "Snap-in providing LDAP functionality"; 
}

The initSnapin method is a callback method that is called by the shell to allow the snap-in to be initialized and pass references to the InitSnapinInfo class, as follows:

public boolean initSnapin(InitSnapinInfo info) 
{ 
    shell = info.getShell(); 
    type = info.getSnapinType(); 
    context = info.getSnapinContext(); 
    
    /*Initialize snap-in; return true if successful, 
    otherwise return false.*/ 
    
    try {
        // Instantiate the command bean
        authBean = (com.novell.ecb.ldap.AuthenticateLdap)
            java.beans.Beans.instantiate(this.getClass().getClassLoader(),
                                        "com.novell.ecb.ldap.AuthenticateLdap");

        // Set the input properties of the command bean
        // get the values from a property file
        authBean.setURL(resourceBundle.getString("URL"));
        authBean.setDN(resourceBundle.getString("DN"));
        authBean.setPassword(resourceBundle.getString("password"));
        authBean.setProtocol("ssl");
    
        // Call the execute method of the command bean
        authBean.execute();
    } catch(ClassNotFoundException e) {
        return false;
    } catch(com.novell.ecb.CommandException e) {
        return false;
    }
    
        return true; 
}

The shutdownSnapin method is where we perform any necessary cleanup, as follows:

public void shutdownSnapin() 
{ 
 /* Resource cleanup here. */ 
}

Adding a New Namespace

To add our own namespace to the ConsoleOne shell, we must create a new class that implements the NamespaceSnapin interface which employs the methods, getUniqueID, getFullName, getObjectEntry, getInitialObjectEntries, and getChildren.

To get the unique identifier or name of the namespace, the getUniqueID method is used. It is suggested that you return the package name and class implemented in the namespace snap-in as the unique ID string. Our implementation of getUniqueID is as follows:

public String getUniqueID() 
{ 
  return "com.sample.snapins.LDAPNamespace"; 
}

To return the full name of the ObjectEntry for which the full name is desired, the getFullName method is used. The full name should represent a unique name in the namespace, as follows:

public String getFullName(ObjectEntry entry) 
{ 
 return entry.getName(); 
}

To return the ObjectEntry associated with a unique string, the getObjectEntry method is used. An exception is thrown, if no ObjectEntry is found, as follows:

public ObjectEntry getObjectEntry(String name)
 throws ObjectNotFoundException 
{ 
 return makeObjectEntry(name, null); 
}

To return the root ObjectEntry array, the getInitialObjectEntries method is used. If there are no root object entries in the namespace, null can be returned, as follows:

public ObjectEntry[] getInitialObjectEntries() 
{ 
    try {
        // Instantiate the command bean
        com.novell.ecb.ldap.ListLdapEntries   bean =
            (com.novell.ecb.ldap.ListLdapEntries)
            java.beans.Beans.instantiate(this.getClass().getClassLoader(),
                                        "com.novell.ecb.ldap.ListLdapEntries");
    
        // Set the input properties of the command bean
    
        bean.setConnection((com.novell.ecb.ldap.LdapConnection)
        authBean.getLdapConnection());
        bean.setName(resourceBundle.getString("Root"));
    
        // Call the execute method of the command bean
        bean.execute();
    
        // Query the output properties of the command bean
        String[] names = bean.getNames();
    
        ObjectEntry[] rootNodes = null;
        if (names.length > 0) {   
            rootNodes = new ObjectEntry[names.length];
            for (int i = 0; i < names.length; i++)
            {
                rootNodes[i] = makeObjectEntry(names[i], null);
            }
        }
    
        return rootNodes;
    } catch(ClassNotFoundException e) {
        // handle error
    } catch(com.novell.ecb.CommandException e) {
        // handle error
    }
    
    return null;
}

We use the getChildren method to obtain an enumeration of subordinate objects that the shell will use for the described namespace. The parent object in the current namespace from which a list of children can be obtained is the parameter passed, as follows:

public ObjectEntryEnumeration getChildren(ObjectEntry parent)
    throws NotAContainerException 
{
        // Instantiate the command bean
        com.novell.ecb.ldap.ReadLdapEntry   bean = null;
    
        int   entryFlags = 0;
    
        try {
            // Instantiate the command bean
            bean = (com.novell.ecb.ldap.ReadLdapEntry)
            java.beans.Beans.instantiate(this.getClass().getClassLoader(),
            "com.novell.ecb.ldap.ReadLdapEntry");
    
            // Set the input properties of the command bean
            bean.setConnection((com.novell.ecb.ldap.LdapConnection)
            authBean.getLdapConnection());
            bean.setName("cn=wcoyote, o=acme");
    
            // Call the execute method of the command bean
            bean.execute();
    
            // Query the output properties of the command bean
            entryFlags = bean.getIntValue("entryFlags");
        } catch(ClassNotFoundException e) {
            // handle error
            return null;
        } catch(com.novell.ecb.CommandException e) {
            // handle error
            return null;
        }
    
        if ((entryFlags & 0x0004) != 0x0004)
        { 
            throw new NotAContainerException(); 
        }
    
        com.novell.ecb.ldap.ListLdapEntries   listBean = null;
    
        try {
            // Instantiate the command bean
            listBean = (com.novell.ecb.ldap.ListLdapEntries)
                java.beans.Beans.instantiate(this.getClass().getClassLoader(),
                    "com.novell.ecb.ldap.ListLdapEntries");
    
            // Set the input properties of the command bean
            listBean.setConnection((com.novell.ecb.ldap.LdapConnection)
            authBean.getLdapConnection());
            listBean.setName("cn=wcoyote, o=acme");
    
            // Call the execute method of the command bean
            listBean.execute();
    
            // Query the output properties of the command bean
            String[] names = bean.getNames();
    
            return new LDAPEnumeration(names, parent, this); 
        } catch(ClassNotFoundException e) {
            // handle error
    
        } catch(com.novell.ecb.CommandException e) {
            // handle error
        }
    
        return null; 
}

You can implement the ObjectEntryEnumeration interface or use the DefaultObjectEntryEnumeration class in association with the getChildren method. This class is a convenience helper class for creating ObjectEntryEnumerations. A Vector or Enumeration is converted to an ObjectEntryEnumeration that can be returned from the getChildren method of the NamespaceSnapin or ExtendChildrenSnapin interface by the DefaultObjectEntryEnumeration class.

The following code shows an implementation of ObjectEntryEnumeration.

public class LDAPEnumeration()
   implements ObjectEntryEnumeration 
{ 
      private ObjectEntry [] m_entries; 
       private int m_index;
 
     public LDAPEnumeration(String[] entries, 
                                    ObjectEntry parent, 
                                 LDAPNamespace namespace) 
        {
            m_entries = new ObjectEntry[entries.length];
         m_index = 0; 
    
         for(int i = 0; i < entries.length; i++)
           { 
               m_entries[i] = namespace.makeObjectEntry(entries[i],
                                                                 parent); 
        }
        // sort the entries using custom sorter routine 
     sorter(m_entries, entries.length - 1); 
  }
    
 public objectEntry next() 
   { 
       if(hasMoreElements()) 
       { 
           return m_entries[m_index++]; 
        } 
       else 
        { 
           throw new NoSuchElementException();
      } 
   }
    
 public boolean hasMoreElements() 
    { 
       return m_index < m_entries.length; 
   }
    
 public Object nextElement() 
 { 
       return next(); 
  } 
}

Displaying Namespace Information

By implementing both the DisplayIconSnapin and DisplayNameSnapin interfaces, one must create a new class.

DisplayIconSnapin and DisplayNameSnapin interfaces provided by the new snap-in class implements the text and image that will show up in the object browser tree and the view for namespace items.

The DisplayIconSnapin interface defines the following three methods used to provide the icon for a specified ObjectEntry: getDisplayIcon, getDisplayIcon, and doesIconChangePerEntry.

Defined are two different forms of the getDisplayIcon method; the first, returning the snap-in icon associated with the specified ObjectEntry parameter. When there is no icon available, a null can be returned; in which case-for the specified entry type-a default icon is returned only if no other registered snap-in, providing an icon for that type of entry, has been defined. Otherwise, a search of all available snap-ins is conducted, and the first snap-in that provides an icon for the specified entry is used to return the icon.

public Icon getDisplayIcon(ObjectEntry entry) 
{ 
  return getDisplayIcon(entry.getObjectType().getNamespace().getUniqueID(),
                                entry.getObjectType().getName()); 
}

Returning the icon associated with the ObjectType that has the same name as the String parameter type is the second construct of the getDisplayIcon method. When there is no icon available, a null can be returned, in which case, a default icon is returned for the specified entry type only if no other registered snap-in has been defined that provides an icon for that type of entry. Otherwise, a search of all available snap-ins is conducted, and the first snap-in that provides an icon for the specified entry is used to return the icon.

public Icon getDisplayIcon(String namespace, String type) 
{ 
    Icon icon = (Icon) icons.get(type); 
    if(icon == null) 
    { 
        Image i = null; 
        String path = "/com/sample/snapins/ldap/images/"; 
        path += type + ".gif"; 
        URL url = getClass().getResource(path); 
        if (url != null) 
        { 
            i = Toolkit.getDefaultToolkit().getImage(url); 
            icon = new ImageIcon(i); 
            icons.put(type, icon); 
        } 
        else 
        { 
            // handle error
        } 
    } 
    return icon; 
}

To determine if the image can change depending on the ObjectEntry of the given type, or if the snap-in will always return the same image for a particular ObjectType, doesIconChangePerEntry is used. This method should return true if the image can change depending on the ObjectEntry of the given type. This method should return false if the snap-in always returns the same image for a particular ObjectType. When possible ConsoleOne can cache the image and use the same image for all objects of the given type.

public boolean doesIconChangePerEntry(ObjectType type) 
{
    return true; 
}

Used to provide the display name for a specified ObjectEntry, the DisplayNameSnapin interface defines two forms of the getDisplayName method.

One must return the display name that the shell will use to display the specified ObjectEntry in the tree or view when using getDisplayName with the ObjectEntry parameter. Displayed next to an icon representing the object is the name of the object. We demonstrate getDisplayName using only the entry parameter here:

public String getDisplayName(ObjectEntry entry) 
{ 
  String name = entry.getName(); 
  if (name.equals("")) 
    { 
       return "Invalid Entry"; 
 }
    
 return name; 
}

Adding a New View

The rightmost ConsoleOne shell panel is referred to as the View. To obtain a list of views that participate with an ObjectEntry when an ObjectEntry is selected in the left panel tree, the shell examines the set of participating snap-ins. In the form of Radio Menu Items, the view menu then displays a list.

For every namespace, ConsoleOne provides a default view. A name and an image for each object entry in a namespace is displayed in a list by the default view.

By creating a new class in which the ViewSnapin interface is implemented, you can add views for objects. Required methods to implement are as follows: getView, getUniqueID, getViewMenuName, and setInactive.

Returned by the getView method is context information about the state of the browser window. Returned by the getObjectEntry method of the ViewSnapinContext class is the currently selected ObjectEntry.

Stored in the InitSnapinInfo parameter supplied to the initSnapin method during ViewSnapin initialization is the current context. For that point of time, a call to getContext will return a valid ViewSnapinContext; but the context can change between initialization and the call to the getView method. Therefore, the context should be obtained from the ViewSnapinContext parameter supplied in the getView method and not from the context provided during initialization.

public Component getView(ViewSnapinContext viewContext) 
{ 
  // Remove any existing Components from the panel. 
   p.removeAll(); 
  
 ObjectEntryEnumeration   children =
          shell.getChildren(viewContext.getObjectEntry()); 
    while(children.hasMoreElements()) 
   { 
       ObjectEntry child = children.next(); 
        MyLabel node = new MyLabel(child); 
      node.addMouseListener(new MyMouseListener()); 
       p.add(node); 
    } 
   return p; 
}

The unique, non-localized name of the view is returned by the getUniqueID method for snap-ins to identify the view. The getUniqueID method is called before the initSnapin method in a view snap-in. Also, the getViewMenuName method is called before the initSnapin method if a view is changed by ConsoleOne.

public String getUniqueID() 
{ 
  return "com.sample.snapins.ldap.LDAPListView"; 
}

Returned by the getViewMenuName method is the language-dependent, localized name that will be used as the text in the view menu.

public String getViewMenuName() 
{ 
  return "Sample LDAP List View"; 
};

To notify the shell that the snap-in is inactive, we must implement the setInactive method.

Summary

Novell's eCommerce Beans for LDAP provide 100% Java components to integrate Web applications with LDAP-based directory trees. These components enable authentication and read/write directory access along with features such as contextless login and SSL security. Novell's ConsoleOne provides a comprehensive GUI framework for hosting powerful, management snap-in services. Using Novell's eCommerce Beans for LDAP as the foundation for directory services access and Novell's ConsoleOne as the foundation for management services, a NetWare application developer can easily write GUI components to manage the features and functionality of directory services.

References

* 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