/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.ode.bpel.engine;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.namespace.QName;

import org.apache.commons.collections.comparators.ComparatorChain;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ode.bpel.common.BpelEventFilter;
import org.apache.ode.bpel.common.Filter;
import org.apache.ode.bpel.common.InstanceFilter;
import org.apache.ode.bpel.common.ProcessFilter;
import org.apache.ode.bpel.dao.ActivityRecoveryDAO;
import org.apache.ode.bpel.dao.BpelDAOConnection;
import org.apache.ode.bpel.dao.CorrelationSetDAO;
import org.apache.ode.bpel.dao.PartnerLinkDAO;
import org.apache.ode.bpel.dao.ProcessDAO;
import org.apache.ode.bpel.dao.ProcessInstanceDAO;
import org.apache.ode.bpel.dao.ScopeDAO;
import org.apache.ode.bpel.dao.XmlDataDAO;
import org.apache.ode.bpel.dao.ProcessManagementDAO.FailedSummaryValue;
import org.apache.ode.bpel.dao.ProcessManagementDAO.InstanceSummaryKey;
import org.apache.ode.bpel.engine.replayer.Replayer;
import org.apache.ode.bpel.evt.ActivityEvent;
import org.apache.ode.bpel.evt.BpelEvent;
import org.apache.ode.bpel.evt.CorrelationEvent;
import org.apache.ode.bpel.evt.CorrelationMatchEvent;
import org.apache.ode.bpel.evt.CorrelationSetEvent;
import org.apache.ode.bpel.evt.CorrelationSetWriteEvent;
import org.apache.ode.bpel.evt.ExpressionEvaluationEvent;
import org.apache.ode.bpel.evt.ExpressionEvaluationFailedEvent;
import org.apache.ode.bpel.evt.NewProcessInstanceEvent;
import org.apache.ode.bpel.evt.PartnerLinkEvent;
import org.apache.ode.bpel.evt.ProcessCompletionEvent;
import org.apache.ode.bpel.evt.ProcessEvent;
import org.apache.ode.bpel.evt.ProcessInstanceEvent;
import org.apache.ode.bpel.evt.ProcessInstanceStartedEvent;
import org.apache.ode.bpel.evt.ProcessInstanceStateChangeEvent;
import org.apache.ode.bpel.evt.ProcessMessageExchangeEvent;
import org.apache.ode.bpel.evt.ScopeCompletionEvent;
import org.apache.ode.bpel.evt.ScopeEvent;
import org.apache.ode.bpel.evt.ScopeFaultEvent;
import org.apache.ode.bpel.evt.VariableEvent;
import org.apache.ode.bpel.evt.VariableModificationEvent;
import org.apache.ode.bpel.evtproc.ActivityStateDocumentBuilder;
import org.apache.ode.bpel.iapi.BpelEngineException;
import org.apache.ode.bpel.iapi.BpelServer;
import org.apache.ode.bpel.iapi.EndpointReference;
import org.apache.ode.bpel.iapi.ProcessConf;
import org.apache.ode.bpel.iapi.ProcessState;
import org.apache.ode.bpel.iapi.ProcessStore;
import org.apache.ode.bpel.iapi.ProcessConf.CLEANUP_CATEGORY;
import org.apache.ode.bpel.o.OBase;
import org.apache.ode.bpel.o.OPartnerLink;
import org.apache.ode.bpel.o.OProcess;
import org.apache.ode.bpel.pmapi.ActivityExtInfoListDocument;
import org.apache.ode.bpel.pmapi.ActivityInfoDocument;
import org.apache.ode.bpel.pmapi.EventInfoListDocument;
import org.apache.ode.bpel.pmapi.GetCommunication;
import org.apache.ode.bpel.pmapi.GetCommunicationResponseDocument;
import org.apache.ode.bpel.pmapi.InstanceInfoDocument;
import org.apache.ode.bpel.pmapi.InstanceInfoListDocument;
import org.apache.ode.bpel.pmapi.InstanceManagement;
import org.apache.ode.bpel.pmapi.InstanceNotFoundException;
import org.apache.ode.bpel.pmapi.InvalidRequestException;
import org.apache.ode.bpel.pmapi.ManagementException;
import org.apache.ode.bpel.pmapi.ProcessInfoCustomizer;
import org.apache.ode.bpel.pmapi.ProcessInfoDocument;
import org.apache.ode.bpel.pmapi.ProcessInfoListDocument;
import org.apache.ode.bpel.pmapi.ProcessManagement;
import org.apache.ode.bpel.pmapi.ProcessNotFoundException;
import org.apache.ode.bpel.pmapi.ProcessingException;
import org.apache.ode.bpel.pmapi.Replay;
import org.apache.ode.bpel.pmapi.ReplayResponse;
import org.apache.ode.bpel.pmapi.ReplayResponseDocument;
import org.apache.ode.bpel.pmapi.ScopeInfoDocument;
import org.apache.ode.bpel.pmapi.TActivityExtInfo;
import org.apache.ode.bpel.pmapi.TActivityStatus;
import org.apache.ode.bpel.pmapi.TActivitytExtInfoList;
import org.apache.ode.bpel.pmapi.TCorrelationProperty;
import org.apache.ode.bpel.pmapi.TDefinitionInfo;
import org.apache.ode.bpel.pmapi.TDeploymentInfo;
import org.apache.ode.bpel.pmapi.TDocumentInfo;
import org.apache.ode.bpel.pmapi.TEndpointReferences;
import org.apache.ode.bpel.pmapi.TEventInfo;
import org.apache.ode.bpel.pmapi.TEventInfoList;
import org.apache.ode.bpel.pmapi.TFailureInfo;
import org.apache.ode.bpel.pmapi.TFailuresInfo;
import org.apache.ode.bpel.pmapi.TFaultInfo;
import org.apache.ode.bpel.pmapi.TInstanceInfo;
import org.apache.ode.bpel.pmapi.TInstanceInfoList;
import org.apache.ode.bpel.pmapi.TInstanceStatus;
import org.apache.ode.bpel.pmapi.TInstanceSummary;
import org.apache.ode.bpel.pmapi.TProcessInfo;
import org.apache.ode.bpel.pmapi.TProcessInfoList;
import org.apache.ode.bpel.pmapi.TProcessProperties;
import org.apache.ode.bpel.pmapi.TProcessStatus;
import org.apache.ode.bpel.pmapi.TScopeInfo;
import org.apache.ode.bpel.pmapi.TScopeRef;
import org.apache.ode.bpel.pmapi.TVariableInfo;
import org.apache.ode.bpel.pmapi.TVariableRef;
import org.apache.ode.bpel.pmapi.VariableInfoDocument;
import org.apache.ode.utils.DOMUtils;
import org.apache.ode.utils.ISO8601DateParser;
import org.apache.ode.utils.msg.MessageBundle;
import org.apache.ode.utils.stl.CollectionsX;
import org.apache.ode.utils.stl.MemberOfFunction;
import org.apache.ode.utils.stl.UnaryFunction;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * Implementation of the Process and InstanceManagement APIs.
 *
 * @todo Move this out of the engine, it no longer belongs here.
 */
public class ProcessAndInstanceManagementImpl implements InstanceManagement, ProcessManagement {

    protected static final Messages __msgs = MessageBundle.getMessages(Messages.class);

    protected static Log __log = LogFactory.getLog(BpelManagementFacadeImpl.class);

    protected static final ProcessStatusConverter __psc = new ProcessStatusConverter();

    protected BpelDatabase _db;

    protected ProcessStore _store;

    // Calendar can be expensive to initialize so we cache and clone it
    protected Calendar _calendar = Calendar.getInstance();

    protected BpelServerImpl _server;

