public void acquireTransactionLocks() {
   if (Boolean.parseBoolean(
       _configProperties.getProperty("ode.acquireTransactionLocks", "false"))) {
     _contexts.scheduler.acquireTransactionLocks();
   }
 }
/**
 * The BPEL server implementation.
 *
 * <p>This implementation is intended to be thread safe. The key concurrency mechanism is a
 * "management" read/write lock that synchronizes all management operations (they require "write"
 * access) and prevents concurrent management operations and processing (processing requires "read"
 * access). Write access to the lock is scoped to the method, while read access is scoped to a
 * transaction.
 *
 * @author Maciej Szefler <mszefler at gmail dot com>
 * @author Matthieu Riou <mriou at apache dot org>
 */
public class BpelServerImpl implements BpelServer, Scheduler.JobProcessor {
  private static final Log __log = LogFactory.getLog(BpelServerImpl.class);

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

  /** Maximum age of a process before it is quiesced */
  private static Long __processMaxAge;

  public static final String DEFERRED_PROCESS_INSTANCE_CLEANUP_DISABLED_NAME =
      "org.apache.ode.disable.deferredProcessInstanceCleanup";

  private static boolean DEFERRED_PROCESS_INSTANCE_CLEANUP_DISABLED =
      Boolean.getBoolean(DEFERRED_PROCESS_INSTANCE_CLEANUP_DISABLED_NAME);

  /**
   * Set of processes that are registered with the server. Includes hydrated and dehydrated
   * processes. Guarded by _mngmtLock.writeLock().
   */
  private final Set<BpelProcess> _registeredProcesses = new HashSet<BpelProcess>();

  private State _state = State.SHUTDOWN;
  private final Contexts _contexts = new Contexts();
  private Properties _configProperties;
  private DehydrationPolicy _dehydrationPolicy;
  private boolean _hydrationLazy;
  private int _hydrationLazyMinimumSize;
  private int _migrationTransactionTimeout;
  private Thread processDefReaper;

  BpelEngineImpl _engine;
  protected BpelDatabase _db;

  /**
   * Management lock for synchronizing management operations and preventing processing
   * (transactions) from occuring while management operations are in progress.
   */
  private ReadWriteLock _mngmtLock = new ReentrantReadWriteLock();

  static {
    // TODO Clean this up and factorize engine configuration
    try {
      String processMaxAge = System.getProperty("ode.process.maxage");
      if (processMaxAge != null && processMaxAge.length() > 0) {
        __processMaxAge = Long.valueOf(processMaxAge);
        __log.info("Process definition max age adjusted. Max age = " + __processMaxAge + "ms.");
      }
    } catch (Throwable t) {
      if (__log.isDebugEnabled()) {
        __log.debug("Could not parse ode.process.maxage environment variable.", t);
      } else {
        __log.info("Could not parse ode.process.maxage environment variable; reaping disabled.");
      }
    }
  }

  private enum State {
    SHUTDOWN,
    INIT,
    RUNNING
  }

  public BpelServerImpl() {}

  public Contexts getContexts() {
    return _contexts;
  }

  public void start() {
    _mngmtLock.writeLock().lock();
    try {
      if (!checkState(State.INIT, State.RUNNING)) {
        __log.debug("start() ignored -- already started");
        return;
      }
      __log.debug("BPEL SERVER starting.");

      // Eventually running some migrations before starting
      if (!(new MigrationHandler(_contexts)
          .migrate(_registeredProcesses, _migrationTransactionTimeout))) {
        throw new BpelEngineException(
            "An error occurred while migrating your database to a newer version of the server. Please make sure that the required migration scripts have been executed before starting the server.");
      }

      _state = State.RUNNING;
      __log.info(__msgs.msgServerStarted());
      if (_dehydrationPolicy != null) {
        processDefReaper = new Thread(new ProcessDefReaper(), "Dehydrator");
        processDefReaper.setDaemon(true);
        processDefReaper.start();
      }
    } finally {
      _mngmtLock.writeLock().unlock();
    }
  }

  public void registerExternalVariableEngine(ExternalVariableModule eve) {
    _contexts.externalVariableEngines.put(eve.getName(), eve);
  }

  /**
   * Register a global listener to receive {@link BpelEvent}s froom all processes.
   *
   * @param listener
   */
  public void registerBpelEventListener(BpelEventListener listener) {
    // Do not synchronize, eventListeners is copy-on-write array.
    listener.startup(_configProperties);
    _contexts.eventListeners.add(listener);
  }

