Sample Code File: SkeletonSubscriptionShim.
java


// Sample code file: SkeletonSubscriptionShim.java
// Warning: This code has been marked up for HTML

/*************************************************************************

Copyright 1999-2002 Novell, Inc. All Rights Reserved.

With respect to this file, Novell hereby grants to Developer a
royalty-free, non-exclusive license to include this sample code
and derivative binaries in its product. Novell grants to Developer
worldwide distribution rights to market, distribute or sell this
sample code file and derivative binaries as a component of
Developer's product(s). Novell shall have no obligations to
Developer or Developer's customers with respect to this code.

DISCLAIMER:

Novell disclaims and excludes any and all express, implied, and
statutory warranties, including, without limitation, warranties
of good title, warranties against infringement, and the implied
warranties of merchantability and fitness for a particular purpose.
Novell does not warrant that the software will satisfy customer's
requirements or that the licensed works are without defect or error
or that the operation of the software will be uninterrupted.
Novell makes no warranties respecting any technical services or
support tools provided under the agreement, and disclaims all other
warranties, including the implied warranties of merchantability and
fitness for a particular purpose.

***************************************************************************/
package com.novell.nds.dirxml.driver.skeleton;

import com.novell.nds.dirxml.driver.*;
import org.w3c.dom.*;

/**
 * A basic skeleton for implementing a <code>SubscriptionShim</code>.
 *
 * The <code>SubscriptionShim</code> defines an interface for an application
 * driver to receive commands from DirXML. These commands must be executed
 * on the application on behalf of DirXML.
 *
 * @version 2.0 28Jun2000
 */
