org.yawlfoundation.yawl.engine.YWorkItem.java Source code

Java tutorial

Introduction

Here is the source code for org.yawlfoundation.yawl.engine.YWorkItem.java

Source

/*
 * Copyright (c) 2004-2012 The YAWL Foundation. All rights reserved.
 * The YAWL Foundation is a collaboration of individuals and
 * organisations who are committed to improving workflow technology.
 *
 * This file is part of YAWL. YAWL is free software: you can
 * redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation.
 *
 * YAWL is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
 * Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with YAWL. If not, see <http://www.gnu.org/licenses/>.
 */

package org.yawlfoundation.yawl.engine;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdom2.Document;
import org.jdom2.Element;
import org.yawlfoundation.yawl.authentication.YClient;
import org.yawlfoundation.yawl.elements.*;
import org.yawlfoundation.yawl.elements.data.YParameter;
import org.yawlfoundation.yawl.elements.state.YIdentifier;
import org.yawlfoundation.yawl.engine.time.YTimer;
import org.yawlfoundation.yawl.engine.time.YWorkItemTimer;
import org.yawlfoundation.yawl.exceptions.YPersistenceException;
import org.yawlfoundation.yawl.logging.*;
import org.yawlfoundation.yawl.util.JDOMUtil;
import org.yawlfoundation.yawl.util.StringUtil;

import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

import static org.yawlfoundation.yawl.engine.YWorkItemStatus.*;

/**
 * 
 * @author Lachlan Aldred
 * Date: 28/05/2003
 * Time: 15:29:33
 *
 * Refactored for v2.0 by Michael Adams 10/2007 - 12/2009
 * 
 */
public class YWorkItem {

    private static DateFormat _df = new SimpleDateFormat("MMM:dd, yyyy H:mm:ss");
    private static YEngine _engine = YEngine.getInstance();
    private YWorkItemRepository _workItemRepository = _engine.getWorkItemRepository();
    private YWorkItemID _workItemID;
    private String _thisID = null;
    private YSpecificationID _specID;
    private YTask _task; // task item is derived from
    private Date _enablementTime;
    private Date _firingTime;
    private Date _startTime;

    private YAttributeMap _attributes; // decomposition attributes

    private YWorkItemStatus _status;
    private YWorkItemStatus _prevStatus = null; // this item's next to last status
    private YClient _externalClient; // the 'started by' service/app
    private String _externalClientStr; // for persistence
    private boolean _allowsDynamicCreation;
    private boolean _requiresManualResourcing;
    private YWorkItem _parent; // this item's parent (if any)
    private Set<YWorkItem> _children; // this item's kids (if any)
    private Element _dataList;
    private String _dataString = null; // persisted version of datalist

    private String _deferredChoiceGroupID = null;

    private YTimerParameters _timerParameters; // timer extensions
    private boolean _timerStarted;
    private long _timerExpiry = 0; // set to expiry when timer starts

    private URL _customFormURL;
    private String _codelet;
    private String _documentation;
    private String _externalLogPredicate; // set by services on checkin

    private final YEventLogger _eventLog = YEventLogger.getInstance();
    private final Logger _log = LogManager.getLogger(YWorkItem.class);

    // CONSTRUCTORS //

    public YWorkItem() {
    } // required for persistence

    /** Creates an enabled WorkItem */
    public YWorkItem(YPersistenceManager pmgr, YSpecificationID specID, YTask task, YWorkItemID workItemID,
            boolean allowsDynamicCreation, boolean isDeadlocked) throws YPersistenceException {

        _log.debug("Spec={} WorkItem={}", specID, workItemID.getTaskID());

        createWorkItem(specID, workItemID, isDeadlocked ? statusDeadlocked : statusEnabled, allowsDynamicCreation);

        _task = task;
        if (task != null)
            _documentation = task.getDocumentationPreParsed();
        _enablementTime = new Date();
        _eventLog.logWorkItemEvent(pmgr, this, _status, null);
        if ((pmgr != null) && (!isDeadlocked))
            pmgr.storeObject(this);
    }