  /**
   * Unregister a global listener from receive {@link BpelEvent}s from all processes.
   *
   * @param listener
   */
  public void unregisterBpelEventListener(BpelEventListener listener) {
    // Do not synchronize, eventListeners is copy-on-write array.
    try {
      listener.shutdown();
    } catch (Exception e) {
      __log.warn(
          "Stopping BPEL event listener "
              + listener.getClass().getName()
              + " failed, nevertheless it has been unregistered.",
          e);
    } finally {
      _contexts.eventListeners.remove(listener);
    }
  }

  private void unregisterBpelEventListeners() {
    for (BpelEventListener l : _contexts.eventListeners) {
      unregisterBpelEventListener(l);
    }
  }

  public void stop() {
    _mngmtLock.writeLock().lock();
    try {
      if (!checkState(State.RUNNING, State.INIT)) {
        __log.debug("stop() ignored -- already stopped");
        return;
      }

      __log.debug("BPEL SERVER STOPPING");

      if (processDefReaper != null) {
        processDefReaper.interrupt();
        processDefReaper = null;
      }
      _contexts.scheduler.stop();
      _engine = null;
      _state = State.INIT;
      __log.info(__msgs.msgServerStopped());
    } finally {
      _mngmtLock.writeLock().unlock();
    }
  }

  public void init() throws BpelEngineException {
    _mngmtLock.writeLock().lock();
    try {
      if (!checkState(State.SHUTDOWN, State.INIT)) return;

      __log.debug("BPEL SERVER initializing ");

      _db = new BpelDatabase(_contexts.dao, _contexts.scheduler);
      _state = State.INIT;

      _engine = createBpelEngineImpl(_contexts);
    } finally {
      _mngmtLock.writeLock().unlock();
    }
  }

  // enable extensibility
  protected BpelEngineImpl createBpelEngineImpl(Contexts contexts) {
    return new BpelEngineImpl(contexts);
  }

  public void shutdown() throws BpelEngineException {
    _mngmtLock.writeLock().lock();
    try {
      stop();
      unregisterBpelEventListeners();

      _db = null;
      _engine = null;
      _state = State.SHUTDOWN;
    } finally {
      _mngmtLock.writeLock().unlock();
    }
  }

  public BpelEngine getEngine() {
    boolean registered = false;
    _mngmtLock.readLock().lock();
    try {
      _contexts.scheduler.registerSynchronizer(
          new Synchronizer() {
            public void afterCompletion(boolean success) {
              _mngmtLock.readLock().unlock();
            }

            public void beforeCompletion() {}
          });
      registered = true;
    } finally {
      // If we failed to register the synchro,then there was an ex/throwable; we need to unlock now.
      if (!registered) _mngmtLock.readLock().unlock();
    }
    return _engine;
  }

  /**
   * This is the unsecured version of above method getEngine. Please use with care. This is a hack
   * to make BPS instance management work. TODO: Need to refactor this code in Apache ODE to make it
   * more flexible to manage process engine.
   *
   * @return
   */
  public BpelEngine getEngineUnsecured() {
    return _engine;
  }

  public void register(ProcessConf conf) {
    if (conf == null)
      throw new NullPointerException("must specify non-null process configuration.");

    __log.debug("register: " + conf.getProcessId());

    // Ok, IO out of the way, we will mod the server state, so need to get a
    // lock.
    try {
      _mngmtLock.writeLock().lockInterruptibly();
    } catch (InterruptedException ie) {
      __log.debug("register(...) interrupted.", ie);
      throw new BpelEngineException(__msgs.msgOperationInterrupted());
    }

    try {
      // If the process is already active, do nothing.
      if (_engine.isProcessRegistered(conf.getProcessId())) {
        __log.debug(
            "skipping doRegister" + conf.getProcessId() + ") -- process is already registered");
        return;
      }

      __log.debug("Registering process " + conf.getProcessId() + " with server.");

      BpelProcess process = createBpelProcess(conf);
      process._classLoader = Thread.currentThread().getContextClassLoader();

      _engine.registerProcess(process);
      _registeredProcesses.add(process);
      if (!isLazyHydratable(process)) {
        process.hydrate();
      } else {
        _engine.setProcessSize(process.getPID(), false);
      }

      __log.info(__msgs.msgProcessRegistered(conf.getProcessId()));

    } catch (Exception ex) {
      __log.error(ex);
      throw new BpelEngineException(ex);
    } finally {
      _mngmtLock.writeLock().unlock();
    }
  }