public class SkeletonSubscriptionShim extends CommonImpl implements
SubscriptionShim
{
/**
* Contains subscriber options from the init params passed to init()
*/
ShimParams params = null;

/**
 * Holds current connection state.
 */
private boolean connected = false;
/**
 * Return document that execute() sets up for use by command
 * handlers. Reference is to &lt;output> element since all return
 * information is placed under the &lt;output> element.
 */
private Element outputElement = null;
/**
 * This value is used to create an association in NDS between an NDS object
 * and a hypothetical object in the skeleton driver's supported application.
 *
 * @see #addHandler
 */
private int currentAssociation = 1;
/**
 * Authentication info initialized by driver shim at init()
 */
private AuthenticationParams authParams = null;

/**
 * constructor, not strictly required, but a good idea
 *
 */
SkeletonSubscriptionShim(AuthenticationParams authParams)
{
//set up the Trace object with the name of this object
super("SkeletonSubscriptionShim");
this.authParams = authParams;
}

/**
 * <code>init</code> will be called before the first invocation of execute.
 *
 * @param initParameters XML document that contains the subscriber
 *initialization parameters.
 * @return XML document containing status/messages from init operation.
 */
public XmlDocument init(XmlDocument initParameters )
{
try
{
tracer.trace("init");
//get any non-authentication options from the init document
params = getShimParams(initParameters.getDocument(),
"subscriber",SUBSCRIBER_PARAMS);
//get any state that may have been passed in (if this is our very
//first invocation, there won't be any state)
//The skeleton driver fakes associations to DirXML so that it appears
//more like a real driver
//see addHandler()
int assocState = params.getIntParam("current-association");
if (assocState != -1)
{
//setup our fake association for handling adds
currentAssociation = assocState;
}
//perform any other initialization that might be required.
//Note that in the skeleton driver, SkeletonDriverShim.init() sets up
//a shared copy of any authentication parameters for use by all three
//shim objects
return createSuccessDocument();
} catch (Throwable t)
{
//something bad happened...
return createStatusDocument(STATUS_FATAL,t.getMessage());
}
}

/**
 * <code>execute</code> will execute a command encoded in an XML document.
 *
  * @param doc The document that contains the commands
  * @param query A query processor that can be used to query the the
 * caller for more information
 *
 * @return This function will return a document that contains information
 * of changes that should occur in NDS as a result of the application
 * processing that changes that were passed in on this call. For example,
 * the object was created in the application space and here is its
 * unique identifier to add to the NDS object's association... It may also
 * contain status and logging information.
 */
public XmlDocument execute( XmlDocument doc, XmlQueryProcessor query)
{
int retryCount = 2;
tracer.trace("execute");
try
{
//setup the return document for use by command handlers
outputElement = createOutputDocument();
//try and connect with our mythical app
while (retryCount-- > 0)
{
try
{
connect();
Document document = doc.getDocument();
//find the <input> element
Element input = (Element)document.getElementsByTagName
("input").item(0);
//iterate through the children, dispatching commands
Node childNode = input.getFirstChild();
while (childNode != null)
{
//only elements are interesting...ignore any interspersed
//text, comments, etc.
if (childNode.getNodeType() == Node.ELEMENT_NODE)
{
dispatch((Element)childNode);
}
childNode = childNode.getNextSibling();
}
//return the result of whatever we were told to do
return new XmlDocument(outputElement.getOwnerDocument());
} catch (java.io.IOException e)
{
if (retryCount <= 0)
{
//done trying
throw e;
}
}
}
//if we fall through here, we failed to connect
throw new java.io.IOException("failed to connect");
} catch (java.io.IOException e)
{
//somehow failed in talking to app, tell DirXML to retry later
return createStatusDocument(STATUS_RETRY,e.toString());
} catch (Throwable t)
{
//something bad happened...
return createStatusDocument(STATUS_ERROR,t.getMessage());
}
}

/**
 * Illustrative method. Connect to supported application if not already
 * connected or if
 * connection has been broken.
 *
 * @exception java.io.IOException If can't connect or connection broken.
 */
private void connect()
throws java.io.IOException
{
if (!connected)
{
//do whatever is required to connect to the supported application -
//e.g., open a Socket, etc.
//if (connection fails)
//{
// throw new java.io.IOException("Can't connect to application");
//}
connected = true;
}
}

/**
 * Dispatch a command to the appropriate handler.
 *
 * @param command The command element from the input document
 */
private void dispatch(Element command)
throws Exception
{
String commandName = command.getNodeName();
if (commandName.equals("add"))
{
addHandler(command);
} else if (commandName.equals("modify"))
{
modifyHandler(command);
} else if (commandName.equals("delete"))
{
deleteHandler(command);
} else if (commandName.equals("rename"))
{
renameHandler(command);
} else if (commandName.equals("move"))
{
moveHandler(command);
} else if (commandName.equals("query"))
{
queryHandler(command);
} else
{
//this won't happen unless a stylesheet rule adds some custom command
//note that the status element is keyed with the event-id attribute
//fromthe command
addStatusElement(outputElement,
STATUS_ERROR,
"Unsupported command: '" + commandName + "'",
command.getAttribute("event-id"));
}
}

/**
  * Handle an &lt;add> command.
  *
  * @param the &lt;add> element.
  */
private void addHandler(Element add)
throws Exception
{
String className = add.getAttribute("class-name");
String eventId = add.getAttribute("event-id");
String srcDn = add.getAttribute("src-dn");
//don't build the string unless it's actually going out
if (tracer.getTraceLevel() >= Trace.DEFAULT_TRACE)
{
tracer.trace("addHandler: class == '" + className + "'");
}
//do whatever is required here for the supported application
//
//In the case of this skeleton driver, we will setup a fake
//association value for the <add> so that we will get modifies,
//deletes, etc. on the associated object.
//
//For a real driver, we would add an association using
//whatever unique key the application
//can supply for the application object (a dn, a GUID, etc.)

//get the output document for creating stuff
Document document = outputElement.getOwnerDocument();
//create the add-association element
Element addAssociation = document.createElement("add-association");
//put it under the output element
outputElement.appendChild(addAssociation);
//add the attributes that tell DirXML to what NDS object this applies
addAssociation.setAttribute("dest-dn",srcDn);
addAssociation.setAttribute("event-id",eventId);
//create the association value and put it under the add-association
//element
Text value = document.createTextNode(Integer.toString
(currentAssociation));
addAssociation.appendChild(value);
//increment our fake association value for the next add
++currentAssociation;
//and we're done
}

/**
 * Handle a &lt;modify> command.
 *
 * @param the &lt;modify> element.
 */
private void modifyHandler(Element modify)
throws Exception
{
String className = modify.getAttribute("class-name");
String eventId = modify.getAttribute("event-id");
String assocValue = getAssociation(modify);
//don't build the string unless it's actually going out
if (tracer.getTraceLevel() >= Trace.DEFAULT_TRACE)
{
tracer.trace("modifyHandler: class == '" + className + "'");
}
//do whatever is required here for the supported application
//use the assocValue to look up the object in the application, then
//apply the attribute modifications

//for the skeleton driver, just pretend everything worked
addStatusElement(outputElement,STATUS_SUCCESS,null,eventId);
}

/**
 * Handle a &lt;delete> command.
 *
 * @param the &lt;delete> element.
 */
private void deleteHandler(Element delete)
throws Exception
{
String className = delete.getAttribute("class-name");
String eventId = delete.getAttribute("event-id");
String assocValue = getAssociation(delete);
//don't build the string unless it's actually going out
if (tracer.getTraceLevel() >= Trace.DEFAULT_TRACE)
{
tracer.trace("deleteHandler: class == '" + className + "'");
}
//do whatever is required here for the supported application
//use the assocValue to look up the object in the application, then
//perform the delete for the skeleton driver, just pretend everything
//worked
addStatusElement(outputElement,STATUS_SUCCESS,null,eventId);
}

/**
 * Handle a &lt;rename> command.
 *
 * @param the &lt;rename> element.
 */
private void renameHandler(Element rename)
throws Exception
{
String className = rename.getAttribute("class-name");
String eventId = rename.getAttribute("event-id");
String assocValue = getAssociation(rename);
//don't build the string unless it's actually going out
if (tracer.getTraceLevel() >= Trace.DEFAULT_TRACE)
{
tracer.trace("renameHandler: class == '" + className + "'");
}
//do whatever is required here for the supported application
//use the assocValue to look up the object in the application, then
//perform the rename
//for the skeleton driver, just pretend everything worked
addStatusElement(outputElement,STATUS_SUCCESS,null,eventId);
}

/**
 * Handle a &lt;move> command.
 *
 * @param the &lt;move> element.
 */
private void moveHandler(Element move)
throws Exception
{
String className = move.getAttribute("class-name");
String eventId = move.getAttribute("event-id");
String assocValue = getAssociation(move);
//don't build the string unless it's actually going out
if (tracer.getTraceLevel() >= Trace.DEFAULT_TRACE)
{
tracer.trace("moveHandler: class == '" + className + "'");
}
//do whatever is required here for the supported application
//use the assocValue to look up the object in the application, then
//perform the move for the skeleton driver, just pretend everything worked
addStatusElement(outputElement,STATUS_SUCCESS,null,eventId);
}

/**
 * Handle a &lt;query> command.
 *
 * @param the &lt;query> element.
 */
private void queryHandler(Element query)
{
String eventId = query.getAttribute("event-id");
tracer.trace("queryHandler");
//The query can specify a read of a single object with one or more
//attributes, or it can specify a search for an object of a particular
//class, or various other things.
//For a real driver, we would parse the query contents and make
//appropriate application calls to execute the query. We would place the
//results of the query in zero or more <instance>
//elements under the <output> element.
//
//for the skeleton driver, just pretend everything worked but we didn't
//find anything (the
//absence of an <instance> element indicates nothing was found)
addStatusElement(outputElement,STATUS_SUCCESS,null,eventId);
}

/**
 * Return the association value for a command element (add, modify, etc.)
 *
 * @param command The command element
 * @return The association value, or null if no association found.
 */
private String getAssociation(Element command)
{
//find the association element as a child of the command
Node childNode = command.getFirstChild();
while (childNode != null)
{
if (childNode.getNodeType() == Node.ELEMENT_NODE &&
childNode.getNodeName().equals("association"))
{
return com.novell.xsl.util.Util.getXSLStringValue(childNode);
}
childNode = childNode.getNextSibling();
}
return null;
}

/**
 * Add subscriber state to the document to be returned at shutdown.
 * This is called by <code>SkeletonDriverShim.shutdown()</code>.
 *
 * @param output The output element for the document that will be
* returned to DirXML.
 */
void setState(Element output)
{
//add our state to be saved at shutdown
addState(output,"subscriber-state","current-association",Integer.toString
(currentAssociation));
}

/**
 * Table of parameters that this subscriber shim wants to get from the
 * &lt;subscriber-options> element of the init-params.
 * <p>
 * This is illustrative, of course, an actual driver would want parameters
 * specific to its functionality.
 */
private static final ShimParamDesc[] SUBSCRIBER_PARAMS =
{
//options
new ShimParamDesc("sub-1",ShimParamDesc.STRING_TYPE,false),
//state
new ShimParamDesc("current-association",ShimParamDesc.INT_TYPE,false)
};
}