    /** Creates a fired WorkItem */
    private YWorkItem(YPersistenceManager pmgr, YWorkItemID workItemID, YSpecificationID specID,
            Date workItemCreationTime, YWorkItem parent, boolean allowsDynamicInstanceCreation)
            throws YPersistenceException {

        _log.debug("Spec={} WorkItem={}", specID, workItemID.getTaskID());

        createWorkItem(specID, workItemID, statusFired, allowsDynamicInstanceCreation);

        _enablementTime = workItemCreationTime;
        _firingTime = new Date();
        _parent = parent;
        _eventLog.logWorkItemEvent(pmgr, this, _status, createLogDataList("fired"));
        if (pmgr != null)
            pmgr.storeObject(this);
    }

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

    // PRIVATE METHODS //

    /** Called from constructors to set some mutual members */
    private void createWorkItem(YSpecificationID specificationID, YWorkItemID workItemID, YWorkItemStatus status,
            boolean allowsDynamicInstanceCreation) throws YPersistenceException {
        _workItemID = workItemID;
        addToRepository();
        set_thisID(_workItemID.toString() + "!" + _workItemID.getUniqueID());
        _specID = specificationID;
        _allowsDynamicCreation = allowsDynamicInstanceCreation;
        _status = status;
    }

    /** completes persisting and event logging for a workitem */
    private void completePersistence(YPersistenceManager pmgr, YWorkItemStatus completionStatus)
            throws YPersistenceException {

        // make sure we can complete this workitem
        if (!(_status.equals(statusExecuting) || _status.equals(statusSuspended))) {
            throw new RuntimeException(this + " [when current status is \"" + _status
                    + "\" it cannot be moved to \"" + completionStatus + "\"]");
        }

        // set final status, log event and remove from persistence
        set_status(null, completionStatus); // don't persist status update
        logAndUnpersist(pmgr, this);
        completeParentPersistence(pmgr);
    }

    /** completes persisting and event logging for a parent workitem if required */
    private void completeParentPersistence(YPersistenceManager pmgr) throws YPersistenceException {

        synchronized (_parent) { // sequentially handle children

            // if all siblings are completed, then the parent is completed too
            boolean parentComplete = true;

            if (_parent.getChildren().size() > 1) { // short-circuit if not multi-task
                for (YWorkItem mysibling : _parent.getChildren()) {
                    if (mysibling.hasUnfinishedStatus()) {
                        parentComplete = false;
                        break;
                    }
                }
            }

            if (parentComplete)
                logAndUnpersist(pmgr, _parent);
        }
    }

    private void logAndUnpersist(YPersistenceManager pmgr, YWorkItem item) throws YPersistenceException {
        _eventLog.logWorkItemEvent(pmgr, item, _status, createLogDataList(_status.name()));
        if (pmgr != null)
            pmgr.deleteObject(item);
    }

    /**
     * Finds the net-level param specified, then deconstructs its data to simple
     * timer parameters. The data in the net-level param is a complex type YTimerType
     * consisting of two elements: 'trigger' (either 'OnEnabled' or 'OnExecuting'), and
     * 'expiry': a string that may represent a duration type, a dateTime type, or a long
     * value to be converted to a Date.
     * @param param the name of the YTimerType parameter
     * @param data the case or net-level data object
     * @return true if the param is successfully unpacked.
     */
    private boolean unpackTimerParams(String param, YNetData data) {
        if (data == null)
            data = _engine.getCaseData(_workItemID.getCaseID());
        if (data == null)
            return false; // couldn't get case data

        Element eData = JDOMUtil.stringToElement(data.getData());
        Element timerParams = eData.getChild(param);
        if (timerParams == null)
            return false; // no var with param's name

        try {
            return _timerParameters.parseYTimerType(timerParams);
        } catch (IllegalArgumentException iae) {
            _log.warn("Unable to set timer for workitem '" + getIDString() + "' - " + iae.getMessage());
            return false;

        }
    }

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

    // MISC METHODS //

    public void addToRepository() {
        _workItemRepository.add(this);
        _engine.getInstanceCache().addWorkItem(this);
    }