  private boolean isLazyHydratable(BpelProcess process) {
    if (process.isHydrationLazySet()) {
      return process.isHydrationLazy();
    }
    if (!_hydrationLazy) {
      return false;
    }
    return process.getEstimatedHydratedSize() < _hydrationLazyMinimumSize;
  }

  // enable extensibility
  protected BpelProcess createBpelProcess(ProcessConf conf) {
    return new BpelProcess(conf);
  }

  public void unregister(QName pid) throws BpelEngineException {
    if (__log.isTraceEnabled()) __log.trace("unregister: " + pid);

    try {
      _mngmtLock.writeLock().lockInterruptibly();
    } catch (InterruptedException ie) {
      __log.debug("unregister() interrupted.", ie);
      throw new BpelEngineException(__msgs.msgOperationInterrupted());
    }

    try {
      BpelProcess p;
      if (_engine != null) {
        p = _engine.unregisterProcess(pid);
        if (p != null) {
          _registeredProcesses.remove(p);
          XslTransformHandler.getInstance().clearXSLSheets(p.getProcessType());
          __log.info(__msgs.msgProcessUnregistered(pid));
        }
      }
    } catch (Exception ex) {
      __log.error(__msgs.msgProcessUnregisterFailed(pid), ex);
      throw new BpelEngineException(ex);
    } finally {
      _mngmtLock.writeLock().unlock();
    }
  }

  /**
   * Register a global message exchange interceptor.
   *
   * @param interceptor message-exchange interceptor
   */
  public void registerMessageExchangeInterceptor(MessageExchangeInterceptor interceptor) {
    // NOTE: do not synchronize, globalInterceptors is copy-on-write.
    _contexts.globalInterceptors.add(interceptor);
  }

  /**
   * Unregister a global message exchange interceptor.
   *
   * @param interceptor message-exchange interceptor
   */
  public void unregisterMessageExchangeInterceptor(MessageExchangeInterceptor interceptor) {
    // NOTE: do not synchronize, globalInterceptors is copy-on-write.
    _contexts.globalInterceptors.remove(interceptor);
  }

  /** Check a state transition from state "i" to state "j". */
  private boolean checkState(State i, State j) {
    if (_state == i) return true;
    if (_state == j) return false;
    return false;
  }

  protected boolean deleteProcessDAO(final QName pid, boolean isInMemory) {
    try {
      if (isInMemory) {
        return deleteProcessDAO(_contexts.inMemDao.getConnection(), pid);
      } else {
        return _db.exec(
            new BpelDatabase.Callable<Boolean>() {
              public Boolean run(BpelDAOConnection conn) throws Exception {
                return deleteProcessDAO(conn, pid);
              }
            });
      }
    } catch (BpelEngineException re) {
      throw re;
    } catch (Exception e) {
      throw new BpelEngineException(e);
    }
  }

  private boolean deleteProcessDAO(BpelDAOConnection conn, QName pid) {
    final ProcessDAO proc = conn.getProcess(pid);
    if (proc != null) {
      // delete routes
      if (__log.isDebugEnabled()) __log.debug("Deleting only the process " + pid + "...");
      proc.deleteProcessAndRoutes();
      if (__log.isInfoEnabled()) __log.info("Deleted only the process " + pid + ".");
      // we do deferred instance cleanup only for hibernate, for now
      if (proc instanceof DeferredProcessInstanceCleanable
          && !DEFERRED_PROCESS_INSTANCE_CLEANUP_DISABLED) {
        // schedule deletion of process runtime data
        _engine._contexts.scheduler.scheduleMapSerializableRunnable(
            new ProcessCleanUpRunnable(((DeferredProcessInstanceCleanable) proc).getPidId()),
            new Date());
      } else if (proc instanceof DeferredProcessInstanceCleanable) {
        ((DeferredProcessInstanceCleanable) proc).deleteInstances(Integer.MAX_VALUE);
      }
      return true;
    }
    return false;
  }

  public void onScheduledJob(JobInfo jobInfo) throws JobProcessorException {
    getEngine().onScheduledJob(jobInfo);
  }