DirXML PublisherShim

The PublisherShim is responsible for executing all of the XDS commands through the publisher channel. NDS will be checked to determine if an object already has an association. This is quickly and easily accomplished because all the associations are indexed and reverse indexed to make them easy to search.

If no object exists, then the match rule will be applies to try and find a match. If multiple matches are found, then DirXML flags an error. If a single match is found, then an association is created. If no match is found, then a new object will be created in NDS.

The create rule will determine if all the minimum attributes are satisfied for the create event. If they are, then a new NDS object is created. It is created in a location in the eDirectory tree according to the location defined in the placement rule.

Once all objects are created and matched, then the association table is updated and the data is synchronized.

An interface is passed by the DirXML engine via the SubscriptionShim.execute() so that the SubscriptionShim can query DirXML for any additional information it may need to process a command. The interface is passed by the PublicationShim into XmlCommandProcessor.execute() when calling DirXML to report an application event. DirXML uses the XmlQueryProcessor interface to query the PublicationShim for any additional information DirXML may need to process the application event.

The DirXML PublisherShim uses the following Interface Methods:

  • init - init will be called before the invocation of start. Init returns an instance of the publisher for the DirXML engine to call.

  • start - start starts the PublicationShim. Start is called by the DirXML engine.

The following code illustrates how to write a PublicationShim for a DirXML driver. For more information see the DirXML Driver Developer Kit documentation at http://developer.novell.com.