    public YWorkItem createChild(YPersistenceManager pmgr, YIdentifier childCaseID) throws YPersistenceException {
        if (this._parent == null) {

            // don't proceed if child caseid is invalid
            YIdentifier parentCaseID = getWorkItemID().getCaseID();
            if ((childCaseID == null) || (childCaseID.getParent() == null)
                    || (!childCaseID.getParent().equals(parentCaseID)))
                return null;

            set_status(pmgr, statusIsParent);

            // if this parent has no children yet, create the set and log it
            if (_children == null) {
                _children = new HashSet<YWorkItem>();
                _eventLog.logWorkItemEvent(pmgr, this, _status, createLogDataList("createChild"));
            }

            YWorkItem childItem = new YWorkItem(pmgr, new YWorkItemID(childCaseID, getWorkItemID().getTaskID()),
                    _specID, getEnablementTime(), this, _allowsDynamicCreation);

            // map relevant (genetic, perhaps?) attributes to child
            childItem.setTask(getTask());
            childItem.setRequiresManualResourcing(requiresManualResourcing());
            childItem.setAttributes(getAttributes());
            childItem.setTimerParameters(getTimerParameters());
            childItem.setCustomFormURL(getCustomFormURL());
            childItem.setCodelet(getCodelet());

            _children.add(childItem);
            if (pmgr != null)
                pmgr.updateObject(this);
            return childItem;
        }
        return null;
    }

    // set by custom service on checkin, and immediately before workitem completes 
    public void setExternalLogPredicate(String predicate) {
        _externalLogPredicate = predicate;
    }

    /** write data input values to event log */
    public void setData(YPersistenceManager pmgr, Element data) throws YPersistenceException {
        _dataList = data;
        _dataString = getDataString();

        if (pmgr != null)
            pmgr.updateObject(this);

        YLogDataItemList logData = assembleLogDataItemList(data, true);
        _eventLog.logDataEvent(pmgr, this, "DataValueChange", logData);
    }

    /** write output data values to event log */
    public void completeData(YPersistenceManager pmgr, Document output) {
        YLogDataItemList logData = assembleLogDataItemList(output.getRootElement(), false);
        _eventLog.logDataEvent(pmgr, this, "DataValueChange", logData);
    }

    private YLogDataItemList assembleLogDataItemList(Element data, boolean input) {
        YLogDataItemList result = new YLogDataItemList();
        if (data != null) {
            Map<String, YParameter> params = _engine.getParameters(_specID, getTaskID(), input);
            String descriptor = (input ? "Input" : "Output") + "VarAssignment";
            for (Element child : data.getChildren()) {
                String name = child.getName();
                String value = child.getValue();
                YParameter param = params.get(name);
                if (param != null) {
                    String dataType = param.getDataTypeNameUnprefixed();

                    // if a complex type, store the structure with the value
                    if (child.getContentSize() > 1) {
                        value = JDOMUtil.elementToString(child);
                    }
                    result.add(new YLogDataItem(descriptor, name, value, dataType));

                    // add any configurable logging predicates for this parameter
                    YLogDataItem dataItem = getDataLogPredicate(param, input);
                    if (dataItem != null)
                        result.add(dataItem);
                }
            }
        }
        return result;
    }

    public void restoreDataToNet(Set<YAWLServiceReference> services) throws YPersistenceException {
        if (getDataString() != null) {
            YNet net;
            try {
                net = _engine.getNetRunner(getCaseID().getParent()).getNet();
            } catch (Exception e) {
                return;
            }
            YAtomicTask task = (YAtomicTask) net.getNetElement(getTaskID());
            if (task != null) {
                try {
                    task.prepareDataForInstanceStarting(getCaseID());
                    net.addNetElement(task);
                } catch (Exception e) {
                    throw new YPersistenceException(e);
                }
            }
            if (_externalClientStr != null) {
                if (_externalClientStr.equals("DefaultWorklist")) {
                    _externalClient = _engine.getDefaultWorklist();
                } else {
                    for (YAWLServiceReference service : services) {
                        if (service.getServiceName().equals(_externalClientStr)) {
                            _externalClient = service;
                            break;
                        }
                    }
                }
            }
        }
    }

