/**
 * Copyright (c) Unpublished Work of Novell, Inc. All Rights Reserved.
 * THIS WORK IS AN UNPUBLISHED WORK AND CONTAINS CONFIDENTIAL,
 * PROPRIETARY AND TRADE SECRET INFORMATION OF NOVELL, INC. ACCESS TO
 * THIS WORK IS RESTRICTED TO (I) NOVELL, INC. EMPLOYEES WHO HAVE A
 * NEED TO KNOW HOW TO PERFORM TASKS WITHIN THE SCOPE OF THEIR
 * ASSIGNMENTS AND (II) ENTITIES OTHER THAN NOVELL, INC. WHO HAVE
 * ENTERED INTO APPROPRIATE LICENSE AGREEMENTS. NO PART OF THIS WORK
 * MAY BE USED, PRACTICED, PERFORMED, COPIED, DISTRIBUTED, REVISED,
 * MODIFIED, TRANSLATED, ABRIDGED, CONDENSED, EXPANDED, COLLECTED,
 * COMPILED, LINKED, RECAST, TRANSFORMED OR ADAPTED WITHOUT THE PRIOR
 * WRITTEN CONSENT OF NOVELL, INC. ANY USE OR EXPLOITATION OF THIS
 * WORK WITHOUT AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO
 * CRIMINAL AND CIVIL LIABILITY.
 *
 * %version:  %
 * %created_by:  %
 * %date_modified: %
 */

package com.novell.nds.dirxml.driver.xds.skeleton;


import com.novell.nds.dirxml.driver.SubscriptionShim;
import com.novell.nds.dirxml.driver.XmlDocument;
import com.novell.nds.dirxml.driver.XmlQueryProcessor;
import com.novell.nds.dirxml.driver.xds.*;
import com.novell.nds.dirxml.driver.xds.util.StatusAttributes;
import com.novell.nds.dirxml.driver.xds.util.XDSUtil;

import java.util.HashMap;
import java.util.ListIterator;
import java.util.Map;


/**
 * A basic skeleton for implementing the <code>SubscriptionShim</code>.
 *
 * <p>
 * The <code>SubscriptionShim</code> defines an interface for an application
 * driver to receive commands from the DirXML engine. These commands must be
 * executed in the application on behalf of the DirXML engine.
 * </p>
 */