    public ProcessAndInstanceManagementImpl(BpelServer server, ProcessStore store) {
        _server = (BpelServerImpl) server;
        _db = _server._db;
        _store = store;
    }

    public ProcessInfoListDocument listProcessesCustom(String filter, String orderKeys,
            final ProcessInfoCustomizer custom) {
        ProcessInfoListDocument ret = ProcessInfoListDocument.Factory.newInstance();
        final TProcessInfoList procInfoList = ret.addNewProcessInfoList();
        final ProcessFilter processFilter = new ProcessFilter(filter, orderKeys);
        try {
            _db.exec(new BpelDatabase.Callable<Object>() {
                public Object run(BpelDAOConnection conn) throws Exception {
                    Collection<ProcessConf> pconfs = processQuery(processFilter);
                    for (ProcessConf pconf : pconfs) {
                        try {
                            fillProcessInfo(procInfoList.addNewProcessInfo(), pconf, custom);
                        } catch (Exception e) {
                            failIfSQLException(e);
                            __log.error("Exception when querying process " + pconf.getProcessId(), e);
                        }
                    }
                    try {
                        fillProcessInfoSummary(conn, procInfoList.getProcessInfoList(), custom);
                    } catch (Exception e) {
                        failIfSQLException(e);
                        __log.error("Exception fetching instances summary", e);
                    }
                    return null;
                }
            });
        } catch (Exception e) {
            __log.error("Exception while listing processes", e);
            throw new ProcessingException("Exception while listing processes: " + e.toString());
        }
        return ret;
    }

    public ProcessInfoListDocument listProcesses(String filter, String orderKeys) {
        return listProcessesCustom(filter, orderKeys, ProcessInfoCustomizer.ALL);
    }

    public ProcessInfoListDocument listAllProcesses() {
        return listProcessesCustom(null, null, ProcessInfoCustomizer.ALL);
    }

    public ProcessInfoListDocument listProcessesSummaryOnly() {
        return listProcessesCustom(null, null, ProcessInfoCustomizer.SUMMARYONLY);
    }

    public ProcessInfoListDocument listProcessesSummaryAndProperties() {
        return listProcessesCustom(null, null, ProcessInfoCustomizer.SUMMARY_PROPERTIES);
    }

    public ProcessInfoDocument getProcessInfoCustom(final QName pid, final ProcessInfoCustomizer custom) {
        try {
            return _db.exec(new BpelDatabase.Callable<ProcessInfoDocument>() {
                public ProcessInfoDocument run(BpelDAOConnection conn) {
                    return genProcessInfoDocument(conn, pid, custom);
                }
            });
        } catch (Exception ex) {
            __log.error("Exception in getProcessInfoCustom()", ex);
            throw new ManagementException("Exception in getProcessInfoCustom(): " + ex.toString());
        }
    }

    public ProcessInfoDocument getProcessInfo(QName pid) {
        return getProcessInfoCustom(pid, ProcessInfoCustomizer.ALL);
    }

    public ProcessInfoDocument activate(QName pid) {
        try {
            _store.setState(pid, org.apache.ode.bpel.iapi.ProcessState.ACTIVE);
        } catch (Exception ex) {
            __log.error("Exception while setting process state", ex);
            throw new ManagementException("Error setting process state: " + ex.toString());
        }
        return getProcessInfoCustom(pid, ProcessInfoCustomizer.NONE);
    }

    public ProcessInfoDocument setRetired(QName pid, boolean retired) throws ManagementException {
        try {
            _store.setState(pid, retired ? ProcessState.RETIRED : ProcessState.ACTIVE);
        } catch (BpelEngineException e) {
            __log.error("Exception while setting process as retired", e);
            throw new ProcessNotFoundException("ProcessNotFound:" + pid);
        }
        return getProcessInfoCustom(pid, ProcessInfoCustomizer.NONE);
    }

    public void setPackageRetired(String packageName, boolean retired)
            throws ManagementException {
        try {
            _store.setRetiredPackage(packageName, retired);
        } catch (BpelEngineException e) {
            __log.error("Exception while setting process as retired", e);
            throw new ProcessNotFoundException("PackageNotFound:" + packageName);
        }
    }

    public ProcessInfoDocument setProcessPropertyNode(final QName pid, final QName propertyName, final Node value)
            throws ManagementException {
        ProcessInfoDocument ret = ProcessInfoDocument.Factory.newInstance();
        final TProcessInfo pi = ret.addNewProcessInfo();
        try {
            _db.exec(new BpelDatabase.Callable<Object>() {
                public Object run(BpelDAOConnection conn) throws Exception {
                    try {
                        _store.setProperty(pid, propertyName, value);
                    } catch (Exception ex) {
                        failIfSQLException(ex);

                        // Likely the process no longer exists in the store.
                        __log.debug("Error setting property value for " + pid + "; " + propertyName, ex);
                    }

                    // We have to do this after we set the property, since the
                    // ProcessConf object
                    // is immutable.
                    ProcessConf proc = _store.getProcessConfiguration(pid);
                    if (proc == null)
                        throw new ProcessNotFoundException("ProcessNotFound:" + pid);

                    fillProcessInfo(pi, proc, new ProcessInfoCustomizer(ProcessInfoCustomizer.Item.PROPERTIES));

                    return null;
                }
            });
        } catch (ManagementException me) {
            throw me;
        } catch (Exception e) {
            __log.error("Exception while setting process property", e);
            throw new ProcessingException("Exception while setting process property: " + e.toString());
        }
        return ret;
    }

    public ProcessInfoDocument setProcessProperty(final QName pid, final QName propertyName, final String value)
            throws ManagementException {
        ProcessInfoDocument ret = ProcessInfoDocument.Factory.newInstance();
        final TProcessInfo pi = ret.addNewProcessInfo();
        try {
            _db.exec(new BpelDatabase.Callable<Object>() {
                public Object run(BpelDAOConnection conn) throws Exception {
                    try {
                        _store.setProperty(pid, propertyName, value);
                    } catch (Exception ex) {
                        failIfSQLException(ex);

                        // Likely the process no longer exists in the store.
                        __log.debug("Error setting property value for " + pid + "; " + propertyName, ex);
                    }

                    // We have to do this after we set the property, since the
                    // ProcessConf object is immutable.
                    ProcessConf proc = _store.getProcessConfiguration(pid);
                    if (proc == null)
                        throw new ProcessNotFoundException("ProcessNotFound:" + pid);

                    fillProcessInfo(pi, proc, new ProcessInfoCustomizer(ProcessInfoCustomizer.Item.PROPERTIES));

                    return null;
                }
            });
        } catch (ManagementException me) {
            throw me;
        } catch (Exception e) {
            __log.error("Exception while setting process property", e);
            throw new ProcessingException("Exception while setting process property" + e.toString());
        }
        return ret;
    }

    private boolean garbage(String filter) {
        if(filter == null) {
            return false;
        }
        Matcher expressionMatcher = Filter.__comparatorPattern.matcher(filter);
        if(!filter.trim().equals("") && !expressionMatcher.find()) {
            return true;
        }
        return false;
    }

    public InstanceInfoListDocument listInstances(String filter, String order, int limit) {
        InstanceInfoListDocument ret = InstanceInfoListDocument.Factory.newInstance();
        final TInstanceInfoList infolist = ret.addNewInstanceInfoList();
        if(garbage(filter)) {
            return ret;
        }
        final InstanceFilter instanceFilter = new InstanceFilter(filter, order, limit);
        try {
            _db.exec(new BpelDatabase.Callable<Object>() {
                public Object run(BpelDAOConnection conn) {
                    Collection<ProcessInstanceDAO> instances = conn.instanceQuery(instanceFilter);
                    for (ProcessInstanceDAO instance : instances) {
                        fillInstanceInfo(infolist.addNewInstanceInfo(), instance);
                    }
                    return null;
                }
            });
        } catch (Exception e) {
            __log.error("Exception while listing instances", e);
            throw new ProcessingException("Exception while listing instances: " + e.toString());
        }
        return ret;
    }