    /** removes workitems from persistence when cancelled **/
    public void cancel(YPersistenceManager pmgr) throws YPersistenceException {
        if (pmgr != null) {

            //remove the children first
            Set<YWorkItem> children = getChildren();
            if (children != null) {
                for (YWorkItem child : children) {
                    deleteWorkItem(pmgr, child);
                }
            }

            deleteWorkItem(pmgr, this);
        }
    }

    private void deleteWorkItem(YPersistenceManager pmgr, YWorkItem item) throws YPersistenceException {
        pmgr.deleteObject(item);
        _eventLog.logWorkItemEvent(pmgr, item, YWorkItemStatus.statusDeleted,
                createLogDataList(YWorkItemStatus.statusDeleted.name()));
        _engine.getAnnouncer().announceCancelledWorkItem(item);
    }

    public void checkStartTimer(YPersistenceManager pmgr, YNetData data) throws YPersistenceException {

        if (_timerParameters != null) {

            // get values from net-level var if necessary
            String netParam = _timerParameters.getVariableName();
            if (netParam != null) {
                if (!unpackTimerParams(netParam, data))
                    return;
            }

            // if current workitem status equals trigger status, start the timer
            if (_timerParameters.statusMatchesTrigger(_status)) {
                YWorkItemTimer timer = null;
                switch (_timerParameters.getTimerType()) {
                case Expiry: {
                    timer = new YWorkItemTimer(_workItemID.toString(), _timerParameters.getDate(), (pmgr != null));
                    break;
                }
                case Duration: {
                    timer = new YWorkItemTimer(_workItemID.toString(), _timerParameters.getDuration(),
                            (pmgr != null));
                    break;
                }
                case Interval: {
                    timer = new YWorkItemTimer(_workItemID.toString(), _timerParameters.getTicks(),
                            _timerParameters.getTimeUnit(), (pmgr != null));
                }
                }
                if (timer != null) {
                    _timerExpiry = timer.getEndTime();
                    setTimerActive();
                    _timerStarted = true;
                    if (pmgr != null)
                        pmgr.storeObject(timer);
                }
            }
        }
    }

    public void cancelTimer() {
        if (hasTimerStarted()) {
            YTimer.getInstance().cancelTimerTask(getIDString());
        }
        YWorkItem parent = getParent();
        if (parent != null && parent.hasTimerStarted()) {
            Set<YWorkItem> children = parent.getChildren();
            if (children != null) {
                for (YWorkItem child : children) {
                    if (!(child.equals(this) || child.hasFinishedStatus())) {
                        return; // parent still has active child
                    }
                }
            }
            YTimer.getInstance().cancelTimerTask(parent.getIDString());
        }
    }

    private void setTimerActive() {
        _engine.getNetRunner(this).updateTimerState(_task, YWorkItemTimer.State.active);
    }

    /** @return true if workitem is 'live' */
    public boolean hasLiveStatus() {
        return _status.equals(statusFired) || _status.equals(statusEnabled) || _status.equals(statusExecuting);
    }

    /** @return true if workitem is finished */
    public boolean hasFinishedStatus() {
        return hasCompletedStatus() || _status.equals(statusDeleted) || _status.equals(statusFailed);
    }

    /** @return true if workitem has completed */
    public boolean hasCompletedStatus() {
        return _status.equals(statusComplete) || _status.equals(statusForcedComplete);
    }

    /** @return true if workitem is not finished */
    public boolean hasUnfinishedStatus() {
        return hasLiveStatus() || _status.equals(statusSuspended) || _status.equals(statusDeadlocked);
    }

    /** @return true if workitem is suspended from enabled status */
    public boolean isEnabledSuspended() {
        return _status.equals(statusSuspended) && _prevStatus.equals(statusEnabled);
    }

    public boolean equals(Object other) {
        if (this == other)
            return true;
        if (other instanceof YWorkItem) { // instanceof = false if other is null
            YWorkItem otherItem = (YWorkItem) other;
            if (this.get_thisID() != null) {
                return this.get_thisID().equals(otherItem.get_thisID());
            } else if (this.getWorkItemID() != null) {
                return this.getWorkItemID().equals(otherItem.getWorkItemID());
            }
        }
        return false;
    }

    public int hashCode() {
        return (get_thisID() != null) ? get_thisID().hashCode()
                : (getWorkItemID() != null) ? getWorkItemID().hashCode() : super.hashCode();
    }

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