  private class ProcessDefReaper implements Runnable {
    public void run() {
      __log.debug("Starting process definition reaper thread.");
      long pollingTime = 10000;
      try {
        while (true) {
          Thread.sleep(pollingTime);
          if (!_mngmtLock.writeLock().tryLock(100L, TimeUnit.MILLISECONDS)) continue;
          try {
            __log.debug("Kicking reaper, OProcess instances: " + OProcess.instanceCount);
            // Copying the runnning process list to avoid synchronization
            // problems and a potential mess if a policy modifies the list
            List<BpelProcess> candidates = new ArrayList<BpelProcess>(_registeredProcesses);
            CollectionsX.remove_if(
                candidates,
                new MemberOfFunction<BpelProcess>() {
                  public boolean isMember(BpelProcess o) {
                    return !o.hintIsHydrated();
                  }
                });

            // And the happy winners are...
            List<BpelProcess> ripped = _dehydrationPolicy.markForDehydration(candidates);
            // Bye bye
            for (BpelProcess process : ripped) {
              __log.debug("Dehydrating process " + process.getPID());
              process.dehydrate();
            }
          } finally {
            _mngmtLock.writeLock().unlock();
          }
        }
      } catch (InterruptedException e) {
        __log.debug(e);
      }
    }
  }

  public void setDehydrationPolicy(DehydrationPolicy dehydrationPolicy) {
    _dehydrationPolicy = dehydrationPolicy;
  }

  public void setConfigProperties(Properties configProperties) {
    _configProperties = configProperties;
  }

  public void setMessageExchangeContext(MessageExchangeContext mexContext)
      throws BpelEngineException {
    _contexts.mexContext = mexContext;
  }

  public void setScheduler(Scheduler scheduler) throws BpelEngineException {
    _contexts.scheduler = scheduler;
  }

  public void setCronScheduler(CronScheduler cronScheduler) throws BpelEngineException {
    _contexts.cronScheduler = cronScheduler;
  }

  public void setEndpointReferenceContext(EndpointReferenceContext eprContext)
      throws BpelEngineException {
    _contexts.eprContext = eprContext;
  }

  /**
   * Set the DAO connection factory. The DAO is used by the BPEL engine to persist information about
   * active processes.
   *
   * @param daoCF {@link BpelDAOConnectionFactory} implementation.
   */
  public void setDaoConnectionFactory(BpelDAOConnectionFactory daoCF) throws BpelEngineException {
    JdbcDelegate.setGlobalDAOConnFac(daoCF);
    _contexts.dao = daoCF;
  }

  public void setInMemDaoConnectionFactory(BpelDAOConnectionFactory daoCF) {
    _contexts.inMemDao = daoCF;
  }

  public void setBindingContext(BindingContext bc) {
    _contexts.bindingContext = bc;
  }

  public DebuggerContext getDebugger(QName pid) throws BpelEngineException {
    return _engine._activeProcesses.get(pid)._debugger;
  }

  public boolean hasActiveInstances(final QName pid) {
    try {
      return _db.exec(
          new BpelDatabase.Callable<Boolean>() {
            public Boolean run(BpelDAOConnection conn) throws Exception {
              return conn.getNumInstances(pid) > 0;
            }
          });
    } catch (BpelEngineException re) {
      throw re;
    } catch (Exception e) {
      throw new BpelEngineException(e);
    }
  }

  public void setHydrationLazy(boolean hydrationLazy) {
    this._hydrationLazy = hydrationLazy;
  }

  public void setProcessThrottledMaximumSize(long hydrationThrottledMaximumSize) {
    _engine.setProcessThrottledMaximumSize(hydrationThrottledMaximumSize);
  }

  public void setProcessThrottledMaximumCount(int hydrationThrottledMaximumCount) {
    _engine.setProcessThrottledMaximumCount(hydrationThrottledMaximumCount);
  }

  public void setHydrationLazyMinimumSize(int hydrationLazyMinimumSize) {
    this._hydrationLazyMinimumSize = hydrationLazyMinimumSize;
  }

  public void setInstanceThrottledMaximumCount(int instanceThrottledMaximumCount) {
    _engine.setInstanceThrottledMaximumCount(instanceThrottledMaximumCount);
  }

  public BpelDatabase getBpelDb() {
    return _db;
  }

  /**
   * A polled runnable instance that implements this interface will be set with the contexts before
   * the run() method is called.
   *
   * @author sean
   */
  public interface ContextsAware {
    void setContexts(Contexts contexts);
  }

  /**
   * This wraps up the executor service for polled runnables.
   *
   * @author sean
   */
  public static class PolledRunnableProcessor implements Scheduler.JobProcessor {
    private ExecutorService _polledRunnableExec;
    private Contexts _contexts;

    // this map contains all polled runnable results that are not completed.
    // keep an eye on this one, since if we re-use this polled runnable and
    // generate too many entries in this map, this becomes a memory leak(
    // long-running memory occupation)
    private final Map<String, PolledRunnableResults> resultsByJobId =
        new HashMap<String, PolledRunnableResults>();