    public InstanceInfoListDocument listInstancesSummary(String filter, String order, int limit) {
        InstanceInfoListDocument ret = InstanceInfoListDocument.Factory.newInstance();
        final TInstanceInfoList infolist = ret.addNewInstanceInfoList();
        if(garbage(filter)) {
            return ret;
        }
        final InstanceFilter instanceFilter = new InstanceFilter(filter, order, limit);
        try {
            _db.exec(new BpelDatabase.Callable<Object>() {
                public Object run(BpelDAOConnection conn) {
                    Collection<ProcessInstanceDAO> instances = conn.instanceQuery(instanceFilter);
                    Map<Long, Collection<CorrelationSetDAO>> icsets = conn.getCorrelationSets(instances);
                    for (ProcessInstanceDAO instance : instances) {
                        TInstanceInfo info = infolist.addNewInstanceInfo();
                        fillInstanceSummary(info, instance);
                        Collection<CorrelationSetDAO> csets = icsets.get(instance.getInstanceId());
                        if (csets != null) {
                            for (CorrelationSetDAO cset: csets) {
                                Map<QName, String> props = cset.getProperties();
                                fillProperties(info, instance, props);
                            }
                        }
                    }
                    return null;
                }
            });
        } catch (Exception e) {
            __log.error("Exception while listing instances", e);
            throw new ProcessingException("Exception while listing instances: " + e.toString());
        }
        return ret;
    }

    public InstanceInfoListDocument listAllInstances() {
        return listInstancesSummary(null, null, Integer.MAX_VALUE);
    }

    public InstanceInfoListDocument listAllInstancesWithLimit(int limit) {
        return listInstancesSummary(null, null, limit);
    }

    public InstanceInfoDocument getInstanceInfo(final Long iid) throws InstanceNotFoundException {
        try {
            return _db.exec(new BpelDatabase.Callable<InstanceInfoDocument>() {
                public InstanceInfoDocument run(BpelDAOConnection conn) {
                    return genInstanceInfoDocument(conn, iid);
                }
            });
        } catch (Exception e) {
            __log.error("Exception while retrieving instance info", e);
            throw new ProcessingException("Exception while retrieving instance info: " + e.toString());
        }
    }

    public ScopeInfoDocument getScopeInfo(String siid) {
        return getScopeInfoWithActivity(siid, false);
    }

    public ScopeInfoDocument getScopeInfoWithActivity(final String siid, final boolean includeActivityInfo) {
        try {
            return _db.exec(new BpelDatabase.Callable<ScopeInfoDocument>() {
                public ScopeInfoDocument run(BpelDAOConnection conn) throws Exception {
                    return genScopeInfoDocument(conn, siid, includeActivityInfo);
                }
            });
        } catch (Exception e) {
            __log.error("Exception while retrieving scope info", e);
            throw new ProcessingException("Exception while retrieving scope info: " + e.toString());
        }
    }

    public VariableInfoDocument getVariableInfo(final String scopeId, final String varName) throws ManagementException {
        VariableInfoDocument ret = VariableInfoDocument.Factory.newInstance();
        final TVariableInfo vinf = ret.addNewVariableInfo();
        final TVariableRef sref = vinf.addNewSelf();
        dbexec(new BpelDatabase.Callable<Object>() {
            public Object run(BpelDAOConnection session) throws Exception {
                ScopeDAO scope = session.getScope(new Long(scopeId));
                if (scope == null) {
                    throw new InvalidRequestException("ScopeNotFound:" + scopeId);
                }

                sref.setSiid(scopeId);
                sref.setIid(scope.getProcessInstance().getInstanceId().toString());
                sref.setName(varName);

                XmlDataDAO var = scope.getVariable(varName);
                if (var == null) {
                    throw new InvalidRequestException("VarNotFound:" + varName);
                }

                Node nval = var.get();
                if (nval != null) {
                    TVariableInfo.Value val = vinf.addNewValue();
                    val.getDomNode().appendChild(val.getDomNode().getOwnerDocument().importNode(nval, true));
                }
                return null;
            }
        });
        return ret;
    }
    
    public VariableInfoDocument setVariable(final String scopeId, final String varName, final XmlObject value) throws ManagementException {
        VariableInfoDocument ret = VariableInfoDocument.Factory.newInstance();
        final TVariableInfo vinf = ret.addNewVariableInfo();
        final TVariableRef sref = vinf.addNewSelf();
        dbexec(new BpelDatabase.Callable<Object>() {
            public Object run(BpelDAOConnection session) throws Exception {
                ScopeDAO scope = session.getScope(new Long(scopeId));
                if (scope == null) {
                    throw new InvalidRequestException("ScopeNotFound:" + scopeId);
                }

                sref.setSiid(scopeId);
                sref.setIid(scope.getProcessInstance().getInstanceId().toString());
                sref.setName(varName);

                XmlDataDAO var = scope.getVariable(varName);
                if (var == null) {
                    throw new InvalidRequestException("VarNotFound:" + varName);
                }
                
                {
                    ByteArrayOutputStream out = new ByteArrayOutputStream(); 
                    value.save(out, new XmlOptions().setSaveOuter());
                    Node value2 = DOMUtils.getFirstChildElement(DOMUtils.stringToDOM(out.toString()));
                    var.set(value2);
                }

                Node nval = var.get();
                if (nval != null) {
                    TVariableInfo.Value val = vinf.addNewValue();
                    val.getDomNode().appendChild(val.getDomNode().getOwnerDocument().importNode(nval, true));
                }
                return null;
            }
        });
        return ret;
    }

    //
    // INSTANCE ACTIONS
    //
    public InstanceInfoDocument fault(Long iid, QName faultname, Element faultData) {
        // TODO: Implement
        return getInstanceInfo(iid);
    }

    public InstanceInfoDocument resume(Long iid) {
        // We need debugger support in order to resume (since we have to force
        // a reduction. If one is not available the getDebugger() method should
        // throw a ProcessingException
        getDebugger(iid).resume(iid);

        return getInstanceInfo(iid);
    }

    public InstanceInfoDocument suspend(Long iid) throws ManagementException {
        DebuggerSupport debugSupport = getDebugger(iid);
        assert debugSupport != null : "getDebugger(Long) returned NULL!";
        debugSupport.suspend(iid);

        return getInstanceInfo(iid);
    }

    public InstanceInfoDocument terminate(Long iid) throws ManagementException {
        DebuggerSupport debugSupport = getDebugger(iid);
        assert debugSupport != null : "getDebugger(Long) returned NULL!";
        debugSupport.terminate(iid);

        return getInstanceInfo(iid);
    }

    public InstanceInfoDocument recoverActivity(final Long iid, final Long aid, final String action) {
        try {
            _db.exec(new BpelDatabase.Callable<QName>() {
                public QName run(BpelDAOConnection conn) throws Exception {
                    ProcessInstanceDAO instance = conn.getInstance(iid);
                    if (instance == null)
                        return null;
                    for (ActivityRecoveryDAO recovery : instance.getActivityRecoveries()) {
                        if (recovery.getActivityId() == aid) {
                            BpelProcess process = _server._engine._activeProcesses.get(instance.getProcess().getProcessId());
                            if (process != null) {
                                process.recoverActivity(instance, recovery.getChannel(), aid, action, null);
                                break;
                            }
                        }
                    }
                    return instance.getProcess().getProcessId();
                }
            });
        } catch (Exception e) {
            __log.error("Exception during activity recovery", e);
            throw new ProcessingException("Exception during activity recovery" + e.toString());
        }
        return getInstanceInfo(iid);
    }