    // STATUS CHANGE METHODS //

    public void setStatusToStarted(YPersistenceManager pmgr, YClient client) throws YPersistenceException {
        if (!_status.equals(statusFired)) {
            throw new RuntimeException(this + " [when current status is \"" + _status
                    + "\" it cannot be moved to \"" + statusExecuting + "\"]");
        }

        set_status(pmgr, statusExecuting);
        _startTime = new Date();
        _externalClient = client;
        if (!_timerStarted)
            checkStartTimer(pmgr, null);
        if (pmgr != null)
            pmgr.updateObject(this);
        _eventLog.logWorkItemEvent(pmgr, this, _status, createLogDataList(_status.name()));
    }

    public void setStatusToComplete(YPersistenceManager pmgr, YEngine.WorkItemCompletion completionFlag)
            throws YPersistenceException {
        YWorkItemStatus completionStatus;
        switch (completionFlag) {
        case Normal:
            completionStatus = statusComplete;
            break;
        case Force:
            completionStatus = statusForcedComplete;
            break;
        case Fail:
            completionStatus = statusFailed;
            break;
        default:
            completionStatus = statusComplete;
        }
        completePersistence(pmgr, completionStatus);
    }

    public void setStatusToDeleted(YPersistenceManager pmgr) throws YPersistenceException {
        completePersistence(pmgr, statusDeleted);
    }

    /**
     * announces and logs that this workitem has been discarded - ie. left in the net when
     * the net completed
     */
    public void setStatusToDiscarded(YPersistenceManager pmgr) {
        try {
            set_status(null, statusDiscarded);
            _eventLog.logWorkItemEvent(pmgr, this, _status, null);
        } catch (YPersistenceException ype) {
            // no action required
        }
    }

    public void rollBackStatus(YPersistenceManager pmgr) throws YPersistenceException {
        if (!_status.equals(statusExecuting)) {
            throw new RuntimeException(this + " [when current status is \"" + _status
                    + "\" it cannot be rolled back to \"" + statusFired + "\"]");
        }

        set_status(pmgr, statusFired);
        _eventLog.logWorkItemEvent(pmgr, this, _status, createLogDataList(_status.name()));
        _startTime = null;
        _externalClient = null;
        if (pmgr != null)
            pmgr.updateObject(this);
    }

    public void setStatusToSuspended(YPersistenceManager pmgr) throws YPersistenceException {
        if (hasLiveStatus()) {
            _prevStatus = _status;
            set_status(pmgr, statusSuspended);
            _eventLog.logWorkItemEvent(pmgr, this, _status, createLogDataList(_status.name()));
        } else
            throw new RuntimeException(
                    this + " [when current status is \"" + _status + "\" it cannot be moved to \"Suspended\".]");
    }

    public void setStatusToUnsuspended(YPersistenceManager pmgr) throws YPersistenceException {
        set_status(pmgr, _prevStatus);
        _prevStatus = null;
        _eventLog.logWorkItemEvent(pmgr, this, "resume", createLogDataList("resume"));
    }

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

    // GETTERS & SETTERS //

    public void set_parent(YWorkItem parent) {
        _parent = parent;
    }

    public YWorkItem get_parent() {
        return _parent;
    }

    public Set get_children() {
        return _children;
    }

    public boolean hasChildren() {
        return _children != null;
    }

    public void add_child(YWorkItem child) {
        _children.add(child);
    }

    public void add_children(Set children) {
        _children.addAll(children);
    }

    public void setWorkItemID(YWorkItemID workitemid) {
        _workItemID = workitemid;
    } //

    public String get_thisID() {
        return _thisID;
    }

    public void set_thisID(String thisID) {
        _thisID = thisID;
    }

    public String get_specIdentifier() {
        return _specID.getIdentifier();
    }

    public String get_specVersion() {
        return _specID.getVersionAsString();
    }

    public String get_specUri() {
        return _specID.getUri();
    }

    public void set_specIdentifier(String id) {
        if (_specID == null)
            _specID = new YSpecificationID((String) null);
        _specID.setIdentifier(id);
    }

