gt:         if (_announcer.hasInterfaceXListeners()) {
context: /*
   * Copyright (c) 2004-2020 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.commons.lang.StringUtils;
  import org.apache.logging.log4j.LogManager;
  import org.apache.logging.log4j.Logger;
  import org.jdom2.Document;
  import org.jdom2.Element;
  import org.yawlfoundation.yawl.elements.*;
  import org.yawlfoundation.yawl.elements.state.YIdentifier;
  import org.yawlfoundation.yawl.elements.state.YInternalCondition;
  import org.yawlfoundation.yawl.engine.announcement.YAnnouncement;
  import org.yawlfoundation.yawl.engine.announcement.YEngineEvent;
  import org.yawlfoundation.yawl.engine.time.YTimer;
  import org.yawlfoundation.yawl.engine.time.YTimerVariable;
  import org.yawlfoundation.yawl.engine.time.YWorkItemTimer;
  import org.yawlfoundation.yawl.exceptions.YDataStateException;
  import org.yawlfoundation.yawl.exceptions.YPersistenceException;
  import org.yawlfoundation.yawl.exceptions.YQueryException;
  import org.yawlfoundation.yawl.exceptions.YStateException;
  import org.yawlfoundation.yawl.logging.YEventLogger;
  import org.yawlfoundation.yawl.logging.YLogDataItem;
  import org.yawlfoundation.yawl.logging.YLogDataItemList;
  import org.yawlfoundation.yawl.logging.YLogPredicate;
  import org.yawlfoundation.yawl.util.JDOMUtil;
  
  import java.net.URL;
  import java.util.*;
  
  /**
   *
   *
   * @author Lachlan Aldred
   *         Date: 16/04/2003
   *         Time: 16:08:01
   *
   * @author Michael Adams (for v2)
   */
  public class YNetRunner {
  
      public enum ExecutionStatus { Normal, Suspending, Suspended, Resuming }
  
      private static final Logger _logger = LogManager.getLogger(YNetRunner.class);
  
      protected YNet _net;
      private YWorkItemRepository _workItemRepository;
      private Set<YTask> _netTasks;
      private Set<YTask> _enabledTasks = new HashSet<YTask>();
      private Set<YTask> _busyTasks = new HashSet<YTask>();
      private final Set<YTask> _deadlockedTasks = new HashSet<YTask>();
      private YIdentifier _caseIDForNet;
      private YSpecificationID _specID;
      private YCompositeTask _containingCompositeTask;
      private YEngine _engine;
      private YAnnouncer _announcer;
      private boolean _cancelling;
      private Set<String> _enabledTaskNames = new HashSet<String>();
      private Set<String> _busyTaskNames = new HashSet<String>();
      private String _caseID = null;
      private String _containingTaskID = null;
      private YNetData _netdata = null;
      private YAWLServiceReference _caseObserver;
      private long _startTime;
      private Map<String, String> _timerStates;
      private ExecutionStatus _executionStatus;
      private Set<YAnnouncement> _announcements;
  
      // used to persist observers
      private String _caseObserverStr = null ;
  
  
      // Constructors //
  
      protected YNetRunner() {
          _logger.debug("YNetRunner: <init>");
          _engine = YEngine.getInstance();
          init();
      }
  
  
      public YNetRunner(YPersistenceManager pmgr, YNet netPrototype, Element paramsData,
                        String caseID) throws YStateException, YDataStateException, YPersistenceException {
           this();
  
          // initialise and persist case identifier - if caseID is null, a new one is supplied
          _caseIDForNet = new YIdentifier(caseID);   
          if (pmgr != null) pmgr.storeObject(_caseIDForNet);
  
          // get case data from external data gateway, if set for this specification
          Element externalData = netPrototype.getCaseDataFromExternal(_caseIDForNet.toString());
          if (externalData != null) paramsData = externalData;
  
          initialise(pmgr, netPrototype, _caseIDForNet, paramsData) ;
      }
  
      /**
       * Constructor called by a composite task (creating a sub-net runner)
       * @param pmgr
       * @param netPrototype
       * @param container
       * @param caseIDForNet
       * @param incomingData
       * @throws YDataStateException
       * @throws YPersistenceException
       */
      public YNetRunner(YPersistenceManager pmgr, YNet netPrototype,
                        YCompositeTask container, YIdentifier caseIDForNet,
                        Element incomingData)
              throws YDataStateException, YPersistenceException {
  
          this();
          initialise(pmgr, netPrototype, caseIDForNet, incomingData) ;
          _containingCompositeTask = container;
          setContainingTaskID(container.getID());
          if (pmgr != null) pmgr.storeObject(this);
      }
  
  
      private void init() {
          _workItemRepository = _engine.getWorkItemRepository();
          _announcer = _engine.getAnnouncer();
      }
  
      private void initialise(YPersistenceManager pmgr, YNet netPrototype,
                        YIdentifier caseIDForNet, Element incomingData)
              throws YDataStateException, YPersistenceException {
  
          _caseIDForNet = caseIDForNet;
          _caseID = _caseIDForNet.toString();
          _netdata = new YNetData(_caseID);
          _net = (YNet) netPrototype.clone();
          _net.initializeDataStore(pmgr, _netdata);
          _netTasks = new HashSet<YTask>(_net.getNetTasks());
          _specID = _net.getSpecification().getSpecificationID();
          _startTime = System.currentTimeMillis();
          prepare(pmgr);
          if (incomingData != null) _net.setIncomingData(pmgr, incomingData);
          initTimerStates();
          refreshAnnouncements();
          _executionStatus = ExecutionStatus.Normal;
      }
  
  
      private void prepare(YPersistenceManager pmgr) throws YPersistenceException {
          YInputCondition inputCondition = _net.getInputCondition();
          inputCondition.add(pmgr, _caseIDForNet);
          _net.initialise(pmgr);
      }
  
  
      public Set<YAnnouncement> refreshAnnouncements() {
          Set<YAnnouncement> current = new HashSet<>();
          if (_announcements != null) {
              current.addAll(_announcements);
          }
          _announcements = new HashSet<>();
          return current;
      }
  
  
      public boolean equals(Object other) {
          return (other instanceof YNetRunner) &&   // instanceof = false if other is null
                  ((getCaseID() != null) ? getCaseID().equals(((YNetRunner) other).getCaseID())
                  : super.equals(other));
      }
  
      public int hashCode() {
          return (getCaseID() != null) ? getCaseID().hashCode() : super.hashCode();
      }
  
  
      /******************************************************************************/
  
      public void setContainingTask(YCompositeTask task) {
          _containingCompositeTask = task;
      }
  
      public String getContainingTaskID() {
          return _containingTaskID;
      }
  
      public void setContainingTaskID(String taskid) {
          _containingTaskID = taskid;
      }
  
      public void setNet(YNet net) {
          _net = net;
          _specID = net.getSpecification().getSpecificationID();
          _net.restoreData(_netdata);
          _netTasks = new HashSet<YTask>(_net.getNetTasks());
      }
  
      public YNet getNet() {
          return _net;
      }
  
      public void setEngine(YEngine engine) {
          _engine = engine;
          init();
      }
  
      public YSpecificationID getSpecificationID() {
          return _specID;
      }
  
      public void setSpecificationID(YSpecificationID id) {
          _specID = id;
      }
  
      public YNetData getNetData() {
          return _netdata;
      }
  
      public void setNetData(YNetData data) {
          _netdata = data;
      }
  
      public YIdentifier get_caseIDForNet() {
          return _caseIDForNet;
      }
  
      public void set_caseIDForNet(YIdentifier id) {
          this._caseIDForNet = id;
          _caseID = _caseIDForNet.toString();
      }
  
  
      public void addBusyTask(YTask ext) {
          _busyTasks.add(ext);
      }
  
      public void addEnabledTask(YTask ext) {
          _enabledTasks.add(ext);
      }
  
      public void removeActiveTask(YPersistenceManager pmgr, YTask task) throws YPersistenceException {
          _busyTasks.remove(task);
          _busyTaskNames.remove(task.getID());
          _enabledTasks.remove(task);
          _enabledTaskNames.remove(task.getID());
          if (pmgr != null) pmgr.updateObject(this);
      }
  
  
      public String get_caseID() {
          return this._caseID;
      }
  
      public void set_caseID(String ID) {
          this._caseID = ID;
      }
  
      public Set<String> getEnabledTaskNames() {
          return _enabledTaskNames;
      }
  
      public Set<String> getBusyTaskNames() {
          return _busyTaskNames;
      }
  
      protected void setEnabledTaskNames(Set<String> names) { _enabledTaskNames = names; }
  
      protected void setBusyTaskNames(Set<String> names) { _busyTaskNames = names; }
  
  
      public long getStartTime() { return _startTime; }
  
      public void setStartTime(long time) { _startTime = time ; }
  
      
      /************************************************/
  
  
      public void start(YPersistenceManager pmgr)
              throws YPersistenceException, YDataStateException,
                     YQueryException, YStateException {
          kick(pmgr);
      }
  
  
      public boolean isAlive() {
          return ! _cancelling;
      }
  
      /**
       * Assumption: this will only get called AFTER a workitem has been progressed?
       * Because if it is called any other time then it will cause the case to stop.
       * @param pmgr
       * @throws YPersistenceException
       */
      public synchronized void kick(YPersistenceManager pmgr)
              throws YPersistenceException, YDataStateException,
                     YQueryException, YStateException {
          _logger.debug("--> YNetRunner.kick");
  
          if (! continueIfPossible(pmgr)) {
              _logger.debug("YNetRunner not able to continue");
  
              // if root net can't continue it means a case completion
              if ((_engine != null) && isRootNet()) {
                  announceCaseCompletion();
                  if (endOfNetReached() && warnIfNetNotEmpty()) {
                      _cancelling = true;                       // flag its not a deadlock                                   
                  }
  
                  // call the external data source, if its set for this specification
                  _net.postCaseDataToExternal(getCaseID().toString());
  
                  _logger.debug("Asking engine to finish case");
                  _engine.removeCaseFromCaches(_caseIDForNet);
  
                  // log it
                  YLogPredicate logPredicate = getNet().getLogPredicate();
                  YLogDataItemList logData = null;
                  if (logPredicate != null) {
                      String predicate = logPredicate.getParsedCompletionPredicate(getNet());
                      if (predicate != null) {
                          logData = new YLogDataItemList(new YLogDataItem("Predicate",
                                       "OnCompletion", predicate, "string"));
                      }
                  }                
                  YEventLogger.getInstance().logNetCompleted(_caseIDForNet, logData);
              }
              if (! _cancelling && deadLocked()) notifyDeadLock(pmgr);
              cancel(pmgr);
              if ((_engine != null) && isRootNet()) _engine.clearCaseFromPersistence(_caseIDForNet);
          }
  
          _logger.debug("<-- YNetRunner.kick");
      }
  
  
      private void announceCaseCompletion() {
          _announcer.announceCaseCompletion(_caseObserver, _caseIDForNet, _net.getOutputData());
  
          // notify exception checkpoint to listeners if any (post's for case end)
          if (_announcer.hasInterfaceXListeners()) {
              Document data = _net.getInternalDataDocument();
              _announcer.announceCheckCaseConstraints(_specID, _caseID,
                      JDOMUtil.documentToString(data), false);
          }
      }
  
  
      private boolean isRootNet() {
          return _caseIDForNet.getParent() == null;
      }
  
  
      private void notifyDeadLock(YPersistenceManager pmgr)
              throws YPersistenceException {
          for (Object o : _caseIDForNet.getLocations()) {
              if (o instanceof YExternalNetElement) {
                  YExternalNetElement element = (YExternalNetElement) o;
                  if (_net.getNetElements().values().contains(element)) {
                      if (element instanceof YTask) {
                          createDeadlockItem(pmgr, (YTask) element);
                      }
                      Set<YExternalNetElement> postset = element.getPostsetElements();
                      for (YExternalNetElement postsetElement : postset) {
                          createDeadlockItem(pmgr, (YTask) postsetElement);
                      }
                  }
              }
          }
          _announcer.announceDeadlock(_caseIDForNet, _deadlockedTasks);
      }
  
  
      private void createDeadlockItem(YPersistenceManager pmgr, YTask task)
              throws YPersistenceException {
          if (! _deadlockedTasks.contains(task)) {
              _deadlockedTasks.add(task);
  
              // create a new deadlocked workitem so that the deadlock can be logged
              new YWorkItem(pmgr, _net.getSpecification().getSpecificationID(), task,
                      new YWorkItemID(_caseIDForNet, task.getID()), false, true);
  
              YProblemEvent event  = new YProblemEvent(_net, "Deadlocked",
                                                       YProblemEvent.RuntimeError);
              event.logProblem(pmgr);
          }
      }
  
  
      /**
       * Assumption: there are no enabled tasks
       * @return if deadlocked
       */
      private boolean deadLocked() {
          for (YNetElement location : _caseIDForNet.getLocations()) {
              if (location instanceof YExternalNetElement) {
                  if (((YExternalNetElement) location).getPostsetElements().size() > 0) {
                      return true;
                  }
              }
          }
          return false;
      }
  
  
      private synchronized void processCompletedSubnet(YPersistenceManager pmgr,
                                                       YIdentifier caseIDForSubnet,
                                                       YCompositeTask busyCompositeTask,
                                                       Document rawSubnetData)
              throws YDataStateException, YStateException, YQueryException,
              YPersistenceException {
  
          _logger.debug("--> processCompletedSubnet");
  
          if (caseIDForSubnet == null) throw new RuntimeException();
  
          if (busyCompositeTask.t_complete(pmgr, caseIDForSubnet, rawSubnetData)) {
              _busyTasks.remove(busyCompositeTask);
              if (pmgr != null) pmgr.updateObject(this);
              logCompletingTask(caseIDForSubnet, busyCompositeTask);
  
              //check to see if completing this task resulted in completing the net.
              if (endOfNetReached()) {
                  if (_containingCompositeTask != null) {
                      YNetRunner parentRunner = _engine.getNetRunner(_caseIDForNet.getParent());
                      if ((parentRunner != null) && _containingCompositeTask.t_isBusy()) {
                          parentRunner.setEngine(_engine);           // added to avoid NPE
                          Document dataDoc = _net.usesSimpleRootData() ?
                                      _net.getInternalDataDocument() :
                                      _net.getOutputData() ;
                          parentRunner.processCompletedSubnet(pmgr, _caseIDForNet,
                                      _containingCompositeTask, dataDoc);
                      }
                  }
              }
              kick(pmgr);
          }
          _logger.debug("<-- processCompletedSubnet");
      }
  
  
      public List<YIdentifier> attemptToFireAtomicTask(YPersistenceManager pmgr, String taskID)
              throws YDataStateException, YStateException, YQueryException,
                     YPersistenceException {
          YAtomicTask task = (YAtomicTask) _net.getNetElement(taskID);
          if (task.t_enabled(_caseIDForNet)) {
              List<YIdentifier> newChildIdentifiers = task.t_fire(pmgr);
              _enabledTasks.remove(task);
              _enabledTaskNames.remove(task.getID());
              _busyTasks.add(task);
              _busyTaskNames.add(task.getID());
              if (pmgr != null) pmgr.updateObject(this);
              _logger.debug("NOTIFYING RUNNER");
              kick(pmgr);
              return newChildIdentifiers;
          }
          throw new YStateException("Task is not (or no longer) enabled: " + taskID);
      }
  
  
      public synchronized YIdentifier addNewInstance(YPersistenceManager pmgr,
                                                     String taskID,
                                                     YIdentifier aSiblingInstance,
                                                     Element newInstanceData)
              throws YDataStateException, YStateException, YQueryException,
                     YPersistenceException {
          YAtomicTask task = (YAtomicTask) _net.getNetElement(taskID);
          if (task.t_isBusy()) {
              return task.t_add(pmgr, aSiblingInstance, newInstanceData);
          }
          return null;
      }
  
  
      public void startWorkItemInTask(YPersistenceManager pmgr, YWorkItem workItem)
              throws YDataStateException, YPersistenceException,
                     YQueryException, YStateException {
          startWorkItemInTask(pmgr, workItem.getCaseID(), workItem.getTaskID());
      }
  
  
      public synchronized void startWorkItemInTask(YPersistenceManager pmgr,
                                                   YIdentifier caseID, String taskID)
              throws YDataStateException, YPersistenceException,
                     YQueryException, YStateException {
          YAtomicTask task = (YAtomicTask) _net.getNetElement(taskID);
          task.t_start(pmgr, caseID);
      }
  
  
      public boolean completeWorkItemInTask(YPersistenceManager pmgr, YWorkItem workItem,
                                            Document outputData)
              throws YDataStateException, YStateException, YQueryException,
                     YPersistenceException {
          return completeWorkItemInTask(pmgr, workItem, workItem.getCaseID(),
                  workItem.getTaskID(), outputData);
      }
  
  
      public synchronized boolean completeWorkItemInTask(YPersistenceManager pmgr,
                                                         YWorkItem workItem,
                                                         YIdentifier caseID,
                                                         String taskID,
                                                         Document outputData)
              throws YDataStateException, YStateException, YQueryException,
                     YPersistenceException {
          _logger.debug("--> completeWorkItemInTask");
          YAtomicTask task = (YAtomicTask) _net.getNetElement(taskID);
          boolean success = completeTask(pmgr, workItem, task, caseID, outputData);
  
          // notify exception checkpoint to service if available
          if (_announcer.hasInterfaceXListeners()) {
              _announcer.announceCheckWorkItemConstraints(
                      workItem, _net.getInternalDataDocument(), false);
          }
          _logger.debug("<-- completeWorkItemInTask");
          return success;
      }
  
  
      public synchronized boolean continueIfPossible(YPersistenceManager pmgr)
             throws YDataStateException, YStateException, YQueryException,
                    YPersistenceException {
          _logger.debug("--> continueIfPossible");
  
          // Check if we are suspending (or suspended?) and if so exit out as we
          // shouldn't post new workitems
          if (isInSuspense()) {
              _logger.debug("Aborting runner continuation as case is currently suspending/suspended");
              return true;
          }
  
          // don't continue if the net has already finished
          if (isCompleted()) return false;
  
          // storage for the running set of enabled tasks
          YEnabledTransitionSet enabledTransitions = new YEnabledTransitionSet();
  
          // iterate through the full set of tasks for the net
          for (YTask task : _netTasks) {
  
              // if this task is an enabled 'transition'
              if (task.t_enabled(_caseIDForNet)) {
                  if (! (_enabledTasks.contains(task) || _busyTasks.contains(task)))
                      enabledTransitions.add(task) ;
              }
              else {
  
                  // if the task is not (or no longer) an enabled transition, and it
                  // has been previously enabled by the engine, then it must be withdrawn
                  if (_enabledTasks.contains(task)) {
                      withdrawEnabledTask(task, pmgr);
                  }
              }
  
              if (task.t_isBusy() && !_busyTasks.contains(task)) {
                  _logger.error("Throwing RTE for lists out of sync");
                  throw new RuntimeException("Busy task list out of synch with a busy task: "
                          + task.getID() + " busy tasks: " + _busyTasks);
              }
          }
  
          // fire the set of enabled 'transitions' (if any)
          if (! enabledTransitions.isEmpty()) fireTasks(enabledTransitions, pmgr);
  
          _busyTasks = _net.getBusyTasks();
          _logger.debug("<-- continueIfPossible");
  
          return hasActiveTasks();
      }
  
  
      private void fireTasks(YEnabledTransitionSet enabledSet, YPersistenceManager pmgr)
              throws YDataStateException, YStateException, YQueryException,
                     YPersistenceException {
          Set<YTask> enabledTasks = new HashSet<YTask>();
  
          // A TaskGroup is a group of tasks that are all enabled by a single condition.
          // If the group has more than one task, it's a deferred choice, in which case:
          // 1. If any are composite, fire one (chosen randomly) - rest are withdrawn
          // 2. Else, if any are empty, fire one (chosen randomly) - rest are withdrawn
          // 3. Else, fire and announce all enabled atomic tasks to the environment
          for (YEnabledTransitionSet.TaskGroup group : enabledSet.getAllTaskGroups()) {
              if (group.hasCompositeTasks()) {
                  YCompositeTask composite = group.getRandomCompositeTaskFromGroup();
                  if (! (enabledTasks.contains(composite) || endOfNetReached())) {
                      fireCompositeTask(composite, pmgr);
                      enabledTasks.add(composite);
                  }
              }
              else if (group.hasEmptyTasks()) {
                  YAtomicTask atomic = group.getRandomEmptyTaskFromGroup();
                  if (! (enabledTasks.contains(atomic) || endOfNetReached())) {
                      processEmptyTask(atomic, pmgr);
                  }
              }
              else {
                  String groupID = group.getDeferredChoiceID();       // null if <2 tasks
                  for (YAtomicTask atomic : group.getAtomicTasks()) {
                      if (! (enabledTasks.contains(atomic) || endOfNetReached())) {
                          YAnnouncement announcement = fireAtomicTask(atomic, groupID, pmgr);
                          if (announcement != null) {
                              _announcements.add(announcement);
                          }    
                          enabledTasks.add(atomic) ;
                      }
                  }
              }
          }
      }
  
  
      private YAnnouncement fireAtomicTask(YAtomicTask task, String groupID,
                                           YPersistenceManager pmgr)
              throws YDataStateException, YStateException, YQueryException,
              YPersistenceException {
  
          _enabledTasks.add(task);
          _enabledTaskNames.add(task.getID());
          YWorkItem item = createEnabledWorkItem(pmgr, _caseIDForNet, task);
          if (groupID != null) item.setDeferredChoiceGroupID(groupID);
          if (pmgr != null) pmgr.updateObject(this);
  
          YAWLServiceGateway wsgw = (YAWLServiceGateway) task.getDecompositionPrototype();
          YAnnouncement announcement = _announcer.createAnnouncement(wsgw.getYawlService(),
                  item, YEngineEvent.ITEM_ADD);
  
  