    public Collection<Long> delete(String filter) {
        final InstanceFilter instanceFilter = new InstanceFilter(filter);
        final List<Long> ret = new LinkedList<Long>();
        if(garbage(filter)) {
            return ret;
        }
        try {
            _db.exec(new BpelDatabase.Callable<Object>() {
                public Object run(BpelDAOConnection conn) {
                    Collection<ProcessInstanceDAO> instances = conn.instanceQuery(instanceFilter);
                    for (ProcessInstanceDAO instance : instances) {
                        ProcessConf proc = _store.getProcessConfiguration(instance.getProcess().getProcessId());
                        if (proc == null)
                            throw new ProcessNotFoundException("ProcessNotFound:" + instance.getProcess().getProcessId());
                        // delete the instance and all related data
                        instance.delete(EnumSet.allOf(CLEANUP_CATEGORY.class));
                        ret.add(instance.getInstanceId());
                    }
                    return null;
                }
            });
        } catch (Exception e) {
            __log.error("Exception during instance deletion", e);
            throw new ProcessingException("Exception during instance deletion: " + e.toString());
        }
        return ret;
    }

    //
    // EVENT RETRIEVAL
    //
    public List<String> getEventTimeline(String instanceFilter, String eventFilter) {
        final InstanceFilter ifilter = new InstanceFilter(instanceFilter, null, 0);
        final BpelEventFilter efilter = new BpelEventFilter(eventFilter, 0);

        List<Date> tline = dbexec(new BpelDatabase.Callable<List<Date>>() {
            public List<Date> run(BpelDAOConnection session) throws Exception {
                return session.bpelEventTimelineQuery(ifilter, efilter);
            }
        });

        ArrayList<String> ret = new ArrayList<String>(tline.size());
        CollectionsX.transform(ret, tline, new UnaryFunction<Date, String>() {
            public String apply(Date x) {
                return ISO8601DateParser.format(x);
            }
        });
        return ret;
    }

    public EventInfoListDocument listEvents(String instanceFilter, String eventFilter, int maxCount) {
        final InstanceFilter ifilter = new InstanceFilter(instanceFilter, null, 0);
        final BpelEventFilter efilter = new BpelEventFilter(eventFilter, maxCount);
        EventInfoListDocument eid = EventInfoListDocument.Factory.newInstance();
        final TEventInfoList eil = eid.addNewEventInfoList();
        dbexec(new BpelDatabase.Callable<Object>() {
            public Object run(BpelDAOConnection session) throws Exception {
                List<BpelEvent> events = session.bpelEventQuery(ifilter, efilter);
                for (BpelEvent event : events) {
                    TEventInfo tei = eil.addNewEventInfo();
                    fillEventInfo(tei, event);
                }
                return null;
            }
        });
        return eid;
    }

    public ActivityExtInfoListDocument getExtensibilityElements(QName pid, Integer[] aids) {
        ActivityExtInfoListDocument aeild = ActivityExtInfoListDocument.Factory.newInstance();
        TActivitytExtInfoList taeil = aeild.addNewActivityExtInfoList();
        OProcess oprocess = _server._engine.getOProcess(pid);
        if (oprocess == null)
            throw new ProcessNotFoundException("The process \"" + pid + "\" does not exist.");

        for (int aid : aids) {
            OBase obase = oprocess.getChild(aid);
            if (obase != null && obase.debugInfo != null && obase.debugInfo.extensibilityElements != null) {
                for (Map.Entry<QName, Object> entry : obase.debugInfo.extensibilityElements.entrySet()) {
                    TActivityExtInfo taei = taeil.addNewActivityExtInfo();
                    taei.setAiid("" + aid);
                    Object extValue = entry.getValue();
                    if (extValue instanceof Element)
                        taei.getDomNode().appendChild(
                                taei.getDomNode().getOwnerDocument().importNode((Element) extValue, true));
                    else if (extValue instanceof String) {
                        Element valueElmt = taei.getDomNode().getOwnerDocument().createElementNS(
                                entry.getKey().getNamespaceURI(), entry.getKey().getLocalPart());
                        valueElmt.appendChild(taei.getDomNode().getOwnerDocument().createTextNode((String) extValue));
                        taei.getDomNode().appendChild(valueElmt);
                    }
                }
            }
        }
        return aeild;
    }

    /**
     * Get the {@link DebuggerSupport} object for the given process identifier.
     * Debugger support is required for operations that resume execution in some
     * way or manipulate the breakpoints.
     *
     * @param procid
     *            process identifier
     * @return associated debugger support object
     * @throws ManagementException
     */
    protected final DebuggerSupport getDebugger(QName procid) throws ManagementException {
        BpelProcess process = _server._engine._activeProcesses.get(procid);
        if (process == null)
            throw new ProcessNotFoundException("The process \"" + procid + "\" does not exist.");

        return process._debugger;
    }

    /**
     * Get the {@link DebuggerSupport} object for the given instance identifier.
     * Debugger support is required for operations that resume execution in some
     * way or manipulate the breakpoints.
     *
     * @param iid
     *            instance identifier
     * @return associated debugger support object
     * @throws ManagementException
     */
    protected final DebuggerSupport getDebugger(final Long iid) {
        QName processId;
        try {
            processId = _db.exec(new BpelDatabase.Callable<QName>() {
                public QName run(BpelDAOConnection conn) throws Exception {
                    ProcessInstanceDAO instance = conn.getInstance(iid);
                    return instance == null ? null : instance.getProcess().getProcessId();
                }
            });
        } catch (Exception e) {
            __log.error("Exception during instance retrieval", e);
            throw new ProcessingException("Exception during instance retrieval: " + e.toString());
        }

        return getDebugger(processId);
    }

    /**
     * Execute a database transaction, unwrapping nested
     * {@link ManagementException}s.
     *
     * @param runnable
     *            action to run
     * @return
     * @throws ManagementException
     */
    protected <T> T dbexec(BpelProcessDatabase.Callable<T> runnable) throws ManagementException {
        try {
            return runnable.exec();
        } catch (ManagementException me) {
            throw me;
        } catch (Exception e) {
            __log.error("Exception during database operation", e);
            throw new ManagementException("Exception during database operation: " + e.toString());
        }
    }

    /**
     * Execute a database transaction, unwrapping nested
     * {@link ManagementException}s.
     *
     * @param callable
     *            action to run
     * @return
     * @throws ManagementException
     */
    protected <T> T dbexec(BpelDatabase.Callable<T> callable) throws ManagementException {
        try {
            return _db.exec(callable);
        } catch (ManagementException me) {
            // Passthrough.
            throw me;
        } catch (Exception ex) {
            __log.error("Exception during database operation", ex);
            throw new ManagementException("Exception during database operation" + ex.toString());
        }
    }

    private ProcessInfoDocument genProcessInfoDocument(BpelDAOConnection conn, QName procid, ProcessInfoCustomizer custom)
            throws ManagementException {
        if (procid == null) {
            throw new InvalidRequestException("Valid QName as process id expected.");
        }
        ProcessInfoDocument ret = ProcessInfoDocument.Factory.newInstance();
        TProcessInfo pi = ret.addNewProcessInfo();
        try {
            ProcessConf pconf = _store.getProcessConfiguration(procid);
            if (pconf == null)
                throw new ProcessNotFoundException("ProcessNotFound:" + procid);
            fillProcessInfo(pi, pconf, custom);
            fillProcessInfoSummary(conn, Collections.singletonList(pi), custom);
        } catch (ManagementException me) {
            throw me;
        } catch (Exception e) {
            __log.error("Exception while retrieving process information", e);
            throw new ProcessingException("Exception while retrieving process information: " + e.toString());
        }

        return ret;
    }