    public void setContexts(Contexts contexts) {
      _contexts = contexts;
    }

    public void setPolledRunnableExecutorService(ExecutorService polledRunnableExecutorService) {
      _polledRunnableExec = polledRunnableExecutorService;
    }

    public void onScheduledJob(final Scheduler.JobInfo jobInfo)
        throws Scheduler.JobProcessorException {
      JOB_STATUS statusOfPriorTry = JOB_STATUS.PENDING;
      Exception exceptionThrownOnPriorTry = null;
      boolean toRetry = false;

      synchronized (resultsByJobId) {
        PolledRunnableResults results = resultsByJobId.get(jobInfo.jobName);
        if (results != null) {
          statusOfPriorTry = results._status;
          exceptionThrownOnPriorTry = results._exception;
        }
        if (statusOfPriorTry == JOB_STATUS.COMPLETED) {
          resultsByJobId.remove(jobInfo.jobName);
          jobInfo.jobDetail.getDetailsExt().put("runnable_status", JOB_STATUS.COMPLETED);
          return;
        }
        if (statusOfPriorTry == JOB_STATUS.PENDING || statusOfPriorTry == JOB_STATUS.FAILED) {
          resultsByJobId.put(
              jobInfo.jobName, new PolledRunnableResults(JOB_STATUS.IN_PROGRESS, null));
          toRetry = true;
        }
      }

      if (toRetry) {
        // re-try
        _polledRunnableExec.submit(
            new Runnable() {
              public void run() {
                try {
                  MapSerializableRunnable runnable =
                      (MapSerializableRunnable) jobInfo.jobDetail.getDetailsExt().get("runnable");
                  runnable.restoreFromDetails(jobInfo.jobDetail);
                  if (runnable instanceof ContextsAware) {
                    ((ContextsAware) runnable).setContexts(_contexts);
                  }
                  runnable.run();
                  synchronized (resultsByJobId) {
                    resultsByJobId.put(
                        jobInfo.jobName, new PolledRunnableResults(JOB_STATUS.COMPLETED, null));
                  }
                } catch (Exception e) {
                  __log.error("", e);
                  synchronized (resultsByJobId) {
                    resultsByJobId.put(
                        jobInfo.jobName, new PolledRunnableResults(JOB_STATUS.FAILED, e));
                  }
                } finally {
                }
              }
            });
      }

      jobInfo.jobDetail.getDetailsExt().put("runnable_status", JOB_STATUS.IN_PROGRESS);
      if (exceptionThrownOnPriorTry != null) {
        throw new Scheduler.JobProcessorException(exceptionThrownOnPriorTry, true);
      }
    }

    private static enum JOB_STATUS {
      PENDING,
      IN_PROGRESS,
      FAILED,
      COMPLETED
    }

    private class PolledRunnableResults {
      private JOB_STATUS _status = JOB_STATUS.PENDING;
      private Exception _exception;

      public PolledRunnableResults(JOB_STATUS status, Exception exception) {
        _status = status;
        _exception = exception;
      }
    }
  }

  public void cleanupProcess(ProcessConf pconf) throws BpelEngineException {
    if (pconf != null) {
      deleteProcessDAO(pconf.getProcessId(), pconf.isTransient());
    }
  }

  public void setMigrationTransactionTimeout(int migrationTransactionTimeout) {
    this._migrationTransactionTimeout = migrationTransactionTimeout;
  }

  public void acquireTransactionLocks() {
    if (Boolean.parseBoolean(
        _configProperties.getProperty("ode.acquireTransactionLocks", "false"))) {
      _contexts.scheduler.acquireTransactionLocks();
    }
  }

  public void registerExtensionBundle(ExtensionBundleRuntime bundle) {
    _contexts.extensionRegistry.put(bundle.getNamespaceURI(), bundle);
    bundle.registerExtensionActivities();
  }

  public void unregisterExtensionBundle(String nsURI) {
    _contexts.extensionRegistry.remove(nsURI);
  }

  public void registerExtensionCorrelationFilter(ExtensionCorrelationFilter filter) {
    _contexts.correlationFilterRegistry.put(filter.getNamespaceURI(), filter);
    filter.registerExtensionCorrelationFilter();
  }

  public void unregisterExtensionCorrelationFilter(String nsURI) {
    _contexts.correlationFilterRegistry.remove(nsURI);
  }
}