public class SkeletonSubscriptionShim
    extends CommonImpl
    implements SubscriptionShim
{
    //~ Static fields/initializers ---------------------------------------------

    //MODIFY:  put the number of subscriber parameters your driver uses here

    /** The number of Subscriber parameters. */
    private static final int NO_OF_PARAMS = 2;

    //MODIFY:  put the tag names of your parameters here

    /**
     * The tag name of the element that holds the current association counter
     *  value.
     * */
    private static final String TAG_CURRENT_ASSOCIATION = "current-association";

    /** The tag name of the first Subscriber parameter. */
    private static final String TAG_SUB_1 = "sub-1";

    //MODIFY:  put the default values for your parameters here

    /** The default value for the first Subscriber parameter. */
    private static final String DEFAULT_SUB_1 = "String for Subscriber";

    /** The default value for the association counter. */
    private static final String DEFAULT_ASSOCIATION = "1";

    //MODIFY:  put your driver's activation id and activation level
    //  here

    /** The activation identifier for this driver. */
    private static final String DRIVER_ID_VALUE = "JSKEL";

    /** The activation level for this driver. */
    private static final String DRIVER_MIN_ACTIVATION_VERSION = "0";

    //~ Instance fields --------------------------------------------------------

    /**
     * Container for subscriber parameters passed to
     * <code>init(XmlDocument)</code>.
     *
     * <p></p>
     *
     * @see #init(XmlDocument)
     * @see #setSubParams()
     */
    private Map subParams;

    /**
     * This value is used to create an association in eDirectory between an
     * eDirectory object and a hypothetical object in the skeleton driver's
     * application.  A real association would be whatever value uniquely
     * identifies an object in your application.
     *
     * <p></p>
     *
     * @see #addHandler(XDSAddElement, XDSCommandResultDocument)
     * @see #init(XmlDocument)
     */
    private int currentAssociation;

    /**
     * A reference to this driver instance. Used to retrieve configuration
     * information.
     *
     * <p></p>
     *
     * @see SkeletonDriverShim#getDriverRDN()
     * @see SkeletonDriverShim#getDriverParams()
     */
    private SkeletonDriverShim driver;

    //~ Constructors -----------------------------------------------------------

    /**
     * Constructor.
     *
     * <p></p>
     *
     * @param someDriver a reference to this driver instance; must not be
     *        <code>null</code>
     */
    SkeletonSubscriptionShim(SkeletonDriverShim someDriver)
    {
        driver = someDriver;

        setDriverRDN(driver.getDriverRDN());
        setTrace();
        setDriverParams(driver.getDriverParams());
        setSubParams();

        currentAssociation = -1;
    }//SkeletonSubscriptionShim(SkeletonDriverShim)

    //~ Methods ----------------------------------------------------------------

    /**
     * A non-interface method that specifies the parameters this
     * SubscriptionShim is expecting.
     */
    private void setSubParams()
    {
        //MODIFY:  construct parameter descriptors here for your
        //  subscriber
        //The XDS.jar library automatically checks parameter
        //data types for you.  When a RequiredConstraint
        //is added to a parameter, the library will check init documents
        //to ensure the parameter is present and has a value.  When you
        //add RangeConstraints or EnumConstraints to a parameter, the
        //library will check parameter values to see if they satisfy these
        //constraints.
        Parameter param;

        subParams     = new HashMap(NO_OF_PARAMS);
        param         = new Parameter(
                TAG_SUB_1,//tag name
                DEFAULT_SUB_1,//default value
                DataType.STRING);//data type
        subParams.put(
            param.tagName(),
            param);

        param = new Parameter(
                TAG_CURRENT_ASSOCIATION,
                DEFAULT_ASSOCIATION,
                DataType.INT);
        param.add(RangeConstraint.POSITIVE);
        subParams.put(
            param.tagName(),
            param);
    }//setSubParams():void

    /**
     * <code>init</code> will be called before the first invocation of
     * <code>execute</code>.
     *
     * <p>
     * In general, application connectivity should be handled in
     * <code>execute(XmlDocument, XmlQueryProcessor)</code> so a driver can
     * start when the application is down.
     * </p>
     *
     * <p></p>
     *
     * @param initXML XML document that contains the subscriber initialization
     *        parameters and state
     *
     * @return an XML document containing status messages for this operation
     */
    public XmlDocument init(XmlDocument initXML)
    {
        //MODIFY:  initialize your driver here
        //example initialization document:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product version="1.1a">DirXML</product>
                   <contact>Novell, Inc.</contact>
               </source>
               <input>
                   <init-params src-dn="\NEW_DELL_TREE\NOVELL\Driver Set\Skeleton Driver (Java, XDS)\Subscriber">
                       <authentication-info>
                           <server>server.app:400</server>
                           <user>User1</user>
                       </authentication-info>
                       <driver-filter>
                           <allow-class class-name="User">
                               <allow-attr attr-name="Surname"/>
                               <allow-attr attr-name="Telephone Number"/>
                               <allow-attr attr-name="Given Name"/>
                           </allow-class>
                       </driver-filter>
                       <subscriber-options>
                   <sub-1 display-name="Sample Subscriber option">String for Subscriber</sub-1>
               </subscriber-options>
                   </init-params>
               </input>
           </nds>
         */

        //example result document:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product build="20021214_0304" instance="Skeleton Driver (Java, XDS)" version="1.1">DirXML Skeleton Driver (Java, XDS)</product>
                   <contact>My Company Name</contact>
               </source>
               <output>
                   <status level="success" type="driver-status">
                       <parameters>
                           <current-association display-name="current-association">1</current-association>
                           <sub-1 display-name="Sample Subscriber option">String for Subscriber</sub-1>
                       </parameters>
                   </status>
               </output>
           </nds>
         */
        trace.trace(
            "init",
            1);

        XDSResultDocument result;
        StatusAttributes  attrs;

        //create result document for reporting status to the DirXML engine
        result = newResultDoc();

        try
        {
            XDSInitDocument  init;
            XDSStatusElement status;
            Parameter        nextAssociation;

            //parse initialization document
            init = new XDSInitDocument(initXML);

            //get subscriber parameters from init doc
            init.parameters(subParams);

            //get any state info from the init doc; if this is the first
            //  time the driver is started, we'll get the default
            currentAssociation     = ((Parameter)subParams.get(
                    TAG_CURRENT_ASSOCIATION)).toInteger().intValue();

            //perform any other initialization that might be required
            //append a successful <status> element to result doc
            attrs     = StatusAttributes.factory(
                    StatusLevel.SUCCESS,
                    StatusType.DRIVER_STATUS,
                    null);//event-id
            status = XDSUtil.appendStatus(
                    result,//doc to append to
                    attrs,//status attribute values
                    null);//description

            //append the parameter values the subscriber is actually using
            status.parametersAppend(subParams);
        }//try
        catch(Exception e)//don't want to catch Error class with Throwable
        {
            //e instance of XDSException:
            //
            //  init document is malformed or invalid -- or --
            //  it is missing required parameters or contains
            //  illegal parameter values
            //e instance of RuntimeException:
            //  e.g., NullPointerException
            attrs = StatusAttributes.factory(
                    StatusLevel.FATAL,
                    StatusType.DRIVER_STATUS,
                    null);//event-id
            XDSUtil.appendStatus(
                result,//doc to append to
                attrs,//status attribute values
                null,//description
                e,//exception
                XDSUtil.appendStackTrace(e),//append stack trace?
                initXML);//xml to append
        }//catch

        //return result doc w/ status to the DirXML engine
        return result.toXML();
    }//init(XmlDocument):XmlDocument

    /**
     * <code>execute</code> will execute a command encoded in an XML document.
     *
     * <p></p>
     *
     * @param commandXML the document that contains the commands
     * @param processor a query processor that can be used to query the
     *        directory
     *
     * @return an XML document containing status messages and commands
     *         resulting from this operation (e.g., &lt;add-association&gt;,
     *         &lt;remove-association&gt;)
     */
    public XmlDocument execute(
        XmlDocument       commandXML,
        XmlQueryProcessor processor)
    {
        //example command document:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product version="1.1a">DirXML</product>
                   <contact>Novell, Inc.</contact>
               </source>
               <input>
                   <add class-name="User" event-id="0" src-dn="\NEW_DELL_TREE\NOVELL\test\a" src-entry-id="33071">
                       <add-attr attr-name="Surname">
                           <value timestamp="1040071990#3" type="string">a</value>
                       </add-attr>
                       <add-attr attr-name="Telephone Number">
                           <value timestamp="1040072034#1" type="teleNumber">111-1111</value>
                       </add-attr>
                   </add>
               </input>
           </nds>
         */

        //example result document:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product build="20021216_0123" instance="Skeleton Driver (Java, XDS)" version="1.1">DirXML Skeleton Driver (Java, XDS)</product>
                   <contact>My Company Name</contact>
               </source>
               <output>
                   <add-association dest-dn="\NEW_DELL_TREE\NOVELL\test\a" event-id="0">1</add-association>
                   <init-params>
                       <subscriber-state>
                           <current-association>2</current-association>
                       </subscriber-state>
                   </init-params>
                   <status event-id="0" level="success" type="driver-general"/>
               </output>
           </nds>
         */
        trace.trace(
            "execute",
            1);

        XDSCommandResultDocument result;
        StatusAttributes         attrs;
        String                   eventID;

        //setup result document for use by command handlers
        result = new XDSCommandResultDocument();
        appendSourceInfo(result);
        eventID = null;

        try
        {
            XDSCommandDocument commands;

            //parse/validate command document; it may have been malformed or
            //  invalidated during style sheet processing
            commands = new XDSCommandDocument(commandXML);

            //unlike other commands, the identity query doesn't require
            //  app connectivity to be processed
            if(commands.containsIdentityQuery())
            {
                XDSQueryElement query;

                query       = commands.identityQuery();
                eventID     = query.getEventID();
                appendDriverIdentityInfo(result);

                //append a successful <status> element to result doc
                attrs = StatusAttributes.factory(
                        StatusLevel.SUCCESS,
                        StatusType.DRIVER_GENERAL,
                        eventID);
                XDSUtil.appendStatus(
                    result,//doc to append to
                    attrs,//status attribute values
                    null);//description
            }//if
            else
            {
                ListIterator   c;
                CommandElement command;

                //try and connect with our mythical app if we aren't already
                //  connected
                connect();

                c = commands.childElements().listIterator();

                while(c.hasNext())
                {
                    command     = (CommandElement)c.next();
                    eventID     = command.getEventID();
                    dispatch(
                        command,
                        result);

                    //append a successful <status> element to result doc
                    attrs = StatusAttributes.factory(
                            StatusLevel.SUCCESS,
                            StatusType.DRIVER_GENERAL,
                            eventID);
                    XDSUtil.appendStatus(
                        result,//doc to append to
                        attrs,//status attribute values
                        null);//description
                }//while
            }//else
        }//try
        catch(XDSException xds)
        {
            //command document is malformed or invalid
            attrs = StatusAttributes.factory(
                    StatusLevel.ERROR,
                    StatusType.DRIVER_GENERAL,
                    null);
            XDSUtil.appendStatus(
                result,//doc to append to
                attrs,//status attribute values
                null,//description
                xds,//exception
                false,//append stack trace?
                commandXML);//xml to append
        }
        catch(java.io.IOException io)
        {
            //there's a connectivity problem; if it's an authentication problem,
            //  set the status level to "fatal" and status type to
            //  "app-authentication" which will cause the driver to shutdown;
            //  if the app isn't available, set the status level to "retry" and
            //  the status type to "app-connection" which will cause the
            //  DirXML engine to send the event again in 30 seconds
            connected     = false;

            attrs = StatusAttributes.factory(
                    StatusLevel.RETRY,
                    StatusType.APP_CONNECTION,
                    eventID);
            XDSUtil.appendStatus(
                result,//doc to append to
                attrs,//status attribute values
                Errors.COMM,//description
                io,//exception
                false,//append stack trace?
                null);//xml to append
        }//try
        catch(Exception e)//don't want to catch Error class with Throwable
        {
            //something bad happened...
            //there's something wrong with the driver; rather than
            //  loose the event, shutdown
            attrs = StatusAttributes.factory(
                    StatusLevel.FATAL,
                    StatusType.DRIVER_STATUS,
                    eventID);
            XDSUtil.appendStatus(
                result,//doc to append to
                attrs,//status attribute values
                null,//description
                e,//exception
                true,//append stack trace?
                commandXML);//xml to append
        }//catch

        //return result doc w/ status to the DirXML engine
        return result.toXML();
    }//execute(XmlDocument, XmlQueryProcessor):XmlDocument

    /**
     * A non-interface method that dispatches a command to the appropriate
     * handler.
     *
     * <p></p>
     *
     * @param command the command element from the command document
     * @param result the document to append commands to (e.g.,
     *        &lt;add-association&gt;, &lt;remove-association&gt;)
     *
     * @throws XDSParseException if command missing required
     *         &lt;association&gt; element
     */
    private void dispatch(
        CommandElement           command,
        XDSCommandResultDocument result)
        throws XDSParseException
    {
        trace.trace(
            "dispatch",
            1);

        Class commandClass;

        commandClass = command.getClass();

        if(commandClass == XDSAddElement.class)
        {
            addHandler(
                (XDSAddElement)command,
                result);
            appendStateInfo(result);
        }
        else if(commandClass == XDSModifyElement.class)
        {
            modifyHandler(
                (XDSModifyElement)command,
                result);
        }
        else if(commandClass == XDSDeleteElement.class)
        {
            deleteHandler(
                (XDSDeleteElement)command,
                result);
        }
        else if(commandClass == XDSRenameElement.class)
        {
            renameHandler(
                (XDSRenameElement)command,
                result);
        }
        else if(commandClass == XDSMoveElement.class)
        {
            moveHandler(
                (XDSMoveElement)command,
                result);
        }
        else if(command instanceof XDSQueryElement)//allow for query and query-ex
        {
            queryHandler(
                (XDSQueryElement)command,
                result);
        }
        else if(commandClass == XDSModifyPasswordElement.class)
        {
            modifyPasswordHandler(
                (XDSModifyPasswordElement)command,
                result);
        }
        else if(commandClass == XDSCheckObjectPasswordElement.class)
        {
            checkObjectPasswordHandler(
                (XDSCheckObjectPasswordElement)command,
                result);
        }
        else
        {
            //this is either a custom command from a style sheet or
            //  a command from a newer DTD that the XDS library
            //  doesn't recongize
            trace.trace(
                "unhandled element:  " + command.tagName(),
                3);
        }
    }//dispatch(CommandElement, XDSCommandResultDocument):void

    /**
     * A non-interface method that handles an &lt;add> command.
     *
     * <p></p>
     *
     * @param add the &lt;add> element; must not be <code>null</code>
     * @param result the document to append an &lt;add-association&gt; to; must
     *        not be <code>null</code>
     */
    private void addHandler(
        XDSAddElement            add,
        XDSCommandResultDocument result)
    {
        //MODIFY:  put code to handle an <add> command here
        //example <add> element:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product version="1.1a">DirXML</product>
                   <contact>Novell, Inc.</contact>
               </source>
               <input>
                   <add class-name="User" event-id="0" src-dn="\NEW_DELL_TREE\NOVELL\test\a" src-entry-id="33071">
                       <add-attr attr-name="Surname">
                           <value timestamp="1040071990#3" type="string">a</value>
                       </add-attr>
                       <add-attr attr-name="Telephone Number">
                           <value timestamp="1040072034#1" type="teleNumber">111-1111</value>
                       </add-attr>
                   </add>
               </input>
           </nds>
         */

        //example add result:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product build="20021216_0123" instance="Skeleton Driver (Java, XDS)" version="1.1">DirXML Skeleton Driver (Java, XDS)</product>
                   <contact>My Company Name</contact>
               </source>
               <output>
                   <add-association dest-dn="\NEW_DELL_TREE\NOVELL\test\a" event-id="0">1</add-association>
                   <init-params>
                       <subscriber-state>
                           <current-association>2</current-association>
                       </subscriber-state>
                   </init-params>
                   <status event-id="0" level="success" type="driver-general"/>
               </output>
           </nds>
         */
        trace.trace(
            "addHandler",
            1);

        String className;

        //because the command document was validated, we're guaranteed to
        //  get a non-null class-name attribute value
        className = add.getClassName();

        trace.trace(
            "addHandler: class-name == " + toLiteral(className),
            2);

        //do whatever is required here for the supported application
        //iterate through the <add-attr> child elements
        ListIterator a;

        //do whatever is required here for the supported application
        //iterate through the <add-attr> child elements
        ListIterator      v;
        XDSAddAttrElement addAttr;
        XDSValueElement   value;

        a = add.extractAddAttrElements().listIterator();

        while(a.hasNext())
        {
            addAttr = (XDSAddAttrElement)a.next();
            trace.trace(
                "addHandler: attr-name  == " +
                toLiteral(addAttr.getAttrName()),
                2);
            v = addAttr.extractValueElements().listIterator();

            while(v.hasNext())
            {
                value = (XDSValueElement)v.next();
                trace.trace(
                    "addHandler:\tvalue == " + toLiteral(value.extractText()),
                    2);
            }
        }//while

        //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.)
        XDSAddAssociationElement addAssociation;

        //append an <add-association> element to result doc
        addAssociation = result.appendAddAssociationElement();

        //add the attributes that tells the DirXML engine to what eDirectory object
        //  this applies
        addAssociation.setDestDN(add.getSrcDN());
        addAssociation.setEventID(add.getEventID());

        //create the association value and put it under the <add-association>
        //  element increment our fake association value for the next add
        addAssociation.appendText(String.valueOf(currentAssociation));
        ++currentAssociation;
    }//addHandler(XDSAddElement, XDSCommandResultDocument):void

    /**
     * A non-interface method that handles a &lt;modify-password> command.
     *
     * <p></p>
     *
     * @param modifyPassword the &lt;modify-password> element; must not be
     *        <code>null</code>
     * @param result unsued; present to ensure API consistency; may be
     *        <code>null</code>
     */
    private void modifyPasswordHandler(
        XDSModifyPasswordElement modifyPassword,
        XDSCommandResultDocument result)
    {
        //MODIFY:  put code to handle a <modify-password> command here
        trace.trace(
            "modifyPasswordHandler",
            1);

        String className;
        String assocValue;

        className      = modifyPassword.getClassName();
        assocValue     = modifyPassword.extractAssociationText();

        trace.trace(
            "modifyPasswordHandler: class-name   == " + toLiteral(className),
            2);
        trace.trace(
            "modifyPasswordHandler: association  == " + toLiteral(assocValue),
            2);

        String oldPassword;
        String newPassword;

        oldPassword = modifyPassword.extractOldPasswordText();
        trace.trace(
            "modifyPasswordHandler: old-password == " + toLiteral(oldPassword),
            2);
        newPassword = modifyPassword.extractPasswordText();
        trace.trace(
            "modifyPasswordHandler: new-password == " + toLiteral(newPassword),
            2);

        //do whatever is required here to modify a password in the application
    }//modifyPasswordHandler(XDSModifyPasswordElement, XDSCommandResultDocument):void

    /**
     * A non-interface method that handles a &lt;check-object-password>
     * command.
     *
     * <p></p>
     *
     * @param checkPassword the &lt;check-object-password> element; must not be
     *        <code>null</code>
     * @param result unsued; present to ensure API consistency; may be
     *        <code>null</code>
     */
    private void checkObjectPasswordHandler(
        XDSCheckObjectPasswordElement checkPassword,
        XDSCommandResultDocument      result)
    {
        //MODIFY:  put code to handle a <check-object-password> command here
        trace.trace(
            "checkObjectPassword",
            1);

        String assocValue;
        String password;

        assocValue     = checkPassword.extractAssociationText();
        password       = checkPassword.extractPasswordText();

        trace.trace(
            "checkObjectPassword: association  == " + toLiteral(assocValue),
            2);
        trace.trace(
            "checkObjectPassword: password     == " + toLiteral(password),
            2);

        //do whatever is required here to check a password in the application
    }//checkObjectPasswordHandler(XDSCheckObjectPasswordElement, XDSCommandResultDocument):void

    /**
     * A non-interface method that handles a &lt;modify> command.
     *
     * <p></p>
     *
     * @param modify the &lt;modify> element; must not be <code>null</code>
     * @param result unsued; present to ensure API consistency; may be
     *        <code>null</code>
     */
    private void modifyHandler(
        XDSModifyElement         modify,
        XDSCommandResultDocument result)
    {
        //MODIFY:  put code to handle a <modify> command here
        //example <modify> element:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product version="1.1a">DirXML</product>
                   <contact>Novell, Inc.</contact>
               </source>
               <input>
                   <modify class-name="User" event-id="0" src-dn="\NEW_DELL_TREE\NOVELL\test\a" src-entry-id="33071" timestamp="1040072695#2">
                       <association state="associated">1</association>
                       <modify-attr attr-name="Telephone Number">
                           <remove-value>
                               <value type="teleNumber">111-1111</value>
                           </remove-value>
                           <add-value>
                               <value timestamp="1040072695#2" type="teleNumber">222-2222</value>
                           </add-value>
                       </modify-attr>
                   </modify>
               </input>
           </nds>
         */

        //example modify result:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product build="20021216_0123" instance="Skeleton Driver (Java, XDS)" version="1.1">DirXML Skeleton Driver (Java, XDS)</product>
                   <contact>My Company Name</contact>
               </source>
               <output>
                   <status event-id="0" level="success" type="driver-general"/>
               </output>
           </nds>
         */
        trace.trace(
            "modifyHandler",
            1);

        String className;
        String assocValue;

        //because the command document was validated, we're guaranteed to
        //  get a non-null class-name attribute value
        className      = modify.getClassName();
        assocValue     = modify.extractAssociationText();

        trace.trace(
            "modifyHandler: class-name  == " + toLiteral(className),
            2);
        trace.trace(
            "modifyHandler: association == " + toLiteral(assocValue),
            2);

        //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
        //iterate through <modify-attr> children
        ListIterator m;

        //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
        //iterate through <modify-attr> children
        ListIterator         c;
        XDSElement           element;
        XDSModifyAttrElement modifyAttr;
        String               attrName;

        m = modify.extractModifyAttrElements().listIterator();

        while(m.hasNext())
        {
            modifyAttr     = (XDSModifyAttrElement)m.next();

            //because the command document was validated, we're guaranteed to
            //  get a non-null attr-name attribute value
            attrName = modifyAttr.getAttrName();
            trace.trace(
                "modifyHandler: attr-name   == " + toLiteral(attrName),
                2);
            c = modifyAttr.childElements().listIterator();

            while(c.hasNext())
            {
                element = (XDSElement)c.next();

                if(element.getClass() == XDSAddValueElement.class)
                {
                    //<add-value>
                    addValueHandler((XDSAddValueElement)element);
                }
                else if(element.getClass() == XDSRemoveValueElement.class)
                {
                    //<remove-value>
                    removeValueHandler((XDSRemoveValueElement)element);
                }
                else if(element.getClass() == XDSRemoveAllValuesElement.class)
                {
                    //<remove-all-values>
                    removeAllValuesHandler((XDSRemoveAllValuesElement)element);
                }
                else
                {
                    trace.trace(
                        "unhandled element:  " + element.tagName(),
                        3);
                }
            }//while
        }//while
    }//modifyHandler(XDSModifyElement, XDSCommandResultDocument):void

    /**
     * A non-interface method that handles an &lt;add-value> command.
     *
     * <p></p>
     *
     * @param addValue the &lt;add-value> element; must not be
     *        <code>null</code>
     */
    private void addValueHandler(XDSAddValueElement addValue)
    {
        //MODIFY:  put code to handle an <add-value> here
        ListIterator    v;
        XDSValueElement value;
        ValueType       type;

        v = addValue.extractValueElements().listIterator();

        while(v.hasNext())
        {
            value = (XDSValueElement)v.next();
            trace.trace(
                "modifyHandler:\tadd-value    == " +
                toLiteral(value.extractText()),
                2);
            type = value.getType();
        }
    }//addValueHandler(XDSAddValueElement, String, String):void

    /**
     * A non-interface method that handles a &lt;remove-value> command.
     *
     * <p></p>
     *
     * @param removeValue the &lt;remove-value> element; must not be
     *        <code>null</code>
     */
    private void removeValueHandler(XDSRemoveValueElement removeValue)
    {
        //MODIFY:  put code to handle a <remove-value> here
        ListIterator    v;
        XDSValueElement value;
        ValueType       type;

        v = removeValue.extractValueElements().listIterator();

        while(v.hasNext())
        {
            value = (XDSValueElement)v.next();
            trace.trace(
                "modifyHandler:\tremove-value == " +
                toLiteral(value.extractText()),
                2);
            type = value.getType();
        }
    }//removeValueHandler(XDSRemoveValueElement, String, String):void

    /**
     * A non-interface method that handles a &lt;remove-all-values> command.
     *
     * <p></p>
     *
     * @param removeAll the &lt;remove-all-values> element
     */
    private void removeAllValuesHandler(XDSRemoveAllValuesElement removeAll)
    {
        //MODIFY:  put code to handle a <remove-all-value> here
        trace.trace(
            "modifyHandler:\tremove-all-values",
            2);
    }//removeAllValuesHandler(XDSRemoveAllValuesElement, String, String):void

    /**
     * A non-interface method that handles a &lt;delete> command.
     *
     * <p></p>
     *
     * @param delete the &lt;delete> element; must not be <code>null</code>
     * @param result the doc to append a &lt;remove-association&gt; to; must
     *        not be <code>null</code>
     *
     * @throws XDSParseException if there's no association value
     */
    private void deleteHandler(
        XDSDeleteElement         delete,
        XDSCommandResultDocument result)
        throws XDSParseException
    {
        //MODIFY:  put code to handle a <delete> command here
        //example <delete> element:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product version="1.1a">DirXML</product>
                   <contact>Novell, Inc.</contact>
               </source>
               <input>
                   <delete class-name="User" event-id="0" src-dn="\NEW_DELL_TREE\NOVELL\other\b" src-entry-id="33071" timestamp="1040073474#0">
                       <association state="associated">1</association>
                   </delete>
               </input>
           </nds>
         */

        //example delete result:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product build="20021216_0123" instance="Skeleton Driver (Java, XDS)" version="1.1">DirXML Skeleton Driver (Java, XDS)</product>
                   <contact>My Company Name</contact>
               </source>
               <output>
                   <status event-id="0" level="success" type="driver-general"/>
               </output>
           </nds>
         */
        trace.trace(
            "deleteHandler",
            1);

        String className;
        String assocValue;

        //because the command document was validated, we're guaranteed to
        //  get a non-null class-name attribute value
        className      = delete.getClassName();
        assocValue     = delete.extractAssociationText();

        trace.trace(
            "deleteHandler: class-name  == " + toLiteral(className),
            2);
        trace.trace(
            "deleteHandler: association == " + toLiteral(assocValue),
            2);

        if(assocValue == null)
        {
            //in general, you'll need an association value to identity
            //  the application object
            throw new XDSParseException(Errors.NO_ASSOC);
        }//if

        //NOTE:  shims should no longer issue <remove-association> commands
        //do whatever is required here for the supported application
        //use the association value to look up the object in the application,
        //then perform the delete
    }//deleteHandler(XDSDeleteElement, XDSCommandResultDocument):void

    /**
     * A non-interface method that handles a &lt;rename> command.
     *
     * <p></p>
     *
     * @param rename the &lt;rename> element; must not be <code>null</code>
     * @param result unsued; present to ensure API consistency; may be
     *        <code>null</code>
     */
    private void renameHandler(
        XDSRenameElement         rename,
        XDSCommandResultDocument result)
    {
        //MODIFY:  put code to handle a <rename> command here
        //example <rename> element:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product version="1.1a">DirXML</product>
                   <contact>Novell, Inc.</contact>
               </source>
               <input>
                   <rename class-name="User" event-id="0" old-src-dn="\NEW_DELL_TREE\NOVELL\test\a" remove-old-name="false" src-dn="\NEW_DELL_TREE\NOVELL\test\b" src-entry-id="33071" timestamp="1040071990#0">
                       <association state="associated">1</association>
                       <new-name>b</new-name>
                   </rename>
               </input>
           </nds>
         */

        //example rename result:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product build="20021216_0123" instance="Skeleton Driver (Java, XDS)" version="1.1">DirXML Skeleton Driver (Java, XDS)</product>
                   <contact>My Company Name</contact>
               </source>
               <output>
                   <status event-id="0" level="success" type="driver-general"/>
               </output>
           </nds>
         */
        trace.trace(
            "renameHandler",
            1);

        String className;
        String assocValue;

        //because the command document was validated, we're guaranteed to
        //  get a non-null class-name attribute value
        className      = rename.getClassName();
        assocValue     = rename.extractAssociationText();

        //don't build the string unless it's actually going out
        trace.trace(
            "renameHandler: class-name  == " + toLiteral(className),
            2);
        trace.trace(
            "renameHandler: association == " + toLiteral(assocValue),
            2);

        //do whatever is required here for the supported application
        //use the assocValue to look up the object in the application, then perform the rename
        String newName;

        //because the command document was validated, we're guaranteed to
        //  get a non-null new-name value
        newName = rename.extractNewNameText();
        trace.trace(
            "renameHandler: new-name    == " + toLiteral(newName),
            2);
    }//renameHandler(XDSRenameElement, XDSCommandResultDocument):void

    /**
     * A non-interface method that handles a &lt;move> command.
     *
     * <p></p>
     *
     * @param move the &lt;move> element; must not be <code>null</code>
     * @param result unsued; present to ensure API consistency; may be
     *        <code>null</code>
     */
    private void moveHandler(
        XDSMoveElement           move,
        XDSCommandResultDocument result)
    {
        //MODIFY:  put code to handle a <move> command here
        //example <move> element:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product version="1.1a">DirXML</product>
                   <contact>Novell, Inc.</contact>
               </source>
               <input>
                   <move class-name="User" event-id="0" old-src-dn="\NEW_DELL_TREE\NOVELL\test\b" src-dn="\NEW_DELL_TREE\NOVELL\other\b" timestamp="1040071990#0">
                       <association state="associated">1</association>
                       <parent src-dn="\NEW_DELL_TREE\NOVELL\other" src-entry-id="33073"/>
                   </move>
               </input>
           </nds>
         */

        //example move result:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product build="20021216_0123" instance="Skeleton Driver (Java, XDS)" version="1.1">DirXML Skeleton Driver (Java, XDS)</product>
                   <contact>My Company Name</contact>
               </source>
               <output>
                   <status event-id="0" level="success" type="driver-general"/>
               </output>
           </nds>
         */
        trace.trace(
            "moveHandler",
            1);

        String className;
        String childAssociation;

        //because the command document was validated, we're guaranteed to
        //  get a non-null class-name attribute value
        className            = move.getClassName();
        childAssociation     = move.extractAssociationText();

        trace.trace(
            "moveHandler: class       == " + toLiteral(className),
            2);
        trace.trace(
            "moveHandler: association == " + toLiteral(childAssociation),
            2);

        //do whatever is required here for the supported application
        //use the assocValue to look up the object in the application, then perform the move
        XDSParentElement parent;
        String           parentAssociation;

        //because the command document was validated, we're guaranteed to
        //  get a non-null <parent> element referece
        parent                = move.extractParentElement();
        parentAssociation     = parent.extractAssociationText();
        trace.trace(
            "moveHandler:\tparent association == " +
            toLiteral(parentAssociation),
            2);
    }//moveHandler(XDSMoveElement, XDSCommandResultDocument):void

    /**
     * A non-interface method that appends driver identification information to
     * <code>result</code> in response to an identity query.
     *
     * <p></p>
     *
     * @param result may be <code>null</code>
     */
    private void appendDriverIdentityInfo(XDSCommandResultDocument result)
    {
        //MODIFY:  put your identity query response here
        //example identity query:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product version="1.1a">DirXML</product>
                   <contact>Novell, Inc.</contact>
               </source>
               <input>
                   <query event-id="query-driver-ident" scope="entry">
                       <search-class class-name="__driver_identification_class__"/>
                       <read-attr/>
                   </query>
               </input>
           </nds>
         */

        //example identity query response:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product build="20021214_0304" instance="Skeleton Driver (Java, XDS)" version="1.1">DirXML Skeleton Driver (Java, XDS)</product>
                   <contact>My Company Name</contact>
               </source>
               <output>
                   <instance class-name="__driver_identification_class__">
                       <attr attr-name="driver-id">
                           <value type="string">JSKEL</value>
                       </attr>
                       <attr attr-name="driver-version">
                           <value type="string">1.1</value>
                       </attr>
                       <attr attr-name="min-activation-version">
                           <value type="string">0</value>
                       </attr>
                   </instance>
                   <status event-id="query-driver-ident" level="success" type="driver-general"/>
               </output>
           </nds>
         */
        if(result != null)
        {
            //when appending static XML, it's simplest to use the XDSUtil class
            StringBuffer buffer;

            buffer = new StringBuffer();

            //library version and skeleton version are synonymous
            buffer.append(
                "<instance class-name=\"__driver_identification_class__\">");
            buffer.append("<attr attr-name=\"driver-id\">");
            buffer.append("<value type=\"string\">");
            buffer.append(DRIVER_ID_VALUE);
            buffer.append("</value>");
            buffer.append("</attr>");
            buffer.append("<attr attr-name=\"driver-version\">");
            buffer.append("<value type=\"string\">");
            buffer.append(XDS.VERSION);
            buffer.append("</value>");
            buffer.append("</attr>");
            buffer.append("<attr attr-name=\"min-activation-version\">");
            buffer.append("<value type=\"string\">");
            buffer.append(DRIVER_MIN_ACTIVATION_VERSION);
            buffer.append("</value>");
            buffer.append("</attr>");
            buffer.append("<attr attr-name=\"query-ex-supported\">");
            buffer.append("<value type=\"state\">false</value>");
            buffer.append("</attr>");
            buffer.append("</instance>");

            //append this XML to the <output> element
            XDSUtil.appendXML(
                result.domIOElement(),
                buffer.toString());

            //here's the equivalent XDS implementation:

            /*
               XDSInstanceElement instance;
               XDSAttrElement attr;
               XDSValueElement value;
               instance = result.appendInstanceElement();
               instance.setClassName(DTD.VAL_DRIVER_IDENT_CLASS);
               attr = instance.appendAttrElement();
               attr.setAttrName(DTD.VAL_DRIVER_ID);
               value = attr.appendValueElement(ValueType.STRING, DRIVER_ID_VALUE);
               attr = instance.appendAttrElement();
               attr.setAttrName(DTD.VAL_DRIVER_VERSION);
               value = attr.appendValueElement(ValueType.STRING, XDS.VERSION);
               attr = instance.appendAttrElement();
               attr.setAttrName(DTD.VAL_MIN_ACTIVATION_VERSION);
               value = attr.appendValueElement(ValueType.STRING, DRIVER_MIN_ACTIVATION_VERSION);
               attr.setAttrName(DTD.VAL_QUERY_EX_SUPPORTED);
               value = attr.appendValueElement(ValueType.STATE, "false");
             */
        }//if
    }//appendDriverIdentityInfo(XDSQueryElement, XDSCommandResultDocument):void

    /**
     * A non-interface method called from
     * <code>SkeletonDriverShim.shutdown(XmlDocument)</code> that signals to
     * the subscriber to free its resources.
     *
     * <p></p>
     *
     * @see SkeletonDriverShim#shutdown(XmlDocument)
     */
    void shutdown()
    {
        //MODIFY:  put your shutdown code here
        trace.trace(
            "shutdown",
            1);

        //do whatever is required to disconnect from the application
        //  e.g., close a Socket, etc.
        disconnect();
    }//shutdown():void

    /**
     * A non-interface method that appends subscriber state information to
     * <code>doc</code>.
     *
     * <p></p>
     *
     * @param doc the document to append state to; may be <code>null</code>
     */
    void appendStateInfo(StateDocument doc)
    {
        //MODIFY:  remove this method or put code to append state info
        //  here; you may need to add equivalent methods to your
        //  DriverShim and PublicationShim as well
        //appending custom content requires use of DOM or the XDSUtil
        //  class;  the example below uses the XDSUtil class:
        if(doc != null)
        {
            String state;

            state = "<init-params>" + "<subscriber-state>" +
                "<current-association>" + String.valueOf(currentAssociation) +
                "</current-association>" + "</subscriber-state>" +
                "</init-params>";

            //append this XML to the <output> element
            XDSUtil.appendXML(
                doc.domIOElement(),
                state);

            //here's an equivalent DOM implementation:

            /*
               XDSInitParamsElement initParams;
               XDSSubscriberStateElement state;
               initParams = doc.appendInitParamsElement();
               state = initParams.appendSubscriberStateElement();
               Element parent, child;
               Document document;
               Text text;
               document = state.domDocument();
               parent = state.domElement();
               child = document.createElement(TAG_CURRENT_ASSOCIATION);
               text = document.createTextNode(String.valueOf(currentAssociation));
               child.appendChild(text);
               parent.appendChild(child);
             */
        }//if
    }//appendStateInfo(StateDocument):void
}//class SkeletonSubscriptionShim