    /**
     * Generate a {@link InstanceInfoDocument} for a given instance. This
     * document contains general information about the instance.
     *
     * @param iid
     *            instance identifier
     * @return generated document
     */
    private InstanceInfoDocument genInstanceInfoDocument(BpelDAOConnection conn,  Long iid) {
        if (iid == null)
            throw new InvalidRequestException("Must specifiy instance id.");

        InstanceInfoDocument ret = InstanceInfoDocument.Factory.newInstance();
        TInstanceInfo ii = ret.addNewInstanceInfo();

        ii.setIid(iid.toString());
        ProcessInstanceDAO instance = conn.getInstance(iid);
        if (instance == null)
            throw new InstanceNotFoundException("InstanceNotFoundException " + iid);
        // TODO: deal with "ERROR" state information.
        fillInstanceInfo(ii, instance);
        return ret;
    }

    /**
     * Generate a {@link ScopeInfoDocument} for a given scope instance.
     *
     * @param siid
     *            scope instance identifier
     * @param includeActivityInfo
     * @return generated document
     * @throws Exception
     */
    private ScopeInfoDocument genScopeInfoDocument(BpelDAOConnection conn, String siid, boolean includeActivityInfo) throws Exception {
        if (siid == null)
            throw new InvalidRequestException("Must specifiy scope instance id.");

        Long siidl;
        try {
            siidl = new Long(siid);
        } catch (NumberFormatException nfe) {
            throw new InvalidRequestException("Invalid scope instance id.");
        }

        ScopeInfoDocument ret = ScopeInfoDocument.Factory.newInstance();
        TScopeInfo ii = ret.addNewScopeInfo();

        ii.setSiid(siid);
        try {
            ScopeDAO instance = conn.getScope(siidl);
            if (instance == null)
                throw new InvalidRequestException("Scope not found: " + siidl);
            // TODO: deal with "ERROR" state information.
            fillScopeInfo(ii, instance, includeActivityInfo);
        } catch (Exception e) {
            failIfSQLException(e);
            __log.error("Exception while retrieving scope information", e);
        }
        return ret;
    }

    private void fillProcessInfoSummary(BpelDAOConnection conn, List<TProcessInfo> infos, ProcessInfoCustomizer custom) {
        if (custom.includeInstanceSummary()) {
            Set<String> pids = new HashSet<String>();
            for (TProcessInfo i : infos) {
               pids.add(i.getPid());
            }

            Map<InstanceSummaryKey, Long> m = conn.getProcessManagement().countInstancesSummary(pids);
            Map<String, FailedSummaryValue> f = conn.getProcessManagement().findFailedCountAndLastFailedDateForProcessIds(pids);

            for (TProcessInfo info : infos) {
                TInstanceSummary isum = info.addNewInstanceSummary();
                genInstanceSummaryEntry(isum.addNewInstances(), TInstanceStatus.ACTIVE, info.getPid(), m);
                genInstanceSummaryEntry(isum.addNewInstances(), TInstanceStatus.COMPLETED, info.getPid(), m);
                genInstanceSummaryEntry(isum.addNewInstances(), TInstanceStatus.ERROR, info.getPid(), m);
                genInstanceSummaryEntry(isum.addNewInstances(), TInstanceStatus.FAILED, info.getPid(), m);
                genInstanceSummaryEntry(isum.addNewInstances(), TInstanceStatus.SUSPENDED, info.getPid(), m);
                genInstanceSummaryEntry(isum.addNewInstances(), TInstanceStatus.TERMINATED, info.getPid(), m);
                getInstanceSummaryActivityFailure(isum, f, info.getPid());
            }
        }
    }

    /**
     * Fill in the <code>process-info</code> element of the transfer object.
     *
     * @param info
     *            destination XMLBean
     * @param pconf
     *            process configuration object (from store)
     * @param proc
     *            source DAO object
     * @param custom
     *            used to customize the quantity of information produced in the
     *            info
     */
    private void fillProcessInfo(TProcessInfo info, ProcessConf pconf, ProcessInfoCustomizer custom) {
        if (pconf == null)
            throw new IllegalArgumentException("Null pconf.");

        if( __log.isDebugEnabled() ) __log.debug("Filling process info for " + pconf.getProcessId());

        info.setPid(pconf.getProcessId().toString());
        // TODO: ACTIVE and RETIRED should be used separately.
        // Active process may be retired at the same time
        if (pconf.getState() == ProcessState.RETIRED) {
            info.setStatus(TProcessStatus.RETIRED);
        } else {
            info.setStatus(TProcessStatus.ACTIVE);
        }
        info.setVersion(pconf.getVersion());

        TDefinitionInfo definfo = info.addNewDefinitionInfo();
        definfo.setProcessName(pconf.getType());

        TDeploymentInfo depinfo = info.addNewDeploymentInfo();
        depinfo.setPackage(pconf.getPackage());
        if( __log.isDebugEnabled() ) __log.debug(" package name: " + depinfo.getPackage());
        depinfo.setDocument(pconf.getBpelDocument());
        depinfo.setDeployDate(toCalendar(pconf.getDeployDate()));
        depinfo.setDeployer(pconf.getDeployer());

        if (custom.includeDocumentLists()) {
            TProcessInfo.Documents docinfo = info.addNewDocuments();
            List<File> files = pconf.getFiles();
            if (files != null)
                genDocumentInfo(docinfo, files.toArray(new File[files.size()]), true);
            else if (__log.isDebugEnabled())
                __log.debug("fillProcessInfo: No files for " + pconf.getProcessId());
        }

        TProcessProperties properties = info.addNewProperties();
        if (custom.includeProcessProperties()) {
            for (Map.Entry<QName, Node> propEntry : pconf.getProcessProperties().entrySet()) {
                TProcessProperties.Property tprocProp = properties.addNewProperty();
                tprocProp.setName(new QName(propEntry.getKey().getNamespaceURI(), propEntry.getKey().getLocalPart()));
                Node propNode = tprocProp.getDomNode();
                Document processInfoDoc = propNode.getOwnerDocument();
                Node node2append = processInfoDoc.importNode(propEntry.getValue(), true);
                propNode.appendChild(node2append);
            }
        }

        TEndpointReferences eprs = info.addNewEndpoints();
        OProcess oprocess = _server._engine.getOProcess(pconf.getProcessId());
        if (custom.includeEndpoints() && oprocess != null) {
            for (OPartnerLink oplink : oprocess.getAllPartnerLinks()) {
                if (oplink.hasPartnerRole() && oplink.initializePartnerRole) {
                    // TODO: this is very uncool.
                    EndpointReference pepr = _server._engine._activeProcesses.get(pconf.getProcessId())
                            .getInitialPartnerRoleEPR(oplink);

                    if (pepr != null) {
                        TEndpointReferences.EndpointRef epr = eprs.addNewEndpointRef();
                        Document eprNodeDoc = epr.getDomNode().getOwnerDocument();
                        epr.getDomNode().appendChild(eprNodeDoc.importNode(pepr.toXML().getDocumentElement(), true));
                    }
                }
            }
        }

        // TODO: add documents to the above data structure.
    }

    String findVersionStringFromNodeToken(String packageName) {
        int i = packageName.length() - 1;
        while( i > 0 && Character.isDigit(packageName.charAt(i)) ) {
            i--;
        }
        if( i < packageName.length() - 1 && packageName.charAt(i) == '-') {
            return packageName.substring(i + 1);
        }

        return null;
    }

    /**
     * Generate document information elements for a set of files.
     *
     * @param docinfo
     *            target element
     * @param files
     *            files
     * @param recurse
     *            recurse down directories?
     */
    private void genDocumentInfo(TProcessInfo.Documents docinfo, File[] files, boolean recurse) {
        if (files == null)
            return;
        for (File f : files) {
            if (f.isHidden())
                continue;

            if (f.isDirectory()) {
                if (recurse)
                    genDocumentInfo(docinfo, f.listFiles(), true);
            } else if (f.isFile()) {
                genDocumentInfo(docinfo, f);
            }
        }
    }