    public void set_specUri(String uri) {
        if (_specID != null)
            _specID.setUri(uri);
        else
            _specID = new YSpecificationID(uri);
    }

    public void set_specVersion(String version) {
        if (_specID == null)
            _specID = new YSpecificationID((String) null);
        _specID.setVersion(version);
    }

    public Hashtable<String, String> getAttributes() {
        return _attributes;
    }

    public void setAttributes(Map<String, String> attributes) {
        if (attributes != null) {
            _attributes = new YAttributeMap(attributes);
        }
    }

    public boolean requiresManualResourcing() {
        return _requiresManualResourcing;
    }

    public void setRequiresManualResourcing(boolean requires) {
        _requiresManualResourcing = requires;
    }

    public String getCodelet() {
        return _codelet;
    }

    public void setCodelet(String codelet) {
        _codelet = codelet;
    }

    public URL getCustomFormURL() {
        return _customFormURL;
    }

    public void setCustomFormURL(URL formURL) {
        _customFormURL = formURL;
    }

    public String get_deferredChoiceGroupID() {
        return _deferredChoiceGroupID;
    }

    public void set_deferredChoiceGroupID(String id) {
        _deferredChoiceGroupID = id;
    }

    public Date get_enablementTime() {
        return _enablementTime;
    }

    public void set_enablementTime(Date eTime) {
        _enablementTime = eTime;
    }

    public Date get_firingTime() {
        return _firingTime;
    }

    public void set_firingTime(Date fTime) {
        _firingTime = fTime;
    }

    public Date get_startTime() {
        return _startTime;
    }

    public void set_startTime(Date sTime) {
        _startTime = sTime;
    }

    public String get_status() {
        return _status.toString();
    }

    public void set_status(String status) { // for hibernate
        _status = YWorkItemStatus.fromString(status);
    }

    public String get_prevStatus() {
        return (_prevStatus != null) ? _prevStatus.toString() : null;
    }

    public void set_prevStatus(String status) {
        _prevStatus = (status != null) ? YWorkItemStatus.fromString(status) : null;
    }

    private void set_status(YPersistenceManager pmgr, YWorkItemStatus status) throws YPersistenceException {
        _engine.getAnnouncer().announceWorkItemStatusChange(this, _status, status);
        _status = status;
        if (pmgr != null)
            pmgr.updateObject(this);
    }

    public String get_externalClient() {
        return (_externalClient != null) ? _externalClient.getUserName() : null;
    }

    public void set_externalClient(String owner) {
        _externalClientStr = owner;
    }

    public boolean get_allowsDynamicCreation() {
        return _allowsDynamicCreation;
    }

    public void set_allowsDynamicCreation(boolean a) {
        _allowsDynamicCreation = a;
    }

    public String get_dataString() {
        return _dataString;
    }

    public void set_dataString(String s) {
        _dataString = s;
    }

    public void setInitData(Element data) {
        _dataList = data;
        _dataString = getDataString();
    }

    public void setStatus(YWorkItemStatus status) {
        _status = status;
    }

    public YWorkItemID getWorkItemID() {
        return _workItemID;
    }

    public Date getEnablementTime() {
        return _enablementTime;
    }

    public String getEnablementTimeStr() {
        return _df.format(_enablementTime);
    }

    public Date getFiringTime() {
        return _firingTime;
    }

    public String getFiringTimeStr() {
        return _df.format(_firingTime);
    }

    public Date getStartTime() {
        return _startTime;
    }

    public String getStartTimeStr() {
        return _df.format(_startTime);
    }

    public YWorkItemStatus getStatus() {
        return _status;
    }

    public YWorkItem getParent() {
        return _parent;
    }

    public Set<YWorkItem> getChildren() {
        return _children;
    }

    public YIdentifier getCaseID() {
        return _workItemID.getCaseID();
    }

    public String getTaskID() {
        return _workItemID.getTaskID();
    }

    public String getIDString() {
        return _workItemID.toString();
    }

    private String getUniqueID() {
        return _workItemID.getUniqueID();
    }

    public String getDeferredChoiceGroupID() {
        return _deferredChoiceGroupID;
    }

    public void setDeferredChoiceGroupID(String id) {
        _deferredChoiceGroupID = id;
    }

