/*************************************************************************
 Copyright © 2000-2003 Novell, Inc. All Rights Reserved.

 THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
 TREATIES. USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE
 AGREEMENT ACCOMPANYING THE SOFTWARE DEVELOPMENT KIT (SDK) THAT CONTAINS
 THIS WORK. PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS
 TO DEVELOPER A ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S
 SAMPLE CODE IN ITS PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION
 RIGHTS TO MARKET, DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT
 OF DEVELOPER'S PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR
 DEVELOPER'S CUSTOMERS WITH RESPECT TO THIS CODE.
 ************************************************************************/

package com.novell.nds.dirxml.driver.vrtest;


import java.io.*;
import java.util.*;

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


/**
 *  The VRTest subscriber.  Subscribes to directory events.
 */
public class VRTestSubscriptionShim
        extends CommonImpl
        implements SubscriptionShim, VRTestConstants, Constants
{

    //this constant identifies the channel/module being traced
    //  (e.g., VRTest Driver\Driver)
    static private final String TRACE_SUFFIX = "Subscriber";

    //the number of subscriber parameters
    static private final int NO_OF_PARAMS = 1;

    /** This driver's activation id. */
    static private final String DRIVER_ID_VALUE = "VRTEST";
    /** This driver's activation level. */
    static private final String DRIVER_MIN_ACTIVATION_VERSION = "0";


    /**
     *  Is data type <code>type</code> not supported by this driver?
     *  <p>
     *  @return
     *  <dl>
     *      <dt><code>true</code>:</dt>
     *      <dd>unsupported</dd>
     *      <dt><code>false</code>:</dt>
     *      <dd>supported</dd>
     *  </dl>
     */
    private static boolean unsupportedType(ValueType type)
    {
        return ((type == ValueType.OCTET) ||
                (type == ValueType.STRUCTURED));
    }//unsupportedType(ValueType):boolean


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


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


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

        setAPI(driver.getAPI());
        setDriverRDN(driver.getDriverRDN());
        setDriverParams(driver.getDriverParams());
        setTrace(TRACE_SUFFIX);
        setSubParams();
    }//VRTestSubscriptionShim(VRTestDriverShim)


    /**
     *  A non-interface method that specifies the parameters
     *  this SubscriptionShim is expecting.
     */
    private void setSubParams()
    {
        Parameter param;

        subParams = new HashMap(NO_OF_PARAMS);

        //state parameters
        //  must be optional since not available when driver first started
        param = new Parameter(TAG_RUN_COUNT, //tag name
                              DEFAULT_COUNT, //default
                              DataType.LONG); //data type
        param.add(RangeConstraint.POSITIVE);
        subParams.put(param.tagName(), param);
    }//setSubParams():void


    /**
     *  Initializes this subscriber.
     *  <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)
    {
        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 param;

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

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

            //get state info from the init doc; if this is the first
            //  time the driver is started, we'll get the default
            param = (Parameter) this.subParams.get(TAG_RUN_COUNT);
            setRunCount(param.toLong().longValue());
            param.overrideValue(String.valueOf(getRunCount()));

            //append subscriber state info to result doc
            appendStateInfo(result.appendInitParamsElement().appendSubscriberStateElement());

            //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 DirXML
        return result.toXML();
    }//init(XmlDocument):XmlDocument


    /**
     *  Executes commands on the VRTest server.
     *  <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
                               )
    {
        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();
                appendDriverIdentityInfo(result);

                //append a successful <status> element to result doc
                attrs = StatusAttributes.factory(StatusLevel.SUCCESS,
                                                 StatusType.DRIVER_GENERAL,
                                                 query.getEventID());
                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();
                this.connected = true;

                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 (VRTestException vrt)
        {
            //application error

            attrs = StatusAttributes.factory(StatusLevel.ERROR,
                                             StatusType.APP_GENERAL,
                                             eventID);
            XDSUtil.appendStatus(result, //doc to append to
                                 attrs, //status attribute values
                                 null, //description
                                 vrt, //exception
                                 false, //append stack trace?
                                 null); //xml to append
        }
        catch (java.io.IOException io)
        {
            //there's a connectivity problem; set the status level to "retry" and
            //  the status type to "app-authentication" which will cause the
            //  DirXML engine to send the event again in 30 seconds

            attrs = StatusAttributes.factory(StatusLevel.RETRY,
                                             StatusType.APP_CONNECTION,
                                             eventID);
            XDSUtil.appendStatus(result, //doc to append to
                                 attrs, //status attribute values
                                 getConnectHeader(), //description
                                 io, //exception
                                 false, //append stack trace?
                                 null); //xml to append
            connected = false;
        }//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>
     *  @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;)
     */
    private void dispatch(CommandElement command,
                          XDSCommandResultDocument result)
            throws VRTestException, IOException
    {
        trace.trace("dispatch", 1);

        Class commandClass;

        commandClass = command.getClass();
        if (commandClass == XDSAddElement.class)
        {
            addHandler((XDSAddElement) command, 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 (commandClass == XDSQueryElement.class)
        {
            queryHandler((XDSQueryElement) command, result);
        }
/*
        //not implemented...
        else if (commandClass == XDSModifyPasswordElement.class)
        {
            modifyPasswordHandler((XDSModifyPasswordElement) 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 adds an object on the VRTest server.
     *  <p>
     *  @param add the &lt;add> element; must not be <code>null</code>
     *  @param result the document to append an
     *      <code>&lt;add-association&gt;</code> to;
     *      must not be <code>null</code>
     *	@throws	VRTestException If an API error occurs.
     *  @throws IOException  If a communications error occurs.
     */
    private void addHandler(XDSAddElement add,
                            XDSCommandResultDocument result
                            )
            throws VRTestException, IOException
    {
        //  <!-- the element we're handling -->
        //
        //	<add class-name="class" src-dn="dn">
        //		<add-attr/> <!-- * -->
        //	</add>
        //
        //  <!-- * == zero or more -->

        //there are two pieces of information we have to extract from the
        //	<add> element in order to create an object on the VRTest server:
        //
        //	(1) the name of the object
        //	(2) the path of the object
        //
        //	object name:
        //		by convention, an object's name is represented on the VRTest
        //		server as an attribute called the naming attribute; the naming
        //		attribute also has a name (not to be confused with the object's
        //		name) which is defined in the server's schema definition file;
        //		by convention, the naming attribute is the first attribute
        //		defined under a class schema definition
        //
        //		e.g.
        //
        //		class
        //			name		"User"
        //			attribute                 <-- (naming attribute)
        //				name		"cn"      <-- (naming attribute name, "cn")
        //				required	true
        //
        //		the naming attribute name is dynamic since it's defined in
        //		the server's schema file, so we'll need to look it up in the
        //		server's schema at run-time
        //
        //		once we lookup the naming attribute name in the server's
        //		schema, we can get the object's name by identifying the
        //		<add-attr> element that has the naming attribute name as it's
        //		attr-name value and parsing the value from it's <value> child
        //		element
        //
        //		e.g.
        //
        //		<add-attr attr-name="cn">  <!-- naming attribute name, "cn" -->
        //			<value>John</value>  <!-- object's name, "John" -->
        //		</add-attr>
        //
        //		once we have both the naming attribute name and the object's
        //		name, we can add a the attribute to the VRTest server object
        //		in VRTest attribute format:
        //
        //		{attribute name, attribute value, attribute action}
        //		or, in this case, {"cn", "John", ADD}
        //
        //	NOTE:
        //		it is currently possible to declare the naming attribute as
        //		optional by setting it's required value to 'false' in the
        //		schema definition file or omit the naming attribute all
        //		together; since this is a bug, this driver requires the naming
        //		attribute be defined in the schema definition file and requires
        //		it for all dependent transactions
        //
        //
        //	object path:
        //	 	we can derive an object's path from the <add> element's
        //		src-dn value; unlike the naming attribute's name, the name
        //		of an object's path is constant; the object path attribute's
        //		name is defined in the String constant OBJECT_PATH_NAME;
        //
        //		an object's path is simply the src-dn of the object (from the
        //		directory side) minus the tree name and the object's name
        //
        //		e.g.
        //
        //		src-dn	== "\Tree\Organization\Jason"
        //		path 	== "\Organization\"
        //
        //		once we have the object's path, we can add the attribute
        //		{OBJECT_PATH_NAME, object path, ADD} to the object
        //
        //NOTE:
        //	we may need additional information to create an object on the
        //	server depending on whether any attributes, in addition to the
        //	naming attribute, are declared as required in the server's schema
        //	definition file; we could search the schema at run-time and find out
        //	if we have all of the required attributes, but it is simpler to just
        //	attempt to create the object and let an exception be thrown; the
        //	subscriber's create rule, when setup correctly, should prevent this
        //	from happening anyway

        //STEP 1:
        //	translate each <add-attr/> element into an equivalent set of
        //	VRTest attributes

        String dn;
        VRTestSchema schema;
        VRTestClassSchema classSchema;
        VRTestAddAttributes attributes;

        schema = api.getSchema();
        classSchema = schema.getClassSchema(add.getClassName());
        if (classSchema == null)
        {
            //	if we can't find the class schema, it's likely caused by
            //	a discrepancy between the schema mapping rule and the server's
            //	schema definition file (e.g. case-sensitivity); whatever the
            //	problem, we have to have the class schema in order
            //	translate <add-attr/> elements into VRTest attributes
            throw new VRTestException
                    (Errors.noClassSchema(add.getClassName()));
        }

        //even though the src-dn attribute is declared as optional in the dtd, we
        //need it for two reasons:
        //	(1) to derive the path of the object if the server's schema is
        //		heirarchcial
        //	(2) to place as the dn value in the return document
        dn = add.getSrcDN();
        if (dn == null)
        {
            throw new VRTestException
                    (Errors.noAttributeValue(DTD.ATTR_SRC_DN));
        }

        attributes = new VRTestAddAttributes();

        if (schema.isHierarchical())
        {
            String objectPath;

            objectPath = VRTestDNParser.ParseObjectPath(dn, VRTestDNType.EDIR);
            attributes.addAttribute(OBJECT_PATH, objectPath);
        }

        //we need the naming attribute schema to ensure the naming attribute
        //	is present among the <add-attr> elements
        VRTestAttributeSchema namingAttributeSchema;
        namingAttributeSchema = classSchema.getNamingAttributeSchema();

        ToVRTestAttributes(add.extractAddAttrElements(),
                           attributes,
                           namingAttributeSchema,
                           classSchema,
                           result);
        //STEP 2:
        //	create the object

        // if all of the rules and filters are setup correctly, we should have
        //	all of the required attributes for creating an object on the
        //	VRTest server
        VRTestObject object;
        XDSAddAssociationElement addAssociation;

        object = api.createObject(classSchema.getName(), //class
                                  attributes);           //attributes

        //STEP 3:
        //	append an <add-association/> element to the return document

        //return the association value to ds; this value needs to be stored
        //	with the object added on the ds side (i.e. the one whose src-dn
        //	was passed into this method)
        addAssociation = result.appendAddAssociationElement();
        addAssociation.setDestDN(dn);
        addAssociation.appendText(object.extractAssociationText());
    }//addHandler(XDSAddElement, XDSCommandResultDocument):void


    /**
     *	A non-interface method that translates zero or more
     *  <code>&lt;add-attr&gt;</code> elements into an
     *	equivalent set of <code>VRTestAttribute</code> objects.  One
     *  <code>&lt;add-attr&gt;</code> may translate into multiple
     *  <code>VRTestAttribute</code> objects.
     *  <p>
     *  @param addAttrElements the attributes to translate;
     *      must not be <code>null</code>
     *  @param attributes the container to hold the <code>VRTestAttribute</code>
     *      objects; must not be <code>null</code>
     *  @param namingAttributeSchema  the schema definition of the
     *      naming attribute; must not be <code>null</code>
     *  @param classSchema contains the schema definitions of attributes
     *      for this class; must not be <code>null</code>
     *  @param statusDoc the document to append warnings to;
     *      must not be <code>null</code>
     *	@throws	VRTestException if an API error occurs
     */
    private void ToVRTestAttributes(List addAttrElements,
                                    VRTestAddAttributes attributes,
                                    VRTestAttributeSchema namingAttributeSchema,
                                    VRTestClassSchema classSchema,
                                    StatusDocument statusDoc
                                    )
            throws VRTestException
    {
        //  <!-- the element being translated -->
        //
        //	<add-attr attr-name="name"/> <!-- * -->
        //
        //  <!-- * == zero or more -->

        //iterate through zero or more <add-attr> elements; add the naming
        //	attribute to the attribute container
        XDSAddAttrElement addAttr;
        VRTestAttributeSchema attributeSchema;
        String attrName;
        boolean foundNamingAttribute;
        ListIterator a;

        a = addAttrElements.listIterator();
        foundNamingAttribute = false;
        while (a.hasNext())
        {
            //parse the value of attr-name
            addAttr = (XDSAddAttrElement) a.next();
            attrName = addAttr.getAttrName();

            //lookup this classes attribute schema corresponding to the value
            //	of attr-name
            attributeSchema = classSchema.getAttributeSchema(attrName);
            if (attributeSchema == null)
            {
                XDSStatusElement status;

                //if we can't find the attribute schema, it's likely caused
                //	by a discrepancy between the schema mapping rule and the
                //	server's schema definition file (e.g. case-sensitivity);
                //	whatever the cause, we can't add this attribute without it;
                //	since this attribute may or may not be required, this could
                //	be handled by appending a warning to the return document
                //	or throwing an exception
                status = statusDoc.appendStatusElement();
                status.setLevel(StatusLevel.WARNING);
                status.appendText(Errors.noAttributeSchema(attrName));
                continue;
            }

            //check to see if this attribute is the naming attribute
            if (namingAttributeSchema.hasName(attrName))
            {
                foundNamingAttribute = true;
            }

            //add the attribute to the object's attribute container
            ToVRTestAttributes(addAttr,
                               attributes,
                               attributeSchema.getName(),
                               attributeSchema.isMultiValued());
        } //while

        //if we didn't find the <add-attr> element containing the naming
        //	attribute information, we can't create the object
        if (!foundNamingAttribute)
        {
            throw new VRTestException
                    (Errors.noAddAttr(namingAttributeSchema.getName()));
        }
    }//ToVRTestAttributes(List, VRTestAddAttributes, VRTestAttributeSchema,
    //			          VRTestClassSchema, StatusDocument):void


    /**
     *	A non-interface method that translates a single
     *  <code>&lt;add-attr&gt;</code> elements into an
     *	equivalent set of <code>VRTestAttribute</code>s.  One
     *  <code>&lt;add-attr&gt;</code> may translate into multiple
     *  <code>VRTestAttribute</code>s.
     *  <p>
     *  @param addAttr  the attribute to translate;
     *      must not be <code>null</code>
     *  @param attributes the container to hold the <code>VRTestAttribute</code>
     *      objects; must not be <code>null</code>
     *  @param attributeName the name of <code>&ltadd-attr&gt</code> in the
     *      VRTest namespace; must not be <code>null</code>
     *  @param multiValued is this attribute multi-valued?
     *	@throws	VRTestException if an API error occurs
     */
    private void ToVRTestAttributes(XDSAddAttrElement addAttr,
                                    VRTestAddAttributes attributes,
                                    String attributeName,
                                    boolean multiValued
                                    )
            throws VRTestException
    {
        //  <!-- the element being translating -->
        //
        //	<add-attr attr-name="name">
        //		<value>value</value> <!-- + -->
        //	</add-attr>
        //
        //  <!-- + == zero or more -->

        //if an <add-attr> element has multiple child <value> elements,
        //	we'll need create a VRTest attribute for each separate value
        //
        //	e.g.
        //
        //	<add-attr attr-name="Name">
        //		<value>Joe</value>
        //		<value>Jack</value>
        //	</add-attr>
        //
        //	would require the creation of two attributest with the same
        //	attribute name but different values
        //
        //	{ {"Name", "Joe",  ADD}, {"Name", "Jack", ADD} }

        //if the server's attribute is declared single-valued in the schema
        //	definition file, we'll take the first value of potentially
        //	multiple values and ignore the rest;  this isn't the only way
        //	to map multiple-valued attribute to a single-valued attribute,
        //	but it is the convention used in the native driver and probably
        //	the most straight-forward; this type of mapping is inherently
        //	problematic and is something that would be addressed on a
        //	per driver basis
        //
        //	e.g
        //
        //	 if we used the same <add-attr> element as in the above example,
        //	 we would only create one "Name" attribute with the value "Joe"
        //
        //	{"Name", "Joe", ADD}

        List valueElements;
        ListIterator v;
        XDSValueElement valueElement;
        String value;

        valueElements = addAttr.extractValueElements();
        v = valueElements.listIterator();
        while (v.hasNext())
        {
            valueElement = (XDSValueElement) v.next();
            if (unsupportedType(valueElement.getType()))
            {
                throw new VRTestException
                        ("Unsupported type:  '" + valueElement.getType() + "'.");
            }
            value = valueElement.extractText();
            attributes.addAttribute(attributeName,
                                    value);

            //only take the first of (possibly) multiple values if the
            //	server's attribute is single-valued; this determination
            //  is arbitrary
            if (!multiValued)
            {
                break;
            }
        }//while
    }//ToVRTestAttribute(XDSAddAttrElement, VRTestAddAttributes,
    //                  String, boolean):void


    /**
     *	A non-interface method that modifies an object on the VRTest server.
     *  <p>
     *	NOTE:  If the object being modified is no longer on the server,
     *	a <code>&lt;remove-association/&gt;</code> element is appended to
     *  <code>result</code>.
     *  <p>
     *  @param modify the &lt;modify> element
     *  @param result unsued; present to ensure API consistency;
     *      may be <code>null</code>
     *	@throws	VRTestException if an API error occurs
     *  @throws IOException if a communications error occurs
     */
    private void modifyHandler(XDSModifyElement modify,
                               XDSCommandResultDocument result
                               )
            throws VRTestException, IOException
    {
        //  <!-- the element being handled -->
        //
        //	<modify class-name="class">
        //		<association>assoc</association>
        //		<modify-attr attr-name="name"/> <!-- + -->
        //	</modify>
        //
        //	<!-- association is required-->
        //	<!-- class-name	is optional -->

        String association;
        String className;
        VRTestObject object;
        VRTestClassSchema classSchema;
        XDSRemoveAssociationElement remove;

        //parse the association value
        association = modify.extractAssociationText();

        //even though class-name is optional, if we have it, it'll speed-up
        //	the lookup operation that follows
        className = null;
        classSchema = api.getSchema().getClassSchema(modify.getClassName());
        if (classSchema != null)
        {
            className = classSchema.getName();
        }

        //lookup the object we're going to modify on the server using
        //	the association and class-name value (if available)
        object = api.getObjectByAssociation(association,
                                            className);
        //className is optional

        if (object == null)
        {
            //we didn't find the object...we should tell ds to remove its
            //	association value since the object went away (presumably while
            //	the driver wasn't running, or we would have published the
            //	delete notification)
            remove = result.appendRemoveAssociationElement();
            remove.appendText(association);
            return;
        }

        //translate one or more <modify-attr> elements to VRTest attributes
        VRTestModifyAttributes attributes;

        attributes = new VRTestModifyAttributes();
        ToVRTestAttributes(attributes,
                           modify,
                           object,
                           classSchema,
                           result);

        //modify the object on the server
        api.modifyObject(object, attributes);
    }//modifyHandler(XDSModifyElement, XDSCommandResultDocument):void


    /**
     *	A non-interface method that translates zero or more
     *  <code>&lt;modify-attr&gt;</code> elements into an
     *	equivalent set of <code>VRTestAttribute</code> objects. One
     *  <code>&lt;modify-attr&gt;</code> may translate into multiple
     *  <code>VRTestAttribute</code> objects.
     *  <p>
     *  @param modifyAttributes the container to hold the <code>VRTestAttribute</code>
     *      objects; must not be <code>null</code>
     *  @param modify the element containing the <code>&lt;modify-attr&gt;</code>
     *      elements; must not be <code>null</code>
     *  @param object the object being modified; must not be <code>null</code>
     *  @param classSchema contains the schema definitions of attributes
     *      for this class; must not be <code>null</code>
     *  @param statusDoc the document to append warnings to;
     *      must not be <code>null</code>
     *	@throws	VRTestException if an API error occurs
     *  @throws IOException if a communications error occurs
     */
    private static void ToVRTestAttributes(VRTestModifyAttributes modifyAttributes,
                                           XDSModifyElement modify,
                                           VRTestObject object,
                                           VRTestClassSchema classSchema,
                                           StatusDocument statusDoc
                                           )
            throws VRTestException, IOException
    {
        //this method translates one or more <modify-attr> into it's
        //	equivalent VRTest attribute representation

        //  <!-- the element were handling -->
        //
        //	<modify-attr attr-name="name"/>
        //		<add-value>value</add-value> <!-- + -->
        //		<remove-value>value</remove-value> <!-- + -->
        //		<remove-all-values/> <!-- + -->
        //	</modify-attr> <!-- + -->
        //
        //	<!-- attr-name == required -->

        ListIterator ma;
        VRTestAttributeSchema attributeSchema;
        XDSModifyAttrElement modifyAttr;
        String attrName;

        ma = modify.extractModifyAttrElements().listIterator();
        while (ma.hasNext())
        {
            //get the attributes name so we can lookup it's corresponding
            //	attribute schema on the server
            modifyAttr = (XDSModifyAttrElement) ma.next();
            attrName = modifyAttr.getAttrName();

            attributeSchema = classSchema.getAttributeSchema(attrName);
            if (attributeSchema == null)
            {
                //if we can't find the attribute schema, it's likely caused
                //	by a discrepancy between the schema mapping rule and the
                //	server's schema definition file (e.g. case-sensitivity);
                //	whatever the cause, we can't modify this attribute without
                //	it; it's absence may or may not cause the operation to
                //  fail
                XDSStatusElement status;

                status = statusDoc.appendStatusElement();
                status.setLevel(StatusLevel.WARNING);
                status.appendText(Errors.noAttributeSchema(attrName));
                continue;
            }

            //translate <add-value>, <remove-value>, or <remove-all-values> element(s)
            // into VRTest attributes
            ToVRTestAttributes(modifyAttributes,
                               modifyAttr,
                               object,
                               attributeSchema);
        }//while
    }//ToVRTestAttributes(VRTestModifyAttributes, XDSModifyElement, VRTestObject,
    //                   VRTestClassSchema, StatusDocument):void


    /**
     *	A non-interface method that translates a single
     *  <code>&lt;modify-attr&gt;</code> element into an
     *	equivalent set of <code>VRTestAttribute</code> objects.  One
     *  <code>&lt;modify-attr&gt;</code> may translate into multiple
     *  <code>VRTestAttribute</code> objects.
     *  <p>
     *  @param modifyAttr the <code>&lt;modify-attr&gt;</code> to translate;
     *      must not be <code>null</code>
     *  @param modifyAttributes the container to hold the <code>VRTestAttribute</code>
     *      objects; must not be <code>null</code>
     *  @param object the object being modified; must not be <code>null</code>
     *  @param attributeSchema the schema definition of this attribute;
     *      must not be <code>null</code>
     *	@throws	VRTestException if an API error occurs
     *  @throws IOException if a communications error occurs
     */
    private static void ToVRTestAttributes(VRTestModifyAttributes modifyAttributes,
                                           XDSModifyAttrElement modifyAttr,
                                           VRTestObject object,
                                           VRTestAttributeSchema attributeSchema
                                           )
            throws VRTestException, IOException
    {
        //  <!-- the elements being translated -->
        //
        //	<remove-all-values/> <!-- + -->                <!-- || -->
        //	<remove-value>value</remove-value> <!-- + -->  <!-- || -->
        //	<add-value>value</add-value> <!-- + --> 	   <!-- || -->
        //
        //  <!-- + == one or more-->
        //  <!-- || == or -->

        ListIterator c;
        XDSElement child;
        String attributeName;

        attributeName = attributeSchema.getName();
        c = modifyAttr.childElements().listIterator();
        while (c.hasNext())
        {
            child = (XDSElement) c.next();
            if (child instanceof XDSRemoveAllValuesElement)
            {
                ToVRTestAttributes(modifyAttributes,
                                   object,
                                   attributeName);
            }
            else if (child instanceof XDSRemoveValueElement)
            {
                ToVRTestAttributes(modifyAttributes,
                                   (XDSRemoveValueElement) child,
                                   attributeName);
            }
            else if (child instanceof XDSAddValueElement)
            {
                ToVRTestAttributes(modifyAttributes,
                                   (XDSAddValueElement) child,
                                   attributeName);
            }

            //ignore unrecognized elements

        }//while
    }//ToVRTestAttributes(VRTestModifyAttributes, XDSModifyAttrElement,
    //                   VRTestObject, VRTestAttributeSchema attributeSchema):void


    /**
     *	A non-interface method that translates a single
     *  <code>&lt;remove-all-values&gt;</code> element into an
     *	equivalent set of <code>VRTestAttribute</code> objects.  One
     *  <code>&lt;remove-all-values&gt;</code> may translate into multiple
     *  <code>VRTestAttribute</code> objects.
     *  <p>
     *  @param modifyAttributes the container to hold the <code>VRTestAttribute</code>
     *      objects; must not be <code>null</code>
     *  @param object the object being modified; must not be <code>null</code>
     *  @param attributeName the attribute's name in the VRTest namespace;
     *      must not be <code>null</code>
     *	@throws	VRTestException if an API error occurs
     *  @throws IOException if a communications error occurs
     */
    static private void ToVRTestAttributes(VRTestModifyAttributes modifyAttributes,
                                           VRTestObject object,
                                           String attributeName
                                           )
            throws VRTestException, IOException
    {
        //  <!-- the element being handled -->
        //
        //	<remove-all-values/>

        //get the attribute; there may be more than one instance of this
        //	attribute, each containing a different value; we'll have to
        //	add a delete for each instance
        //
        //	e.g.
        //
        //	{Name, "Jack"}, {Name, "Jim"}
        //
        //	would require two separate deletions
        //
        //	{Name, "Jack", DELETE}, {Name, "Jim", DELETE}

        VRTestAttributeFilter filter;
        ListIterator oa;
        VRTestAttribute objectAttribute;

        filter = new VRTestAttributeFilter();
        filter.addAttribute(attributeName, VRTestAttribute.ANY_VALUE);

        oa = object.getAttributes(filter).listIterator();
        //iterate over the object's attributes, deleting each
        while (oa.hasNext())
        {
            objectAttribute = (VRTestAttribute) oa.next();
            modifyAttributes.removeAttribute(objectAttribute.getName(),
                                             objectAttribute.getValue());
        }
    }//ToVRTestAttributes(VRTestModifyAttributes, VRTestObject object,
    //                   String):void


    /**
     *	Translates a single <code>&lt;remove-value&gt;</code> element into an
     *	equivalent set of <code>VRTestAttribute</code> objects.  One
     *  <code>&lt;remove-value&gt;</code> may translate into multiple
     *  <code>VRTestAttribute</code> objects.
     *  <p>
     *  @param attributes the container to hold the <code>VRTestAttribute</code>
     *      objects; must not be <code>null</code>
     *  @param removeValue the element containing one or more
     *      <code>&lt;value&gt;</code> elements; must not be <code>null</code>
     *  @param attributeName the attribute's name in the VRTest namesapce;
     *      must not be <code>null</code>
     *	@throws	VRTestException if an API error occurs
     */
    static private void ToVRTestAttributes(VRTestModifyAttributes attributes,
                                           XDSRemoveValueElement removeValue,
                                           String attributeName
                                           )
            throws VRTestException
    {
        //  <!-- the element being handled -->
        //
        //	<remove-value>
        //		<value>value</value> <!-- + -->
        //	</remove-value>
        //
        //  <!-- + == one or more -->

        //if there may be multiple values associated with each <remove-value/>
        //	element, we'll need to remove each value individually
        //
        //	e.g.
        //
        //	<remove-value>
        //		<value>Jack</value>
        //		<value>Jim</value>
        //	</remove-value>
        //
        //	would require the creation of two VRTest attributes
        //
        //  {"Name", "Jack", DELETE}, {"Name", "Jim",  DELETE}

        ListIterator v;
        XDSValueElement value;

        v = removeValue.extractValueElements().listIterator();
        while (v.hasNext())
        {
            value = (XDSValueElement) v.next();
            if (unsupportedType(value.getType()))
            {
                throw new VRTestException
                        ("Unsupported type '" + value.getType() + "'.");
            }
            else
            {
                attributes.removeAttribute(attributeName,
                                           value.extractText());
            }
        }//while
    }//ToVRTestAttributes(VRTestModifyAttributes, XDSRemoveValueElement,
    //                   String):void


    /**
     *	A non-interface method that deletes an object on the VRTest server.
     *  <p>
     *  @param delete the &lt;delete> element
     *  @param result the doc to append a &lt;remove-association&gt; to
     *	@throws VRTestException	if an internal API error occurs
     *  @throws IOException if a communications error occurs
     */
    private void deleteHandler(XDSDeleteElement delete,
                               XDSCommandResultDocument result
                               )
            throws VRTestException, IOException
    {
        //  <!-- the element we are handling -->
        //
        //	<delete class-name="class">
        //		<association>assoc</association>
        //	</delete>
        //
        //	<!-- association is required -->
        //	<!-- class-name is optional -->

        String association;
        String className;
        VRTestClassSchema classSchema;
        XDSRemoveAssociationElement remove;

        //parse the association value,
        association = delete.extractAssociationText();

        //the class-name value is optional; if present, we can use it to
        //	speed-up the delete operation
        className = null;
        classSchema = api.getSchema().getClassSchema(delete.getClassName());
        if (classSchema != null)
        {
            className = classSchema.getName();
        }

        api.deleteObject(association, className);

        remove = result.appendRemoveAssociationElement();
        remove.setEventID(delete.getEventID());
        remove.appendText(association);
    }//deleteHandler(XDSDeleteElement, XDSCommandResultDocument):void


    /**
     *	A non-interface method that renames an object on the VRTest server.
     *  <p>
     *	NOTE: If the object being renamed is no longer on the server,
     *	a <code>&lt;remove-association/&gt;</code> element is appended
     *  <code>result</code>.
     *  <p>
     *  @param rename the &lt;rename> 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	VRTestException if an API error occurs
     *  @throws IOException if a communications error occurs
     */
    void renameHandler(XDSRenameElement rename,
                       XDSCommandResultDocument result
                       )
            throws VRTestException, IOException
    {
        //  <!-- the element being handled -->
        //
        //	<rename class-name="class">
        //		<association>association</association>
        //		<new-name>new name</new name>
        //	</rename>

        //STEP 1:
        //	lookup the object we're going to rename so we can get it's old
        //	name value

        String className;
        String namingAttributeName;
        String oldName;
        VRTestClassSchema classSchema;
        VRTestObject object;
        XDSRemoveAssociationElement remove;

        //even though class-name is technically optional, we need it to find
        //	the naming attributes name so we can get the object's old name
        //	value
        className = rename.getClassName();
        if (className == null)
        {
            throw new VRTestException
                    (Errors.noAttributeValue(DTD.ATTR_CLASS_NAME));
        }

        object = api.getObjectByAssociation(rename.extractAssociationText(), className);
        if (object == null)
        {
            //didn't find the object...we should tell nds to remove its
            //	association value since the object was deleted (presumably
            //	while the driver wasn't running, or we would have published
            //	the delete notification)
            remove = result.appendRemoveAssociationElement();
            remove.appendText(rename.extractAssociationText());
            return;
        }//if

        oldName = object.getName();
        //we have to have the old name value
        if (oldName == null)
        {
            throw new VRTestException
                    (Errors.NO_NAME);
        }

        //STEP 2:
        //	find the name of the attribute that names the object
        //	(i.e. the naming attribute name)

        classSchema = object.getClassSchema();
        if (classSchema == null)
        {
            //	if we can't find the class schema, it's likely caused by
            //	a discrepancy between the schema mapping rule and the server's
            //	schema definition file (e.g. case-sensitivity); whatever the
            //	problem, we have to have the class schema in order to create
            //	an object on the server
            throw new VRTestException
                    (Errors.noClassSchema(className));
        }
        namingAttributeName = classSchema.getNamingAttributeSchema().getName();

        //STEP 3:
        //	remove the old name value and add the new

        VRTestModifyAttributes attributes;

        attributes = new VRTestModifyAttributes();
        //remove must come before add
        attributes.removeAttribute(namingAttributeName, oldName);
        attributes.addAttribute(namingAttributeName, rename.extractNewNameText());

        api.modifyObject(object, attributes);
    }//renameHandler(XDSRenameElement, XDSCommandResultDocument):void


    /**
     *	Moves an object on the VRTest server.
     *  <p>
     *  NOTE: If the object being moved is no longer on the server, a
     *	<code>&lt;remove-association/&gt;</code> element is appended to
     *  <code>result</code>.
     *  <p>
     *	@param move The element that contains the association of the
     *      object to move and it's new parent. Not <code>null</code>.
     *  @param result the doc to append a &lt;remove-association&gt; to;
     *      must not be <code>null</code>
     *	@throws	VRTestException if an API error occurs
     *  @throws IOException if a communications error occurs
     */
    void moveHandler(XDSMoveElement move,
                     XDSCommandResultDocument result
                     )
            throws VRTestException, IOException
    {
        //  <!--the element being handled -->
        //
        //	<move class-name="class">
        //		<association>association</association>
        //		<parent>
        //			<association>association</association>
        //		</parent>
        //	</move>

        if (!api.getSchema().isHierarchical())
        {
            //can't move object in a flat namespace
            return;
        }

        //STEP 1:
        //	lookup the object we're going to move using it's association so we
        //	can get it's old path value

        VRTestObject objectToMove;
        VRTestClassSchema classSchema;
        String className;
        String oldPath;
        XDSRemoveAssociationElement remove;

        //even though the class-name value is optional, we can use it to speed-up
        //	the lookup operation
        className = null;
        classSchema = api.getSchema().getClassSchema(move.getClassName());
        if (classSchema != null)
        {
            className = classSchema.getName();
        }

        objectToMove = api.getObjectByAssociation(move.extractAssociationText(),
                                                  className);
        if (objectToMove == null)
        {
            //didn't find the object...we should tell ds to remove its
            //	association value since the object was deleted (presumably
            //	while the driver wasn't running, or we would have published
            //	the delete notification)
            remove = result.appendRemoveAssociationElement();
            remove.appendText(move.extractAssociationText());
            //the operation is a success, since the moved object is going to be
            //	disassociated
            return;
        }

        oldPath = objectToMove.getPath();
        //we have to have the old path value
        if (oldPath == null)
        {
            throw new VRTestException
                    (Errors.NO_PATH);
        }

        //STEP 2:
        //	derive the new path using the parent object's dn

        VRTestRootObject root;
        XDSParentElement parent;
        String newPath;
        String association;

        root = api.getRootObject();
        parent = move.extractParentElement();
        association = parent.extractAssociationText();

        //is the parent root?
        if (root.hasAssociation(association))
        {
            newPath = root.getChildPath();
        }
        else
        {
            //lookup the parent object on the server
            VRTestObject parentObject;
            String dn;

            //don't know class name
            parentObject = api.getObjectByAssociation(association,
                                                      null);  //class name
            if (parentObject == null)
            {
                //didn't find the object...we should tell nds to remove its
                //	association value since it was deleted (presumably
                //	while the shim wasn't running, or we would have published
                //	the delete notification)
                remove = result.appendRemoveAssociationElement();
                remove.appendText(association);
                //the operation has failed since we can't move the object, but it
                //	is still associated
                throw new VRTestException
                        (Errors.NO_PARENT);
            }

            dn = parentObject.getDN();
            if (dn == null)
            {
                throw new VRTestException(Errors.NO_DN);
            }

            newPath = dn + PATH_DELIMITER_STRING;
        }

        //STEP 3:
        //	remove the old path value and add the new

        VRTestModifyAttributes attributes;

        //remove must come before add
        attributes = new VRTestModifyAttributes();
        attributes.removeAttribute(OBJECT_PATH, oldPath);
        attributes.addAttribute(OBJECT_PATH, newPath);

        api.modifyObject(objectToMove, attributes);
    }//moveHandler(XDSMoveElement, XDSCommandResultDocument):void


    /**
     *  A non-interface method that appends driver identification information
     *  to <code>result</code> in response to an identity query.
     *  <p>
     *  @param result may be <code>null</code>
     */
    private void appendDriverIdentityInfo(XDSCommandResultDocument result)
    {
        //  <!-- the element we're constructing -->
        //
        //  <instance class-name="__driver_identification_class__">
        //      <attr attr-name="driver-id">
        //          <value type="string">VRTEST</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>

        if (result != null)
        {
            //when appending static XML, it's simplest to use the XDSUtil class

            String driverID;

            //library version and skeleton version are synonymous
            driverID = "<instance class-name=\"__driver_identification_class__\">" +
                    "<attr attr-name=\"driver-id\">" +
                    "<value type=\"string\">" + DRIVER_ID_VALUE + "</value>" +
                    "</attr>" +
                    "<attr attr-name=\"driver-version\">" +
                    "<value type=\"string\">" + VERSION + "</value>" +
                    "</attr>" +
                    "<attr attr-name=\"min-activation-version\">" +
                    "<value type=\"string\">" + DRIVER_MIN_ACTIVATION_VERSION + "</value>" +
                    "</attr>" +
                    "</instance>";

            //append this XML to the <output> element
            XDSUtil.appendXML(result.domIOElement(), driverID);
        }//if
    }//appendDriverIdentityInfo(XDSQueryElement, XDSCommandResultDocument):void


    /**
     *  Frees subscriber resources.
     *  <p>
     *  @see VRTestDriverShim#shutdown(XmlDocument)
     */
    void shutdown()
    {
        disconnect();
    }//shutdown():void

}//class VRTestSubscriptionShim