    private void genDocumentInfo(TProcessInfo.Documents docinfo, File f) {
        DocumentInfoGenerator dig = new DocumentInfoGenerator(f);
        if (dig.isRecognized() && dig.isVisible()) {
            TDocumentInfo doc = docinfo.addNewDocument();

            doc.setName(dig.getName());
            doc.setSource(dig.getURL());
            doc.setType(dig.getType());
        }
    }

    private void genInstanceSummaryEntry(TInstanceSummary.Instances instances,
                                         TInstanceStatus.Enum state, String pid, Map<InstanceSummaryKey, Long> summary)
    {
        instances.setState(state);
        Long count = summary.get(new InstanceSummaryKey(pid, state.toString()));
        instances.setCount(count == null ? 0 : count.intValue());
    }

    private void getInstanceSummaryActivityFailure(TInstanceSummary summary, Map<String, FailedSummaryValue> f, String pid) {
        FailedSummaryValue v = f.get(pid);
        if (v != null) {
            TFailuresInfo failures = summary.addNewFailures();
            failures.setDtFailure(toCalendar(v.lastFailed));
            failures.setCount(v.count.intValue());
        }
    }

    private void fillProperties(TInstanceInfo info, ProcessInstanceDAO instance, Map<QName, String> props) {
        TInstanceInfo.CorrelationProperties corrProperties = info.addNewCorrelationProperties();
        for (Map.Entry<QName, String> property : props.entrySet()) {
            TCorrelationProperty tproperty = corrProperties.addNewCorrelationProperty();
            // not setting correlation-set id here -- too inconvenient for performance
            // tproperty.setCsetid("" + cset.getCorrelationSetId());
            tproperty.setPropertyName(property.getKey());
            tproperty.setStringValue(property.getValue());
        }
    }

    private void fillInstanceSummary(TInstanceInfo info, ProcessInstanceDAO instance) {
        info.setIid("" + instance.getInstanceId());
        ProcessDAO processDAO = instance.getProcess();
        info.setPid(processDAO.getProcessId().toString());
        info.setProcessName(processDAO.getType());
        info.setDtStarted(toCalendar(instance.getCreateTime()));
        info.setDtLastActive(toCalendar(instance.getLastActiveTime()));
        info.setStatus(__psc.cvtInstanceStatus(instance.getState()));
        if (instance.getFault() != null) {
            TFaultInfo faultInfo = info.addNewFaultInfo();
            faultInfo.setName(instance.getFault().getName());
            faultInfo.setExplanation(instance.getFault().getExplanation());
            faultInfo.setAiid(instance.getFault().getActivityId());
            faultInfo.setLineNumber(instance.getFault().getLineNo());
        }
        if (instance.getActivityFailureCount() > 0) {
            TFailuresInfo failures = info.addNewFailures();
            failures.setDtFailure(toCalendar(instance.getActivityFailureDateTime()));
            failures.setCount(instance.getActivityFailureCount());
        }
    }

    private void fillInstanceInfo(TInstanceInfo info, ProcessInstanceDAO instance) {
        fillInstanceSummary(info, instance);

        if (instance.getRootScope() != null)
            info.setRootScope(genScopeRef(instance.getRootScope()));

        ProcessInstanceDAO.EventsFirstLastCountTuple flc = instance.getEventsFirstLastCount();
        TInstanceInfo.EventInfo eventInfo = info.addNewEventInfo();
        if (flc != null) {
            eventInfo.setFirstDtime(toCalendar(flc.first));
            eventInfo.setLastDtime(toCalendar(flc.last));
            eventInfo.setCount(flc.count);
        }

        // Setting valued correlation properties
        if (!instance.getCorrelationSets().isEmpty()) {
            TInstanceInfo.CorrelationProperties corrProperties = info.addNewCorrelationProperties();
            for (CorrelationSetDAO correlationSetDAO : instance.getCorrelationSets()) {
                for (Map.Entry<QName, String> property : correlationSetDAO.getProperties().entrySet()) {
                    TCorrelationProperty tproperty = corrProperties.addNewCorrelationProperty();
                    tproperty.setCsetid("" + correlationSetDAO.getCorrelationSetId());
                    tproperty.setPropertyName(property.getKey());
                    tproperty.setStringValue(property.getValue());
                }
            }
        }
    }

    private void fillScopeInfo(TScopeInfo scopeInfo, ScopeDAO scope, boolean includeActivityInfo) {
        scopeInfo.setSiid("" + scope.getScopeInstanceId());
        scopeInfo.setName(scope.getName());
        if (scope.getParentScope() != null)
            scopeInfo.setParentScopeRef(genScopeRef(scope.getParentScope()));

        scopeInfo.setStatus(__psc.cvtScopeStatus(scope.getState()));

        TScopeInfo.Children children = scopeInfo.addNewChildren();
        for (ScopeDAO i : scope.getChildScopes())
            fillScopeRef(children.addNewChildRef(), i);

        TScopeInfo.Variables vars = scopeInfo.addNewVariables();
        for (XmlDataDAO i : scope.getVariables())
            fillVariableRef(vars.addNewVariableRef(), i);

        // Setting correlations and their valued properties
        if (!scope.getCorrelationSets().isEmpty()) {
            TScopeInfo.CorrelationSets correlationSets = scopeInfo.addNewCorrelationSets();
            for (CorrelationSetDAO correlationSetDAO : scope.getCorrelationSets()) {
                TScopeInfo.CorrelationSets.CorrelationSet correlationSet = correlationSets.addNewCorrelationSet();
                correlationSet.setCsetid("" + correlationSetDAO.getCorrelationSetId());
                correlationSet.setName(correlationSetDAO.getName());
                for (Map.Entry<QName, String> property : correlationSetDAO.getProperties().entrySet()) {
                    TCorrelationProperty tproperty = correlationSet.addNewCorrelationProperty();
                    tproperty.setCsetid("" + correlationSetDAO.getCorrelationSetId());
                    tproperty.setPropertyName(property.getKey());
                    tproperty.setStringValue(property.getValue());
                }
            }

        }

        if (includeActivityInfo) {
            Collection<ActivityRecoveryDAO> recoveries = scope.getProcessInstance().getActivityRecoveries();

            TScopeInfo.Activities activities = scopeInfo.addNewActivities();
            List<BpelEvent> events = scope.listEvents();

            // if event generation was enabled
            if(events!=null && events.size() >0) {
                ActivityStateDocumentBuilder b = new ActivityStateDocumentBuilder();
                for (BpelEvent e : events)
                    b.onEvent(e);
                for (ActivityInfoDocument ai : b.getActivities()) {
                    for (ActivityRecoveryDAO recovery : recoveries) {
                        if (String.valueOf(recovery.getActivityId()).equals(ai.getActivityInfo().getAiid())) {
                            TFailureInfo failure = ai.getActivityInfo().addNewFailure();
                            failure.setReason(recovery.getReason());
                            failure.setDtFailure(toCalendar(recovery.getDateTime()));
                            failure.setActions(recovery.getActions());
                            failure.setRetries(recovery.getRetries());
                            ai.getActivityInfo().setStatus(TActivityStatus.FAILURE);
                        }
                    }
                    activities.addNewActivityInfo().set(ai.getActivityInfo());
                }
            }

            // otherwise at least try to get the information about failed activities
            // TODO: we are losing information about which scope does failed activities belong to
            // as failure table does not have scope id, we would attach every failed activity to process scope
            else {
                if(scope.getParentScope() == null) {
                    for (ActivityRecoveryDAO recovery : recoveries) {
                        ActivityInfoDocument ai = ActivityInfoDocument.Factory.newInstance();
                        ai.addNewActivityInfo().setAiid(String.valueOf(recovery.getActivityId()));
                        ai.getActivityInfo().setType("OActivity");
                        ai.getActivityInfo().setScope(TScopeRef.Factory.newInstance());
                        TFailureInfo failure = ai.getActivityInfo().addNewFailure();
                        failure.setReason(recovery.getReason());
                        failure.setDtFailure(toCalendar(recovery.getDateTime()));
                        failure.setActions(recovery.getActions());
                        failure.setRetries(recovery.getRetries());
                        ai.getActivityInfo().setStatus(TActivityStatus.FAILURE);
                        activities.addNewActivityInfo().set(ai.getActivityInfo());
                    }
                }
            }
        }

        Collection<PartnerLinkDAO> plinks = scope.getPartnerLinks();
        if (plinks.size() > 0) {
            TEndpointReferences refs = scopeInfo.addNewEndpoints();
            for (PartnerLinkDAO plink : plinks) {
                if (plink.getPartnerRoleName() != null && plink.getPartnerRoleName().length() > 0) {
                    TEndpointReferences.EndpointRef ref = refs.addNewEndpointRef();
                    ref.setPartnerLink(plink.getPartnerLinkName());
                    ref.setPartnerRole(plink.getPartnerRoleName());
                    if (plink.getPartnerEPR() != null) {
                        Document eprNodeDoc = ref.getDomNode().getOwnerDocument();
                        ref.getDomNode().appendChild(eprNodeDoc.importNode(plink.getPartnerEPR(), true));
                    }
                }
            }
        }
    }