    public String getSpecName() {
        return _specID.getUri();
    }

    public YSpecificationID getSpecificationID() {
        return _specID;
    }

    public YTimerParameters getTimerParameters() {
        return _timerParameters;
    }

    public void setTimerParameters(YTimerParameters params) {
        _timerParameters = params;
    }

    public boolean hasTimerStarted() {
        return _timerStarted;
    }

    public void setTimerStarted(boolean started) {
        _timerStarted = started;
        if (started)
            setTimerActive();
    }

    public long getTimerExpiry() {
        return _timerExpiry;
    }

    public void setTimerExpiry(long time) {
        _timerExpiry = time;
    }

    public String getTimerStatus() {
        if (_timerParameters == null)
            return "Nil";
        if (_timerExpiry == 0)
            return "Dormant";
        return "Active";
    }

    public boolean allowsDynamicCreation() {
        return _allowsDynamicCreation;
    }

    public String toString() {
        String idString = getWorkItemID() != null ? getWorkItemID().toString()
                : get_thisID() != null ? get_thisID() : "";
        return getClass().getSimpleName() + " : " + idString;
    }

    public YClient getExternalClient() {
        return _externalClient;
    }

    public YNetRunner getNetRunner() {
        return _engine.getNetRunnerRepository().get(this);
    }

    public Element getDataElement() {
        return _dataList;
    }

    public String getDataString() {
        return JDOMUtil.elementToString(_dataList);
    }

    public YTask getTask() {
        return _task;
    }

    public void setTask(YTask task) {
        _task = task;
    }

    public String getDocumentation() {
        return (_parent != null) ? _parent.getDocumentation() : _documentation;
    }

    public String toXML() {
        StringBuilder xml = new StringBuilder("<workItem");
        if (_attributes != null)
            xml.append(_attributes.toXML());
        xml.append(">");
        xml.append(StringUtil.wrap(getTaskID(), "taskid"));
        xml.append(StringUtil.wrap(getCaseID().toString(), "caseid"));
        xml.append(StringUtil.wrap(getUniqueID(), "uniqueid"));
        xml.append(StringUtil.wrap(_task.getName(), "taskname"));
        xml.append(StringUtil.wrap(getDocumentation(), "documentation"));
        if (_specID.getIdentifier() != null)
            xml.append(StringUtil.wrap(_specID.getIdentifier(), "specidentifier"));

        xml.append(StringUtil.wrap(String.valueOf(_specID.getVersion()), "specversion"));
        xml.append(StringUtil.wrap(_specID.getUri(), "specuri"));
        xml.append(StringUtil.wrap(_status.toString(), "status"));
        xml.append(StringUtil.wrap(String.valueOf(_allowsDynamicCreation), "allowsdynamiccreation"));
        xml.append(StringUtil.wrap(String.valueOf(_requiresManualResourcing), "requiresmanualresourcing"));
        xml.append(StringUtil.wrap(_codelet, "codelet"));
        if (_deferredChoiceGroupID != null)
            xml.append(StringUtil.wrap(_deferredChoiceGroupID, "deferredChoiceGroupID"));
        if (_dataList != null)
            xml.append(StringUtil.wrap(getDataString(), "data"));
        xml.append(StringUtil.wrap(_df.format(getEnablementTime()), "enablementTime"));
        xml.append(StringUtil.wrap(String.valueOf(getEnablementTime().getTime()), "enablementTimeMs"));
        if (getFiringTime() != null) {
            xml.append(StringUtil.wrap(_df.format(getFiringTime()), "firingTime"));
            xml.append(StringUtil.wrap(String.valueOf(getFiringTime().getTime()), "firingTimeMs"));
        }
        if (getStartTime() != null) {
            xml.append(StringUtil.wrap(_df.format(getStartTime()), "startTime"));
            xml.append(StringUtil.wrap(String.valueOf(getStartTime().getTime()), "startTimeMs"));
            if (_externalClient != null) {
                xml.append(StringUtil.wrap(_externalClient.getUserName(), "startedBy"));
            }
        }
        if (_timerParameters != null) {
            long expiry = _timerExpiry > 0 ? _timerExpiry : _parent != null ? _parent.getTimerExpiry() : 0;
            YWorkItemTimer.Trigger trigger = _timerParameters.getTrigger();
            if (trigger != null && expiry > 0) {
                String triggerName = trigger.name();
                xml.append(StringUtil.wrap(triggerName, "timertrigger"));
                xml.append(StringUtil.wrap(String.valueOf(expiry), "timerexpiry"));
            }
        }
        if (_customFormURL != null) {
            xml.append(StringUtil.wrap(_customFormURL.toString(), "customform"));
        }
        YDecomposition decomp = _task.getDecompositionPrototype();
        if (decomp != null) {
            YLogPredicate logPredicate = decomp.getLogPredicate();
            if (logPredicate != null) {
                xml.append(logPredicate.toXML());
            }
        }
        xml.append("</workItem>");
        return xml.toString();
    }

