/**
 * 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.*;
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.io.IOException;

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


/**
 * A basic skeleton for implementing the <code>PublicationShim</code>.
 *
 * <p>
 * The <code>PublicationShim</code> is an interface used by the DirXML engine
 * to start and stop an application driver's publication process.
 * </p>
 *
 * <p>
 * A <code>PublicationShim</code> will almost always also implement
 * <code>XmlQueryProcessor</code> but it could also delegate it to another
 * object.
 * </p>
 *
 * <p>
 * NOTE:  the publisher init() and start() methods are called on a thread
 * separate from the thread used for calling the DriverShim and
 * SubscriptionShim methods
 * </p>
 */
public class SkeletonPublicationShim
    extends CommonImpl
    implements PublicationShim, XmlQueryProcessor
{
    //~ Static fields/initializers ---------------------------------------------

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

    /** The number of Publisher parameters. */
    private static final int NO_OF_PARAMS = 3;

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

    /** The tag name of the first Publisher parameter. */
    private static final String TAG_PUB_1 = "pub-1";

    /** The tag name of the polling interval parameter */
    private static final String TAG_POLLING_INTERVAL = "polling-interval";

    /** The tag name of the heartbeat interval parameter. */
    private static final String TAG_HEARTBEAT_INTERVAL = "pub-heartbeat-interval";

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

    /** The default value of the first Publisher parameter. */
    private static final String DEFAULT_PUB_1 = "String for Publisher";

    /** The default polling interval value. */
    private static final String DEFAULT_POLLING_INTERVAL = "10";//seconds

    /** The default heartbeat interval parameter. */
    private static final String DEFAULT_HEARTBEAT_INTERVAL = "0";//minutes (disabled)

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

    /**
     * Variable used to determine when to return from <code>start()</code>.
     *
     * <p></p>
     *
     * @see #start(XmlCommandProcessor)
     */
    private boolean shutdown;

    /**
     * Variable used to control how often the thread in <code>start()</code>
     * wakes up to poll the application. Value is in milliseconds.
     *
     * <p></p>
     *
     * @see #start(XmlCommandProcessor)
     */
    private long pollingInterval;

    /**
     * Variable used to control how often the thread in <code>start()</code>
     * wakes up to send a document to the DirXML engine. Value is in
     * milliseconds.
     *
     * <p></p>
     *
     * @see #start(XmlCommandProcessor)
     */
    private long heartbeatInterval;

    /**
     * Should a heartbeat document be sent?
     *
     * <p></p>
     *
     * @see #start(XmlCommandProcessor)
     */
    private boolean doHeartbeat;

    /**
     * The smaller of the polling interval or heartbeat interval > 0.
     *
     * <p></p>
     *
     * @see #start(XmlCommandProcessor)
     */
    private long interval;

    /**
     * Object used as a semaphore so subscriber-channel thread can wake
     * publisher thread up to tell it to shutdown.
     *
     * <p></p>
     *
     * @see #start(XmlCommandProcessor)
     */
    private Object semaphore;

    /**
     * Used to filter application events before sending them to the DirXML
     * engine. Events are already filtered on the subscriber channel before
     * they reach the driver so we don't have to worry about filtering
     * subscription events.
     *
     * <p></p>
     *
     * @see #start(XmlCommandProcessor)
     */
    private DriverFilter filter;

    /**
     * Container for publisher parameters from the document passed to
     * <code>init(XmlDocument)</code>.
     *
     * <p></p>
     *
     * @see #init(XmlDocument)
     * @see #setPubParams()
     */
    private Map pubParams;

    /**
     * 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>
     */
    SkeletonPublicationShim(SkeletonDriverShim someDriver)
    {
        driver = someDriver;
        setDriverRDN(driver.getDriverRDN());
        setTrace();
        setDriverParams(driver.getDriverParams());
        setPubParams();
        shutdown            = false;
        pollingInterval     = -1;
        semaphore           = new short[0];//smallest object
        filter              = null;
        doHeartbeat         = false;
    }//SkeletonPublicationShim(SkeletonDriverShim)

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

    /**
     * A non-interface method that describes the parameters this
     * PublicationShim is expecting.
     *
     * <p></p>
     *
     * @see SkeletonPublicationShim#init(XmlDocument)
     */
    private void setPubParams()
    {
        //MODIFY:  construct parameter descriptors here for your
        //  publisher parameters
        //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;

        pubParams     = new HashMap(NO_OF_PARAMS);

        param = new Parameter(
                TAG_PUB_1,//tag name
                DEFAULT_PUB_1,//default value (optional)
                DataType.STRING);//data type
        pubParams.put(
            param.tagName(),
            param);

        param = new Parameter(
                TAG_POLLING_INTERVAL,
                DEFAULT_POLLING_INTERVAL,
                DataType.INT);
        param.add(RangeConstraint.POSITIVE);//ensures > 0
        pubParams.put(
            param.tagName(),
            param);

        param = new Parameter(
                TAG_HEARTBEAT_INTERVAL,
                DEFAULT_HEARTBEAT_INTERVAL,
                DataType.INT);
        param.add(RangeConstraint.NON_NEGATIVE);//ensures >= 0
        pubParams.put(
            param.tagName(),
            param);
    }//setPubParams():void

    /**
     * <code>init</code> will be called before the invocation of
     * <code>start</code>.
     *
     * <p>
     * In general, application connectivity should be handled in
     * <code>start(XmlCommandProcessor)</code> so a driver can start when the
     * application is down.
     * </p>
     *
     * <p></p>
     *
     * @param initXML XML document that contains the publisher initialization
     *        parameters and state
     *
     * @return an XML document containing status messages for this operation
     */
    public XmlDocument init(XmlDocument initXML)
    {
        //MODIFY:  initialize your publisher 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)\Publisher">
                       <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="Given Name"/>
                           </allow-class>
                       </driver-filter>
                       <publisher-options>
                   <pub-1 display-name="Sample Publisher option">String for Publisher</pub-1>
                   <polling-interval display-name="Polling interval in seconds">10</polling-interval>
               </publisher-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>
                           <polling-interval display-name="Polling interval in seconds">10</polling-interval>
                           <pub-1 display-name="Sample Publisher option">String for Publisher</pub-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        param;

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

            //get any publisher options from init doc
            init.parameters(pubParams);

            //get the polling interval that may have been passed in
            param     = (Parameter)pubParams.get(TAG_POLLING_INTERVAL);

            //our pollingInterval value is in seconds, Object.wait(long)
            //  takes milliseconds
            pollingInterval     = XDSUtil.toMillis(
                    param.toInteger().intValue());

            //get the polling interval that may have been passed in
            param     = (Parameter)pubParams.get(TAG_HEARTBEAT_INTERVAL);

            //our heartbeatInterval value is in minutes, Object.wait(long)
            //  takes milliseconds
            heartbeatInterval     = XDSUtil.toMillis(
                    param.toInteger().intValue() * 60);
            doHeartbeat     = (heartbeatInterval > 0);
            interval        = (doHeartbeat)
                ? Math.min(
                    pollingInterval,
                    heartbeatInterval)
                : pollingInterval;

            //construct a driver filter for the publication shim to use for
            //filtering application events in start(); in an actual driver,
            //the publisher would use the filter to filter events from the
            //application to avoid publishing unnecessary events to the DirXML
            //engine
            //
            //NOTE: the skeleton publisher doesn't actually make use of the
            //filter, but this code is here to illustrate how to get the
            //publisher filter from the init document
            filter     = init.driverFilter();

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

            //append the parameter values the publisher is actually using
            status.parametersAppend(pubParams);
        }//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

    /**
     * <code>start()</code> starts the <code>PublicationShim</code>.  The
     * publisher shim should not return from start until DriverShim.shutdown()
     * is called, or a fatal error occurs. Returning prematurely from
     * <code>start()</code> will cause the DirXML engine to shut down the
     * driver.
     *
     * <p></p>
     *
     * @param processor <code>XmlCommandProcessor</code> that can invoked in
     *        order to publish information to eDirectory on behalf of the
     *        application; processor must only be invoked from the thread on
     *        which <code>start()</code> was invoked
     *
     * @return XML document containing status from start operation
     *
     * @see SkeletonDriverShim#shutdown(XmlDocument)
     */
    public XmlDocument start(XmlCommandProcessor processor)
    {
        //MODIFY:  implement your publisher here
        //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"/>
               </output>
           </nds>
         */

        //NOTE: this implements a polling method of communication with the
        //  application; this may not be appropriate if the application
        //  supports an event notification system
        trace.trace(
            "start",
            1);

        //create result document for reporting status to DirXML engine
        XDSResultDocument result;
        XmlDocument       heartbeat;
        StatusAttributes  shutdownAttrs;
        long              lastBeatTime;
        long              lastPollTime;
        long              elapsedTime;
        long              sleep;
        boolean           published;

        result        = newResultDoc();
        heartbeat     = new XDSHeartbeatDocument().toXML();

        //assume we'll shutdown normally
        shutdownAttrs     = StatusAttributes.factory(
                StatusLevel.SUCCESS,
                StatusType.DRIVER_STATUS,
                null);

        //ensure we poll first time into the loop
        lastPollTime     = pollingInterval;
        lastBeatTime     = heartbeatInterval;

        try
        {
            //loop until we're told to shutdown (or some fatal error occurs)
            while(!shutdown)
            {
                //skeleton implementation just wakes up every so often to
                //  see if it needs to poll, issue a heartbeat, or
                //  shutdown and return
                try
                {
                    published = false;

                    if(lastPollTime >= pollingInterval)
                    {
                        //try and connect with our mythical app if we aren't already
                        //  connected
                        connect();
                        published        = poll(processor);
                        lastPollTime     = 0;
                    }//if

                    if(published)
                    {
                        //sending any document equates to a heartbeat
                        lastBeatTime = 0;
                    }
                    else if(lastBeatTime >= heartbeatInterval)
                    {
                        lastBeatTime = 0;

                        if(doHeartbeat)
                        {
                            trace.trace(
                                "sending heartbeat",
                                2);
                            processor.execute(
                                heartbeat,
                                this);
                        }
                    }//if

                    //how long do we need to sleep for to poll again or
                    //  issue a heartbeat?
                    sleep = Math.min(
                            pollingInterval - lastPollTime,
                            heartbeatInterval - lastBeatTime);

                    //0 == sleep forever
                    if(sleep == 0)
                    {
                        sleep = interval;
                    }

                    //wait for subscriber channel thread to wake us up, for
                    //  polling interval to expire, or heartbeat interval to
                    //  expire
                    //
                    //NOTE: the use of the semaphore is highly recommended. It
                    //  prevents a long polling interval from interfering with the
                    //  orderly shutdown of the driver.
                    synchronized(semaphore)
                    {
                        long start = System.currentTimeMillis();

                        trace.trace(
                            "sleeping for " + (sleep / 1000) + " seconds",
                            2);
                        semaphore.wait(sleep);
                        elapsedTime = (System.currentTimeMillis() - start);
                        lastPollTime += elapsedTime;
                        lastBeatTime += elapsedTime;
                    }
                }//try
                catch(IOException io)
                {
                    //there's a connectivity problem; try to reconnect
                    disconnect();
                    connect();
                }
                catch(InterruptedException ie)
                {
                    //we've been woken by the subscriber thread;
                    //  it's time to shutdown
                }
            }//while

            //append a successful <status> element to the result doc
            XDSUtil.appendStatus(
                result,//doc to append to
                shutdownAttrs,//status attribute values
                null);//description
        }//try
        catch(Exception e)//don't want to catch Error class with Throwable
        {
            //something bad happened...
            shutdownAttrs.setLevel(StatusLevel.FATAL);
            XDSUtil.appendStatus(
                result,//doc to append to
                shutdownAttrs,//status attribute values
                null,//description
                e,//exception
                true,//append stack trace?
                null);//xml to append
        }
        finally
        {
            trace.trace(
                "stopping...",
                2);
            disconnect();
        }

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

    /**
     * A non-interface method called from
     * <code>SkeletonPublicationShim.start(XmlCommandProcessor)</code> to poll
     * the application for events.
     *
     * <p></p>
     *
     * @see SkeletonPublicationShim#start(XmlCommandProcessor)
     */
    private boolean poll(XmlCommandProcessor processor)
    {
        trace.trace(
            "poll",
            1);

        //in a real driver, we'd do whatever was necessary to ask the
        //  application what changed and build a command document to
        //  publish the changes to DirXML
        //simulate random events; the probability is relatively low so
        //  the heartbeat will be sent sometimes
        if(Math.random() > .35)
        {
            trace.trace(
                "nothing to do",
                2);
            return false;
        }

        //example command 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>
               <input>
                   <status event-id="0" level="warning" type="driver-general">
                       <description>Publisher not implemented.</description>
                   </status>
               </input>
           </nds>
         */

        //example result document:

        /*
           <nds dtdversion="1.1" ndsversion="8.6">
               <source>
                   <product version="1.1a">DirXML</product>
                   <contact>Novell, Inc.</contact>
               </source>
               <output>
                   <status event-id="0" level="success"></status>
               </output>
           </nds>
         */
        XDSCommandDocument       command;
        XDSCommandResultDocument response;
        StatusLevel              level;
        StatusAttributes         pollAttrs;
        String                   eventID;

        eventID       = "0";
        response      = null;
        command       = newCommandDoc();
        pollAttrs     = StatusAttributes.factory(
                StatusLevel.WARNING,
                StatusType.DRIVER_GENERAL,
                eventID);//event id
        XDSUtil.appendStatus(
            command,//doc to append to
            pollAttrs,//status attribute values
            Errors.NO_PUB);//description

        if(XDS.DEBUG)
        {
            //to assist in debugging your driver, the XDS library
            //  allows you to validate XDS documents before you
            //  send them to the DirXML engine; this only makes
            //  sense if your document is fully constituted at
            //  this point and doesn't require style sheet
            //  intervention to make it valid
            try
            {
                command.validate();
            }
            catch(XDSParseException xds)
            {
                //something isn't right...
                pollAttrs = StatusAttributes.factory(
                        StatusLevel.WARNING,
                        StatusType.DRIVER_GENERAL,
                        null);
                XDSUtil.appendStatus(
                    command,//doc to append to
                    pollAttrs,
                    Errors.INVALID_DOC,//description
                    xds,//exception
                    false,//append stack trace?
                    null);//xml to append
            }//catch
        }//if

        response = null;

        try
        {
            response     = new XDSCommandResultDocument(
                    processor.execute(
                        command.toXML(),
                        this));

            //check command results
            level = response.mostSevereStatusLevel(eventID);
            trace.trace(
                "start: status == " + toLiteral(level.toString()),
                3);
        }
        catch(XDSParseException xds)
        {
            //the doc we got back is malformed or invalid due to
            //  style sheet processing
            command       = newCommandDoc();
            pollAttrs     = StatusAttributes.factory(
                    StatusLevel.ERROR,
                    StatusType.DRIVER_GENERAL,
                    null);//event-id
            XDSUtil.appendStatus(
                command,//doc to append to
                pollAttrs,//status attribute values
                Errors.INVALID_DOC,//description
                xds,//exception
                false,//append stack trace?
                response.toXML());//xml to append
            processor.execute(
                command.toXML(),
                this);
        }//catch

        return true;
    }//poll(XmlCommandProcessor):boolean

    /**
     * A non-interface method called from
     * <code>SkeletonDriverShim.shutdown()</code> to signal the publisher
     * thread that it needs to exit from <code>start()</code>.
     *
     * <p>
     * NOTE: This method is called by a thread other than the publisher thread.
     * </p>
     *
     * <p></p>
     *
     * @see SkeletonDriverShim#shutdown(XmlDocument)
     */
    void shutdown()
    {
        //MODIFY:  put your shutdown code here
        trace.trace(
            "shutdown",
            1);

        //tell publisher thread it's time to exit
        shutdown = true;

        //tell the publisher thread to wake up, if it happens to be sleeping
        synchronized(semaphore)
        {
            semaphore.notifyAll();
        }
    }//shutdown():void

    /**
     * <code>query</code> will accept an XDS-encoded query and return the
     * results.
     *
     * <p></p>
     *
     * @param queryXML a document containing an XDS-encoded query
     *
     * @return the results of the query
     */
    public XmlDocument query(XmlDocument queryXML)
    {
        trace.trace(
            "query",
            1);

        XDSQueryResultDocument result;
        StatusAttributes       attrs;
        String                 eventID;

        result = new XDSQueryResultDocument();
        appendSourceInfo(result);
        eventID = null;

        try
        {
            XDSQueryDocument queryDoc;
            XDSQueryElement  query;
            ListIterator     q;

            queryDoc     = new XDSQueryDocument(queryXML);
            q            = queryDoc.extractQueryElements().listIterator();

            while(q.hasNext())
            {
                query       = (XDSQueryElement)q.next();
                eventID     = query.getEventID();
                queryHandler(
                    query,
                    result);
                attrs = StatusAttributes.factory(
                        StatusLevel.SUCCESS,
                        StatusType.DRIVER_GENERAL,
                        eventID);
                XDSUtil.appendStatus(
                    result,//doc to append to
                    attrs,//status attribute values
                    null);//description
            }//while
        }//try
        catch(XDSException xds)
        {
            //query doc is malformed or invalid
            attrs = StatusAttributes.factory(
                    StatusLevel.ERROR,
                    StatusType.DRIVER_GENERAL,
                    null);//event-id
            XDSUtil.appendStatus(
                result,//doc to append to
                attrs,//status attribute values
                null,//description
                xds,//exception
                false,//append stack trace?
                queryXML);//xml to append
        }//try
        catch(Exception e)//don't want to catch Error class with Throwable
        {
            //the DirXML engine ignores "retry" and "fatal" status
            //  messages from this call; everything is basically treated
            //  as an "error."
            attrs = StatusAttributes.factory(
                    StatusLevel.ERROR,
                    StatusType.DRIVER_GENERAL,
                    null);//event-id
            XDSUtil.appendStatus(
                result,//doc to append to
                attrs,//status attribute values
                null,//description
                e,//exception
                true,//append stack trace?
                queryXML);//xml to append
        }//catch

        //return result doc w/ status and query results
        //  to the DirXML engine
        return result.toXML();
    }//query(XmlDocument):XmlDocument
}//class SkeletonPublicationShim