    private void fillVariableRef(TVariableRef ref, XmlDataDAO i) {
        ref.setIid(i.getScopeDAO().getProcessInstance().getInstanceId().toString());
        ref.setSiid(i.getScopeDAO().getScopeInstanceId().toString());
        ref.setName(i.getName());
    }

    private TScopeRef genScopeRef(ScopeDAO scope) {
        TScopeRef tref = TScopeRef.Factory.newInstance();
        fillScopeRef(tref, scope);
        return tref;
    }

    private void fillScopeRef(TScopeRef tref, ScopeDAO scope) {
        tref.setSiid(scope.getScopeInstanceId().toString());
        tref.setStatus(__psc.cvtScopeStatus(scope.getState()));
        tref.setName(scope.getName());
        tref.setModelId("" + scope.getModelId());
    }

    private void fillEventInfo(TEventInfo info, BpelEvent event) {
        info.setName(BpelEvent.eventName(event));
        info.setType(event.getType().toString());
        info.setLineNumber(event.getLineNo());
        info.setTimestamp(toCalendar(event.getTimestamp()));
        if (event instanceof ActivityEvent) {
            info.setActivityName(((ActivityEvent) event).getActivityName());
            info.setActivityId(((ActivityEvent) event).getActivityId());
            info.setActivityType(((ActivityEvent) event).getActivityType());
            info.setActivityDefinitionId(((ActivityEvent) event).getActivityDeclarationId());
        }
        if (event instanceof CorrelationEvent) {
            info.setPortType(((CorrelationEvent) event).getPortType());
            info.setOperation(((CorrelationEvent) event).getOperation());
            info.setMexId(((CorrelationEvent) event).getMessageExchangeId());
        }
        if (event instanceof CorrelationMatchEvent) {
            info.setPortType(((CorrelationMatchEvent) event).getPortType());
        }
        if (event instanceof CorrelationSetEvent) {
            info.setCorrelationSet(((CorrelationSetEvent) event).getCorrelationSetName());
        }
        if (event instanceof CorrelationSetWriteEvent) {
            info.setCorrelationKey(((CorrelationSetWriteEvent) event).getCorrelationSetName());
        }
        if (event instanceof ExpressionEvaluationEvent) {
            info.setExpression(((ExpressionEvaluationEvent) event).getExpression());
        }
        if (event instanceof ExpressionEvaluationFailedEvent) {
            info.setFault(((ExpressionEvaluationFailedEvent) event).getFault());
        }
        if (event instanceof NewProcessInstanceEvent) {
            if ((((NewProcessInstanceEvent) event).getRootScopeId()) != null)
                info.setRootScopeId(((NewProcessInstanceEvent) event).getRootScopeId());
            info.setScopeDefinitionId(((NewProcessInstanceEvent) event).getScopeDeclarationId());
        }
        if (event instanceof PartnerLinkEvent) {
            info.setPartnerLinkName(((PartnerLinkEvent) event).getpLinkName());
        }
        if (event instanceof ProcessCompletionEvent) {
            info.setFault(((ProcessCompletionEvent) event).getFault());
        }
        if (event instanceof ProcessEvent) {
            info.setProcessId(((ProcessEvent) event).getProcessId());
            info.setProcessType(((ProcessEvent) event).getProcessName());
        }
        if (event instanceof ProcessInstanceEvent) {
            info.setInstanceId(((ProcessInstanceEvent) event).getProcessInstanceId());
        }
        if (event instanceof ProcessInstanceStartedEvent) {
            info.setRootScopeId(((ProcessInstanceStartedEvent) event).getRootScopeId());
            info.setRootScopeDeclarationId(((ProcessInstanceStartedEvent) event).getScopeDeclarationId());
        }
        if (event instanceof ProcessInstanceStateChangeEvent) {
            info.setOldState(((ProcessInstanceStateChangeEvent) event).getOldState());
            info.setNewState(((ProcessInstanceStateChangeEvent) event).getNewState());
        }
        if (event instanceof ProcessMessageExchangeEvent) {
            info.setPortType(((ProcessMessageExchangeEvent) event).getPortType());
            info.setOperation(((ProcessMessageExchangeEvent) event).getOperation());
            info.setMexId(((ProcessMessageExchangeEvent) event).getMessageExchangeId());
        }
        if (event instanceof ScopeCompletionEvent) {
            info.setSuccess(true);
        }
        if (event instanceof ScopeEvent) {
            info.setScopeId(((ScopeEvent) event).getScopeId());
            if (((ScopeEvent) event).getParentScopeId() != null)
                info.setParentScopeId(((ScopeEvent) event).getParentScopeId());
            if (((ScopeEvent) event).getScopeName() != null)
                info.setScopeName(((ScopeEvent) event).getScopeName());
            info.setScopeDefinitionId(((ScopeEvent) event).getScopeDeclarationId());
        }
        if (event instanceof ScopeFaultEvent) {
            info.setFault(((ScopeFaultEvent) event).getFaultType());
            info.setFaultLineNumber(((ScopeFaultEvent) event).getFaultLineNo());
            info.setExplanation(((ScopeFaultEvent) event).getExplanation());
        }
        if (event instanceof VariableEvent) {
            info.setVariableName(((VariableEvent) event).getVarName());
        }
        if(event instanceof VariableModificationEvent) {
            if(((VariableModificationEvent) event).getNewValue()!=null)
                info.setNewValue(DOMUtils.domToString(((VariableModificationEvent) event).getNewValue()));
        }
    }

    /**
     * Convert a {@link Date} to a {@link Calendar}.
     *
     * @param dtime
     *            a {@link Date}
     * @return a {@link Calendar}
     */
    private Calendar toCalendar(Date dtime) {
        if (dtime == null)
            return null;

        Calendar c = (Calendar) _calendar.clone();
        c.setTime(dtime);
        return c;
    }