    private YLogDataItemList createLogDataList(String tag) {
        YLogDataItemList itemList = new YLogDataItemList();
        if (_externalClient != null) {
            itemList.add(new YLogDataItem("OwnerService", tag, _externalClient.getUserName(), "string"));
        }
        if (tag.equals(statusExecuting.name())) {
            YLogDataItem dataItem = getDecompLogPredicate(YWorkItemStatus.valueOf(tag));
            if (dataItem != null)
                itemList.add(dataItem);
        } else if (tag.equals(statusComplete.name()) || tag.equals(statusForcedComplete.name())) {
            itemList.addAll(getCompletionPredicates());
        }
        return (itemList.isEmpty()) ? null : itemList;
    }

    private YLogDataItemList getCompletionPredicates() {
        YLogDataItemList completionList = new YLogDataItemList();
        YLogDataItem completionItem = null;
        if (_externalLogPredicate != null) {
            if (_externalLogPredicate.startsWith("<logdataitemlist>")) {
                completionList.fromXML(_externalLogPredicate);
                for (YLogDataItem item : completionList) {
                    if (item.getName().equals("Complete")) {
                        completionItem = item;
                        break;
                    }
                }
            } else if (_externalLogPredicate.startsWith("<logdataitem>")) {
                YLogDataItem item = new YLogDataItem(_externalLogPredicate);
                completionList.add(item);
                if (item.getName().equals("Complete"))
                    completionItem = item;
            } else {
                completionItem = new YLogDataItem("Predicate", "External", _externalLogPredicate, "string");
                completionList.add(completionItem);
            }
        }
        if (completionItem != null) {
            completionItem.setValue(new YLogPredicateWorkItemParser(this).parse(completionItem.getValue()));
        } else {
            completionItem = getDecompLogPredicate(YWorkItemStatus.statusComplete);
            if (completionItem != null)
                completionList.add(completionItem);
        }
        return completionList;
    }

    private YLogDataItem getDecompLogPredicate(YWorkItemStatus itemStatus) {
        YLogDataItem dataItem = null;
        String predicate = null;
        YLogPredicate logPredicate = getDecompLogPredicate();
        if (logPredicate != null) {
            if (itemStatus.equals(YWorkItemStatus.statusExecuting)) {
                predicate = logPredicate.getParsedStartPredicate(this);
            } else if (itemStatus.equals(YWorkItemStatus.statusComplete)) {
                predicate = logPredicate.getParsedCompletionPredicate(this);
            }
            if (predicate != null) {
                dataItem = new YLogDataItem("Predicate", itemStatus.name(), predicate, "string");
            }
        }
        return dataItem;
    }

    private YLogPredicate getDecompLogPredicate() {
        YDecomposition decomp = _task.getDecompositionPrototype();
        return (decomp != null) ? decomp.getLogPredicate() : null;
    }

    private YLogDataItem getDataLogPredicate(YParameter param, boolean input) {
        YLogDataItem dataItem = null;
        YLogPredicate logPredicate = param.getLogPredicate();
        if (logPredicate != null) {
            String predicate = input ? logPredicate.getParsedStartPredicate(param)
                    : logPredicate.getParsedCompletionPredicate(param);
            if (predicate != null) {
                dataItem = new YLogDataItem("Predicate", param.getPreferredName(), predicate, "string");
            }
        }
        return dataItem;
    }

}