Sample Code File: SkeletonDriverShim.java


// Sample code file: SkeletonPublicationShim.java
// Warning: This code has been marked up for HTML

/*************************************************************************

Copyright 1999-2002 Novell, Inc. All Rights Reserved.

With respect to this file, Novell hereby grants to Developer a
royalty-free, non-exclusive license to include this sample code
and derivative binaries in its product. Novell grants to Developer
worldwide distribution rights to market, distribute or sell this
sample code file and derivative binaries as a component of
Developer's product(s). Novell shall have no obligations to
Developer or Developer's customers with respect to this code.

DISCLAIMER:

Novell disclaims and excludes any and all express, implied, and
statutory warranties, including, without limitation, warranties
of good title, warranties against infringement, and the implied
warranties of merchantability and fitness for a particular purpose.
Novell does not warrant that the software will satisfy customer's
requirements or that the licensed works are without defect or error
or that the operation of the software will be uninterrupted.
Novell makes no warranties respecting any technical services or
support tools provided under the agreement, and disclaims all other
warranties, including the implied warranties of merchantability and
fitness for a particular purpose.

***************************************************************************/
package com.novell.nds.dirxml.driver.skeleton;

import com.novell.nds.dirxml.driver.*;
import org.w3c.dom.*;

/**
 * A basic skeleton for implementing a <code>PublicationShim</code>.
 * <p>
 * The <code>PublicationShim</code> is an interface used to start
 * the application driver publication process.
 * <p>
 * A <code>PublicationShim</code> will almost always also implement
 *<code>XmlQueryProcessor</code>
 * but it could also delegate it to another object
 * <p>
 * Note that the publisher init() and start() methods are called on a thread
 *separate from
 * the thread used for calling the DriverShim and SubscriptionShim methods.
 *
 * @version 2.0 28Jun2000
 */