    /**
     * @see org.apache.ode.bpel.pmapi.InstanceManagement#queryInstances(java.lang.String)
     */
    public InstanceInfoListDocument queryInstances(final String query) {
        InstanceInfoListDocument ret = InstanceInfoListDocument.Factory.newInstance();
        final TInstanceInfoList infolist = ret.addNewInstanceInfoList();
        try {
            _db.exec(new BpelDatabase.Callable<Object>() {
                public Object run(BpelDAOConnection conn) {
                    Collection<ProcessInstanceDAO> instances = conn.instanceQuery(query);
                    for (ProcessInstanceDAO instance : instances) {
                        fillInstanceInfo(infolist.addNewInstanceInfo(), instance);
                    }
                    return null;
                }
            });
        } catch (Exception e) {
            __log.error("Exception while querying instances", e);
            throw new ProcessingException("Exception while querying instances: " + e.toString());
        }
        return ret;
    }

    /**
     * Query processes based on a {@link ProcessFilter} criteria. This is
     * implemented in memory rather than via database calls since the processes
     * are managed by the {@link ProcessStore} object and we don't want to make
     * this needlessly complicated.
     *
     * @param filter
     * @return
     */
    @SuppressWarnings("unchecked")
    Collection<ProcessConf> processQuery(ProcessFilter filter) {

        List<QName> pids = _store.getProcesses();

        // Name filter can be implemented using only the PIDs.
        if (filter != null && filter.getNameFilter() != null) {
            final Pattern pattern = Pattern.compile(filter.getNameFilter().replace("*",".*") + "(-\\d*)?");
            CollectionsX.remove_if(pids, new MemberOfFunction<QName>() {
                @Override
                public boolean isMember(QName o) {
                    return !pattern.matcher(o.getLocalPart()).matches();
                }
            });
        }

        if (filter != null && filter.getNamespaceFilter() != null) {
            final Pattern pattern = Pattern.compile(filter.getNamespaceFilter().replace("*",".*"));
            CollectionsX.remove_if(pids, new MemberOfFunction<QName>() {
                @Override
                public boolean isMember(QName o) {
                    String ns = o.getNamespaceURI() == null ? "" : o.getNamespaceURI();
                    return !pattern.matcher(ns).matches();
                }

            });
        }

        // Now we need the process conf objects, we need to be
        // careful since someone could have deleted them by now
        List<ProcessConf> confs = new LinkedList<ProcessConf>();
        for (QName pid : pids) {
            ProcessConf pconf = _store.getProcessConfiguration(pid);
            confs.add(pconf);
        }

        if (filter != null) {
            // TODO Implement process status filtering when status will exist
            // Specific filter for deployment date.
            if (filter.getDeployedDateFilter() != null) {
                for (final String ddf : filter.getDeployedDateFilter()) {
                    final Date dd;
                    try {
                        dd = ISO8601DateParser.parse(Filter.getDateWithoutOp(ddf));
                    } catch (ParseException e) {
                        // Should never happen.
                        __log.error("Exception while parsing date", e);
                        throw new RuntimeException(e.toString());
                    }

                    CollectionsX.remove_if(confs, new MemberOfFunction<ProcessConf>() {
                        @Override
                        public boolean isMember(ProcessConf o) {

                            if (ddf.startsWith("="))
                                return !o.getDeployDate().equals(dd);

                            if (ddf.startsWith("<="))
                                return o.getDeployDate().getTime() > dd.getTime();

                            if (ddf.startsWith(">="))
                                return o.getDeployDate().getTime() < dd.getTime();

                            if (ddf.startsWith("<"))
                                return o.getDeployDate().getTime() >= dd.getTime();

                            if (ddf.startsWith(">"))
                                return o.getDeployDate().getTime() <= dd.getTime();

                            return false;
                        }

                    });

                }
            }

            // Ordering
            if (filter.getOrders() != null) {
                ComparatorChain cchain = new ComparatorChain();
                for (String key : filter.getOrders()) {
                    boolean ascending = true;
                    String orderKey = key;
                    if (key.startsWith("+") || key.startsWith("-")) {
                        orderKey = key.substring(1, key.length());
                        if (key.startsWith("-"))
                            ascending = false;
                    }

                    Comparator c;
                    if ("name".equals(orderKey))
                        c = new Comparator<ProcessConf>() {
                            public int compare(ProcessConf o1, ProcessConf o2) {
                                return o1.getProcessId().getLocalPart().compareTo(o2.getProcessId().getLocalPart());
                            }
                        };
                    else if ("namespace".equals(orderKey))
                        c = new Comparator<ProcessConf>() {
                            public int compare(ProcessConf o1, ProcessConf o2) {
                                String ns1 = o1.getProcessId().getNamespaceURI() == null ? "" : o1.getProcessId()
                                        .getNamespaceURI();
                                String ns2 = o2.getProcessId().getNamespaceURI() == null ? "" : o2.getProcessId()
                                        .getNamespaceURI();
                                return ns1.compareTo(ns2);
                            }
                        };
                    else if ("version".equals(orderKey))
                        c = new Comparator<ProcessConf>() {
                            public int compare(ProcessConf o1, ProcessConf o2) {
                                // TODO: implement version comparisons.
                                return 0;
                            }
                        };
                    else if ("deployed".equals(orderKey))
                        c = new Comparator<ProcessConf>() {
                            public int compare(ProcessConf o1, ProcessConf o2) {
                                return o1.getDeployDate().compareTo(o2.getDeployDate());
                            }

                        };

                    else {
                        // unrecognized
                        __log.debug("unrecognized order key" + orderKey);
                        continue;
                    }

                    cchain.addComparator(c, !ascending);
                }

                Collections.sort(confs, cchain);
            }

        }

        return confs;
    }

    public ReplayResponseDocument replay(final Replay request) throws ManagementException {
        final Throwable[] e = new Throwable[1];
        try {
            ReplayResponseDocument response = _db.exec(new BpelDatabase.Callable<ReplayResponseDocument>() {
                public ReplayResponseDocument run(BpelDAOConnection conn) throws Exception {
                    try {
                        ReplayResponse response = ReplayResponse.Factory.newInstance();
                        Replayer replayer = new Replayer();
                        List<Long> iids = replayer.replayInstances(request, _server.getEngine(), conn);
                        for (Long iid : iids) {
                            response.addRestoredIID(iid);
                        }
                        ReplayResponseDocument responseDoc = ReplayResponseDocument.Factory.newInstance();
                        responseDoc.setReplayResponse(response);
                        return responseDoc;
                    } catch (Throwable t) {
                        e[0] = t;
                        throw new Exception("", t);
                    }
                }
            });

            if (e[0] != null) {
                __log.debug("throwing pending exception");
                throw e[0];
            }
            return response;
        } catch (Throwable e2) {
            throw new ManagementException("", e2);
        }
    }

    public GetCommunicationResponseDocument getCommunication(final GetCommunication request) throws ManagementException {
        final Throwable[] e = new Throwable[1];
        try {
            GetCommunicationResponseDocument response = _db.exec(new BpelDatabase.Callable<GetCommunicationResponseDocument>() {
                public GetCommunicationResponseDocument run(BpelDAOConnection conn) throws Exception {
                    try {
                        Replayer replayer = new Replayer();

                        GetCommunicationResponseDocument responseDoc = GetCommunicationResponseDocument.Factory.newInstance();
                        responseDoc.setGetCommunicationResponse(replayer.getCommunication(request, conn));
                        return responseDoc;
                    } catch (Throwable e2) {
                        e[0] = e2;
                        throw new Exception("", e2);
                    }
                }
            });
            if (e[0] != null) {
                __log.debug("throwing pending exception");
                throw e[0];
            }
            return response;
        } catch (Throwable e2) {
            throw new ManagementException("", e2);
        }
    }

    private static void failIfSQLException(Exception e) throws Exception {
        String[] fatal = { "org.hibernate", "org.apache.openjpa", "java.sql", "javax.sql" };
        for (int i=0; i<fatal.length; i++) {
            if (e.getClass().getName().startsWith(fatal[i])) throw e;
        }
    }

}