public class SkeletonPublicationShim extends CommonImpl
implements PublicationShim, XmlQueryProcessor
{
/**
 * Variable used in <code>start()</code> to determine when to return from
* <code>start()</code>.
 */
boolean shutdown = false;
/**
 * Variable used to control how often the thread in <code>start()</code>
* wakes up to
 * poll the application. Value is in seconds.
 */
int pollingInterval = 2;
/**
* Object used as a semaphore so subscriber-channel thread can
* wake publisher thread up to tell it to shutdown.
*/
Object semaphore = new Object();
/**
* Filter used to filter application events before sending them
* to DirXML.
*/
DriverFilter filter = null;
/**
* Authentication info initialized by driver shim at init()
*/
private AuthenticationParams authParams = null;

/**
* constructor, not strictly required, but a good idea
*/
SkeletonPublicationShim(AuthenticationParams authParams)
{
//set up the Trace object with the name of this object
super("SkeletonPublicationShim");
this.authParams = authParams;
}

/**
* <code>init</code> will be called before the invocation of start.
*
* @param initParameters XML document that contains the publisher
* initialization parameters.
* @return XML document containing status/messages from init operation.
*/
public XmlDocument init(XmlDocument initParameters )
{
tracer.trace("init");
//construct a driver filter for the publication shim to use for filtering
//application events. In an actual driver,
//the publisher would use the filter to filter events
//from the application to avoid publishing unnecessary events to DirXML.
Document initDoc = initParameters.getDocument();
//get any non-authentication options from the init document
ShimParams params = getShimParams(initDoc,"publisher",PUBLISHER_PARAMS);
//get any the polling interval that may have been passed in
int pi = params.getIntParam("polling-interval");
if (pi != -1)
{
//change our default polling interval to whatever was setup using
//ConsoleOne
pollingInterval = pi;
}
//setup a filter for use in start()
//NOTE: the skeleton publisher doesn't actually make use of the filter,
//but this code is here to illustrate how to create the filter based on
//the init
//parameters
NodeList filterList = initDoc.getElementsByTagName("driver-filter");
int i = 0;
Element filterElement;
while ((filterElement = (Element)filterList.item(i++)) != null)
{
String type = filterElement.getAttribute("type");
if (type.length() == 0 || type.equals("publisher"))
{
filter = new DriverFilter(filterElement);
break;
}
}
if (filter == null)
{
//if weren't able to setup a filter, setup a null
//filter so we don't have to check for filter != 0 everywhere
filter = new DriverFilter();
}
return createSuccessDocument();
}

/**
* <code>start()</code> starts the <code>PublicationShim</code>. The
* publisher shim should not return from start until DriverShim.shutdown() is
* called, or a fatal error occurs. Returning prematurely from
* <code>start()</code> will cause DirXML to shut down the driver.
*
* @param execute <code>XmlCommandProcessor</code> that can invoked in order
* to publish information to NDS on behalf of the application. execute must
* only be invoked from the thread on which <code>start()</code> was invoked.
*
* @return XML document containing status/messages from start operation
*/
public XmlDocument start( XmlCommandProcessor execute)
{
//NOTE: this implements a polling method of communication with the
//application. this may not be appropriate if the application supports an
//event notification system
tracer.trace("start");
//loop until we're told to shutdown (or some fatal error occurs)
while(!shutdown)
{
// skeleton implementation just wakes up every so often to
// see if it needs to shutdown and return.
try
{
tracer.trace("polling...");
//In a real driver, we'd do whatever was necessary to ask the
//application what changed and build an input document to publish
//the change events to DirXML.

//wait for subscriber channel thread to wake us up, or for polling
//interval to expire.
//NOTE: the use of the semaphore is highly recommended. It prevents
//a long polling interval from interfering with the orderly
//shutdown of the driver.
synchronized(semaphore)
{
//our pollingInterval value is in seconds, Object.wait() takes
//milliseconds
semaphore.wait(pollingInterval * 1000);
}
}
catch(InterruptedException ie)
{
}
}
tracer.trace("stopping");
return createSuccessDocument();
}

/**
 * <code>stop()</code> is not part of the PublicationShim interface but is
* an implementation detail of
* the skeleton driver. <code>SkeletonDriver.shutdown()</code> calls
* <code>stop()</code> to
 * signal the publisher thread that it needs to exit from
* <code>start()</code>.
 */
void stop()
{
//tell publisher thread it's time to exit
shutdown = true;
//tell the publisher thread to wake up, if it happens to be sleeping
synchronized(semaphore)
{
semaphore.notifyAll();
}
}

// implementation of XmlQueryProcessor

/**
 * <code>query</code> will accept an XML-encoded query and
 * return the results
 *
 * @param doc A document containing an XDS encoded query
 * @return The results of the query
 */
public XmlDocument query(XmlDocument doc)
{
tracer.trace("query");
//since this is a skeleton, and there is nothing to query, just return
//an empty output document with a success status. The absence of an
//<instance> element tells DirXML that nothing matched the query.
return createSuccessDocument();
}

/**
 * Table of parameters that this publisher shim wants to get from the
* &lt;publisher-options> element
 * of the init-params.
 * <p>
 * This is illustrative, of course, an actual driver would
* want parameters specific to its
 * functionality.
 */
private static final ShimParamDesc[] PUBLISHER_PARAMS =
{
//options
new ShimParamDesc("pub-1",ShimParamDesc.STRING_TYPE,false),
new ShimParamDesc("polling-interval",ShimParamDesc.INT_TYPE,false)
};

}

XML Uses in DirXML Drivers

Novell's DiXML drivers are quite straight-forward thanks to the power and flexibility of XML. The following are some of ways that DirXML makes use of XML and similar technologies.

XML, XSL, and XDS

Novell's DirXML makes extensive use of XML, XSL, and XDS. If your external application does not store the information in XML format, your driver will need to convert the data from its native format to XDS before sending it to eDirectory to store. XDS is the DirXML flavor of XML and is documented in the nds.dtd file. When your driver receives updates from eDirectory, your driver will need to take the XDS formatted data and convert it to the application's native format.

XSLT

XSLT (Extensible Stylesheet Language Transformations) can be used to transform the XDS format to another variant of XML or another standard format such as LDIF.

XMLDocument

XMLDocument represents a XML document, which mostly uses the Document Object Model (DOM). It is a platform and language neutral interface the will allow programs and scripts to dynamically access and update the content, structure and style of documents in a tree-like form.

XMLCommandProcessor

XMLCOmmandProcessor is an Interface that defines an interface for any class that can take an XML-encoded command and process it. The interface is passed to PublicationShim.start() so that the PublicationShim can notify DirXML of application events.



Download Sample Code - 16.4MB
Previous blank Table of Contents blank Next