/*
 * JBoss, Home of Professional Open Source
 * Copyright 2009, Red Hat Middleware LLC, and others contributors as indicated
 * by the @authors tag. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * 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,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package org.jboss.soa.bpel.runtime.engine.ode;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ode.bpel.common.evt.DebugBpelEventListener;
import org.apache.ode.bpel.engine.BpelManagementFacadeImpl;
import org.apache.ode.bpel.engine.BpelServerImpl;
import org.apache.ode.bpel.engine.CountLRUDehydrationPolicy;
import org.apache.ode.bpel.engine.cron.CronScheduler;
import org.apache.ode.bpel.extvar.jdbc.JdbcExternalVariableModule;
import org.apache.ode.bpel.iapi.*;
import org.apache.ode.bpel.intercept.MessageExchangeInterceptor;
import org.apache.ode.bpel.memdao.BpelDAOConnectionFactoryImpl;
import org.apache.ode.dao.bpel.BpelDAOConnectionFactory;
import org.apache.ode.dao.store.ConfStoreDAOConnectionFactory;
import org.apache.ode.il.config.OdeConfigProperties;
import org.apache.ode.il.dbutil.Database;
import org.apache.ode.scheduler.simple.JdbcDelegate;
import org.apache.ode.scheduler.simple.SimpleScheduler;
import org.apache.ode.store.ProcessStoreImpl;
import org.apache.ode.store.RiftSawProcessStore;
import org.apache.ode.utils.DOMUtils;
import org.apache.ode.utils.GUID;
import org.apache.ode.utils.Properties;
import org.jboss.soa.bpel.runtime.engine.BPELEngine;
import org.jboss.soa.bpel.runtime.engine.InvocationAdapter;
import org.w3c.dom.Element;

import javax.sql.DataSource;
import javax.transaction.*;
import javax.transaction.xa.XAResource;
import java.util.StringTokenizer;
import java.util.concurrent.*;

/**
 * This is the JBoss Service wrapping the ODE BPEL engine.
 *
 * @author gbrown
 *
 */
public class BPELEngineImpl implements BPELEngine, ExecutionEnvironment
{

  protected final Log __log = LogFactory.getLog(getClass());
  protected final Log __logTx = LogFactory.getLog("org.apache.ode.tx");

  protected BpelServerImpl _bpelServer;
  public ProcessStoreImpl _store;
  protected OdeConfigProperties _odeConfig;
  protected TransactionManager _txMgr;
  protected BpelDAOConnectionFactory _daoCF;
  protected ConfStoreDAOConnectionFactory _storeCF;
  protected Scheduler _scheduler;
  protected Database _db;
  protected ExecutorService _executorService;
  protected CronScheduler _cronScheduler;

  /**
   * The default constructor.
   */
  public BPELEngineImpl() {
  }

  public Object getManagementInterface()
  {
    return new BpelManagementFacadeImpl(_bpelServer, _store);
  }

  /**
   * This method invokes a BPEL process, associated with the supplied
   * service and operation, using the supplied message.
   *
   * @throws Exception Failed to invoke the operation
   */
  public void invoke(InvocationAdapter invocationAdapter)
      throws Exception
  {
    boolean success = true;
    MyRoleMessageExchange odeMex = null;
    Future responseFuture = null;
   
    try
    {
      // start TX
      _txMgr.begin();
      if (__log.isDebugEnabled()) __log.debug("Starting transaction.");
      
      odeMex = createMessageExchange(invocationAdapter);
      odeMex.setProperty("isTwoWay", Boolean.toString(odeMex.getOperation().getOutput() != null));
      if (__log.isDebugEnabled()) __log.debug("Is two way operation? "+odeMex.getProperty("isTwoWay"));
      
      if (odeMex.getOperation() != null)
      {
        // Preparing message to send to ODE
        Message odeRequest = odeMex.createMessage(odeMex.getOperation().getInput().getMessage().getQName());
        
        // distinguish WS and ESB invocation
        invocationAdapter.parseRequest(odeMex, odeRequest);

        // TODO: Might need to store session id/epr of caller?
        //readHeader(msgContext, odeMex);

        if (__log.isDebugEnabled()) {
          __log.debug("Invoking ODE using MEX " + odeMex);
          __log.debug("Message content:  " + DOMUtils.domToString(odeRequest.getMessage()));
        }

        // Invoke ODE
        responseFuture = odeMex.invoke(odeRequest);

        __log.debug("Commiting ODE MEX " + odeMex);
        try {
          if (__log.isDebugEnabled()) __log.debug("Commiting transaction.");
          _txMgr.commit();
        } catch (Exception e) {
          __log.error("Commit failed", e);
          success = false;
        }
      } else {
        success = false;
      }
    } catch (Exception e) {
      __log.error("Exception occured while invoking ODE", e);
      success = false;
      String mesg = e.getMessage();
      if (mesg == null) {
        mesg = "An exception occured while invoking ODE.";
      }
      throw new Exception(mesg, e);
    } finally {
      if (!success) {
        if (odeMex != null) odeMex.release(success);
        try {
          _txMgr.rollback();
        } catch (Exception e) {
          throw new Exception("Rollback failed", e);
        }
      }
    }

    if (odeMex.getOperation().getOutput() != null) {
      // Waits for the response to arrive
      try {
        responseFuture.get(resolveTimeout(invocationAdapter, odeMex), TimeUnit.MILLISECONDS);
      } catch (Exception e) {
        String errorMsg = "Timeout or execution error when waiting for response to MEX "
            + odeMex + " " + e.toString();
        __log.error(errorMsg, e);
        throw new Exception(errorMsg);
      }
      
      
      // Hopefully we have a response
      __log.debug("Handling response for MEX " + odeMex);
      boolean commit = false;
      try {
        if (__log.isDebugEnabled()) __log.debug("Starting transaction.");
        _txMgr.begin();
      } catch (Exception ex) {
        throw new Exception("Error starting transaction!", ex);
      }
      try {
        // Refreshing the message exchange
        odeMex = (MyRoleMessageExchange) _bpelServer.getEngine().getMessageExchange(odeMex.getMessageExchangeId());
        onResponse(odeMex, invocationAdapter);

        __log.debug("Returning: "+ invocationAdapter.getInvocationResult());


        commit = true;
      } catch (Exception e) {
        __log.error("Error processing response for MEX " + odeMex, e);
        throw new Exception("An exception occured when invoking ODE.", e);
      } finally {
        odeMex.release(commit);
        if (commit) {
          try {
            if (__log.isDebugEnabled()) __log.debug("Comitting transaction.");
            _txMgr.commit();
          } catch (Exception e) {
            throw new Exception("Commit failed!", e);
          }
        } else {
          try {
            _txMgr.rollback();
          } catch (Exception ex) {
            throw new Exception("Rollback failed!", ex);
          }
        }
        //}
      }
      if (!success) {
        throw new Exception("Message was either unroutable or timed out!");
      }
    } else {
      // One ways cleanup
      odeMex.release(true);
    }

  }

  private MyRoleMessageExchange createMessageExchange(InvocationAdapter adapter)
  {
    // Creating message exchange
    String messageId = new GUID().toString();
    MyRoleMessageExchange odeMex = _bpelServer.getEngine()
        .createMessageExchange(messageId, adapter.getServiceName(), adapter.getOperationName());
    if (__log.isDebugEnabled())  __log.debug("ODE routed to operation " + odeMex.getOperation() + " from service " + adapter.getServiceName());
    return odeMex;
  }

  private void onResponse(MyRoleMessageExchange mex, InvocationAdapter invocationAdapter) throws Exception {
    Element ret=null;

    switch (mex.getStatus()) {
      case FAULT:
        if (__log.isDebugEnabled())
          __log.debug("Fault response message: " + mex.getFault());

        invocationAdapter.createFault(mex);
        break;
      case ASYNC:
      case RESPONSE:
        invocationAdapter.createResponse(mex);        
        if (__log.isDebugEnabled())
          __log.debug("Response message " + ret);
        break;
      case FAILURE:
        if (__log.isDebugEnabled())
          __log.debug("Failure response message: " + mex.getFault());
        __log.error("Failure details: "+mex.getFaultResponse());

        throw new Exception("Failure response message: "+mex.getFault()+" : "+mex.getFaultExplanation());
      default:
        throw new Exception("Received ODE message exchange in unexpected state: " + mex.getStatus());
    }    
  }

  private long resolveTimeout(InvocationAdapter invocationAdapter, MyRoleMessageExchange odeMex) {
	  ProcessConf conf=odeMex.getProcessConf();
	  
      String timeout = conf.getEndpointProperties(invocationAdapter.getServiceName(), invocationAdapter.getPortName()).get(Properties.PROP_MEX_TIMEOUT);
      
      if (timeout != null) {
          try {
              return Long.parseLong(timeout);
          } catch (NumberFormatException e) {
              if(__log.isWarnEnabled()) __log.warn("Mal-formatted Property: ["+ Properties.PROP_MEX_TIMEOUT+"="+timeout+"] Default value ("+Properties.DEFAULT_MEX_TIMEOUT+") will be used");
          }
      }
    return Properties.DEFAULT_MEX_TIMEOUT;
  }

  public void init() throws Exception {

    java.util.Properties props=new java.util.Properties();

    try {
      java.io.InputStream is=BPELEngineImpl.class.getClassLoader().getResourceAsStream("bpel.properties");
      props.load(is);
    } catch(Exception e) {
      __log.error("Failed to load properties", e);
    }

    __log.debug("ODE PROPS="+props);

    _odeConfig = new OdeConfigProperties(props, "bpel.");

    __log.debug("Initializing transaction manager");
    initTxMgr();
    __log.debug("Creating data source.");
    initDataSource();
    __log.debug("Starting DAO.");
    initDAO();
    EndpointReferenceContextImpl eprContext = new EndpointReferenceContextImpl(this);
    __log.debug("Initializing BPEL process store.");
    initProcessStore(eprContext);
    __log.debug("Initializing BPEL server.");
    initBpelServer(eprContext);

    _store.loadAll();

    // Register BPEL event listeners configured in axis2.properties file.
    registerEventListeners();
    registerMexInterceptors();

    //registerExtensionActivityBundles();

    registerExternalVariableModules();

    try {
      _bpelServer.start();
    } catch (Exception ex) {
      String errmsg = "SERVER START FAILED";
      __log.error(errmsg, ex);
    }

    __log.debug("Starting scheduler");
    _scheduler.start();
  }

  
  public void close() throws Exception {
      ClassLoader old = Thread.currentThread().getContextClassLoader();
      Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
      try {

          if (_bpelServer != null)
              try {
                  __log.debug("shutting down BPEL server.");
                  _bpelServer.shutdown();
                  _bpelServer = null;
              } catch (Throwable ex) {
                  __log.debug("Error stopping services.", ex);
              }

          if( _cronScheduler != null ) {
              try {
                  __log.debug("shutting down cron scheduler.");
                  _cronScheduler.shutdown();
                  _cronScheduler = null;
              } catch (Exception ex) {
                  __log.debug("Cron scheduler couldn't be shutdown.", ex);
              }
          }
          
          if (_scheduler != null)
              try {
                  __log.debug("shutting down scheduler.");
                  _scheduler.shutdown();
                  _scheduler = null;
              } catch (Exception ex) {
                  __log.debug("Scheduler couldn't be shutdown.", ex);
              }

          if (_store != null)
              try {
                  _store.shutdown();
                  _store = null;
              } catch (Throwable t) {
                  __log.debug("Store could not be shutdown.", t);
              }

          if (_daoCF != null)
              try {
                  _daoCF.shutdown();
              } catch (Throwable ex) {
                  __log.debug("Bpel DAO shutdown failed.", ex);
              } finally {
                  _daoCF = null;
              }
              
	      if (_storeCF != null)
	          try {
	              _storeCF.shutdown();
	          } catch (Throwable ex) {
	              __log.debug("Store DAO shutdown failed.", ex);
	          } finally {
	              _storeCF = null;
	          }

          if (_db != null)
              try {
                  _db.shutdown();

              } catch (Throwable ex) {
                  __log.debug("DB shutdown failed.", ex);
              } finally {
                  _db = null;
              }

          if (_txMgr != null) {
              __log.debug("shutting down transaction manager.");
              _txMgr = null;
          }

      } finally {
          Thread.currentThread().setContextClassLoader(old);
      }  
	  
	  
  }

  private void initDataSource() throws Exception {
    _db = new Database(_odeConfig);
    _db.setTransactionManager(_txMgr);
    // TODO: Not sure if needed for ODE1.3.3
    //_db.setWorkRoot(_workRoot);

    try {
      _db.start();
    } catch (Exception ex) {
      String errmsg = "FAILED TO INITIALISE DATA SOURCE";
      __log.error(errmsg, ex);
      throw new Exception(errmsg, ex);
    }

  }

  private void initTxMgr() throws Exception {
    String txFactoryName = _odeConfig.getTxFactoryClass();
    __log.debug("Initializing transaction manager using " + txFactoryName);
    try {
      Class<?> txFactClass = this.getClass().getClassLoader().loadClass(txFactoryName);
      Object txFact = txFactClass.newInstance();
      _txMgr = (TransactionManager) txFactClass.getMethod("getTransactionManager", (Class[]) null).invoke(txFact);
      if (__logTx.isDebugEnabled() && System.getProperty("ode.debug.tx") != null)
        _txMgr = new DebugTxMgr(_txMgr);
      //_axisConfig.addParameter("ode.transaction.manager", _txMgr);
    } catch (Exception e) {
      __log.fatal("Couldn't initialize a transaction manager with factory: " + txFactoryName, e);
      throw new Exception("Couldn't initialize a transaction manager with factory: " + txFactoryName, e);
    }
  }

  protected void initDAO() throws Exception {
    __log.debug("USING DAO: "+_odeConfig.getDAOConnectionFactory());
    try {
      _daoCF = _db.createDaoCF();
      _storeCF = _db.createDaoStoreCF();
    } catch (Exception ex) {
      String errmsg = "DAO INSTANTIATION FAILED: "+_odeConfig.getDAOConnectionFactory() + " and " + _odeConfig.getDAOConfStoreConnectionFactory();
      __log.error(errmsg, ex);
      throw new Exception(errmsg, ex);

    }
  }

  protected void initProcessStore(EndpointReferenceContext eprContext) {
    _store = createProcessStore(eprContext, _txMgr, _storeCF);
    _store.registerListener(new ProcessStoreListenerImpl());
  }

  protected ProcessStoreImpl createProcessStore(EndpointReferenceContext eprContext, TransactionManager txm, ConfStoreDAOConnectionFactory cf) {
    return new RiftSawProcessStore(eprContext, txm, cf);
  }

  protected Scheduler createScheduler() {
    SimpleScheduler scheduler = new SimpleScheduler(new GUID().toString(),new JdbcDelegate(_db.getDataSource()), _odeConfig.getProperties());
    scheduler.setExecutorService(_executorService);
    scheduler.setTransactionManager(_txMgr);

    return scheduler;
  }

  private void initBpelServer(EndpointReferenceContextImpl eprContext) {
    if (__log.isDebugEnabled()) {
      __log.debug("ODE initializing");
    }
    ThreadFactory threadFactory = new ThreadFactory() {
      int threadNumber = 0;
      public Thread newThread(Runnable r) {
        threadNumber += 1;
        Thread t = new Thread(r, "ODEServer-"+threadNumber);
        t.setDaemon(true);
        return t;
      }
    };

    if (_odeConfig.getThreadPoolMaxSize() == 0)
      _executorService = Executors.newCachedThreadPool(threadFactory);
    else
      _executorService = Executors.newFixedThreadPool(_odeConfig.getThreadPoolMaxSize(), threadFactory);


    _bpelServer = new BpelServerImpl();
    _scheduler = createScheduler();
    _scheduler.setJobProcessor(_bpelServer);

    BpelServerImpl.PolledRunnableProcessor polledRunnableProcessor = new BpelServerImpl.PolledRunnableProcessor();
    polledRunnableProcessor.setPolledRunnableExecutorService(_executorService);
    polledRunnableProcessor.setContexts(_bpelServer.getContexts());
    _scheduler.setPolledRunnableProcesser(polledRunnableProcessor);

    _cronScheduler = new CronScheduler();
    _cronScheduler.setScheduledTaskExec(_executorService);
    _cronScheduler.setContexts(_bpelServer.getContexts());
    _bpelServer.setCronScheduler(_cronScheduler);

    _bpelServer.setDaoConnectionFactory(_daoCF);
    _bpelServer.setInMemDaoConnectionFactory(new BpelDAOConnectionFactoryImpl(_scheduler, _odeConfig.getInMemMexTtl()));
    _bpelServer.setEndpointReferenceContext(eprContext);
    _bpelServer.setMessageExchangeContext(new MessageExchangeContextImpl(this));
    
    _bpelServer.setBindingContext(new JAXWSBindingContext(this));
    
    _bpelServer.setScheduler(_scheduler);
    if (_odeConfig.isDehydrationEnabled()) {
      CountLRUDehydrationPolicy dehy = new CountLRUDehydrationPolicy();
      dehy.setProcessMaxAge(_odeConfig.getDehydrationMaximumAge());
      dehy.setProcessMaxCount(_odeConfig.getDehydrationMaximumCount());
      _bpelServer.setDehydrationPolicy(dehy);
    }
    _bpelServer.setConfigProperties(_odeConfig.getProperties());
    _bpelServer.init();
    _bpelServer.setInstanceThrottledMaximumCount(_odeConfig.getInstanceThrottledMaximumCount());
    _bpelServer.setProcessThrottledMaximumCount(_odeConfig.getProcessThrottledMaximumCount());
    _bpelServer.setProcessThrottledMaximumSize(_odeConfig.getProcessThrottledMaximumSize());
    _bpelServer.setHydrationLazy(_odeConfig.isHydrationLazy());
    _bpelServer.setHydrationLazyMinimumSize(_odeConfig.getHydrationLazyMinimumSize());
  }

  private void registerEventListeners() {

    // let's always register the debugging listener....
    _bpelServer.registerBpelEventListener(new DebugBpelEventListener());

    // then, whatever else they want.
    String listenersStr = _odeConfig.getEventListeners();
    if (listenersStr != null) {
      for (StringTokenizer tokenizer = new StringTokenizer(listenersStr, ",;"); tokenizer.hasMoreTokens();) {
        String listenerCN = tokenizer.nextToken();
        try {
          _bpelServer.registerBpelEventListener((BpelEventListener) Class.forName(listenerCN).newInstance());
          __log.debug("REGISTERED EVENT LISTENER: "+listenerCN);
        } catch (Exception e) {
          __log.warn("Couldn't register the event listener " + listenerCN + ", the class couldn't be "
              + "loaded properly: " + e);
        }
      }

    }
  }

  private void registerMexInterceptors() {
    String listenersStr = _odeConfig.getMessageExchangeInterceptors();
    if (listenersStr != null) {
      for (StringTokenizer tokenizer = new StringTokenizer(listenersStr, ",;"); tokenizer.hasMoreTokens();) {
        String interceptorCN = tokenizer.nextToken();
        try {
          _bpelServer.registerMessageExchangeInterceptor((MessageExchangeInterceptor) Class.forName(interceptorCN).newInstance());
          __log.debug("MESSAGE EXCHANGE INTERCEPTOR REGISTERED: "+interceptorCN);
        } catch (Exception e) {
          __log.warn("Couldn't register the event listener " + interceptorCN + ", the class couldn't be "
              + "loaded properly: " + e);
        }
      }
    }
  }

  private void registerExternalVariableModules() {
    JdbcExternalVariableModule jdbcext;
    jdbcext = new JdbcExternalVariableModule();
    jdbcext.registerDataSource("ode", _db.getDataSource());
    _bpelServer.registerExternalVariableEngine(jdbcext);

  }

  private void handleEvent(ProcessStoreEvent pse) {

    __log.debug("Process store event: " + pse);
    ProcessConf pconf = _store.getProcessConfiguration(pse.pid);
    switch (pse.type) {
      case DEPLOYED:
        if (pconf != null) {
          /*
          * If and only if an old process exists with the same pid, the old process is cleaned up.
          * The following line is IMPORTANT and used for the case when the deployment and store
          * do not have the process while the process itself exists in the BPEL_PROCESS table.
          * Notice that the new process is actually created on the 'ACTIVATED' event.
          */
          _bpelServer.cleanupProcess(pconf);
        }
        break;
      case ACTVIATED:
        // bounce the process
        _bpelServer.unregister(pse.pid);
        if (pconf != null) {
          _bpelServer.register(pconf);
        } else {
          __log.debug("slighly odd: recevied event " +
              pse + " for process not in store!");
        }
        break;
      case RETIRED:
        // are there are instances of this process running?
        boolean instantiated = _bpelServer.hasActiveInstances(pse.pid);
        // remove the process
        _bpelServer.unregister(pse.pid);
        // bounce the process if necessary
        if (instantiated) {
          if (pconf != null) {
            _bpelServer.register(pconf);
          } else {
            __log.debug("slighly odd: recevied event " +
                pse + " for process not in store!");
          }
        } else {
          // we may have potentially created a lot of garbage, so,
          // let's hope the garbage collector is configured properly.
          if (pconf != null) {
            _bpelServer.cleanupProcess(pconf);
          }
        }
        break;
      case DISABLED:
      case UNDEPLOYED:
        _bpelServer.unregister(pse.pid);
        if (pconf != null) {
          _bpelServer.cleanupProcess(pconf);
        }
        break;
      default:
        __log.debug("Ignoring store event: " + pse);
    }

    if( pconf != null ) {
      if( pse.type == ProcessStoreEvent.Type.UNDEPLOYED) {
        __log.debug("Cancelling all cron scheduled jobs on store event: " + pse);
        _bpelServer.getContexts().cronScheduler.cancelProcessCronJobs(pse.pid, true);
      }

      // Except for undeploy event, we need to re-schedule process dependent jobs
      __log.debug("(Re)scheduling cron scheduled jobs on store event: " + pse);
      if( pse.type != ProcessStoreEvent.Type.UNDEPLOYED) {
        _bpelServer.getContexts().cronScheduler.scheduleProcessCronJobs(pse.pid, pconf);
      }
    }
  }

  private class ProcessStoreListenerImpl implements ProcessStoreListener {

    public void onProcessStoreEvent(ProcessStoreEvent event) {
      handleEvent(event);
    }

  }

  private class DebugTxMgr implements TransactionManager {
    private TransactionManager _tm;

    public DebugTxMgr(TransactionManager tm) {
      _tm = tm;
    }

    public void begin() throws NotSupportedException, SystemException {
      __logTx.debug("Txm begin");
      _tm.begin();
    }

    public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException, RollbackException, SecurityException, SystemException {
      __logTx.debug("Txm commit");
      for (StackTraceElement traceElement : Thread.currentThread().getStackTrace()) {
        __logTx.debug(traceElement.toString());
      }
      _tm.commit();
    }

    public int getStatus() throws SystemException {
      __logTx.debug("Txm status");
      return _tm.getStatus();
    }

    public Transaction getTransaction() throws SystemException {
      Transaction tx = _tm.getTransaction();
      __logTx.debug("Txm get tx " + tx);
      return tx == null ? null : new DebugTx(tx);
    }

    public void resume(Transaction transaction) throws IllegalStateException, InvalidTransactionException, SystemException {
      __logTx.debug("Txm resume");
      _tm.resume(transaction);
    }

    public void rollback() throws IllegalStateException, SecurityException, SystemException {
      __logTx.debug("Txm rollback");
      _tm.rollback();
    }

    public void setRollbackOnly() throws IllegalStateException, SystemException {
      __logTx.debug("Txm set rollback");
      _tm.setRollbackOnly();
    }

    public void setTransactionTimeout(int i) throws SystemException {
      __logTx.debug("Txm set tiemout " + i);
      _tm.setTransactionTimeout(i);
    }

    public Transaction suspend() throws SystemException {
      __logTx.debug("Txm suspend");
      return _tm.suspend();
    }
  }

  private class DebugTx implements Transaction {
    private Transaction _tx;

    public DebugTx(Transaction tx) {
      _tx = tx;
    }

    public void commit() throws HeuristicMixedException, HeuristicRollbackException, RollbackException, SecurityException, SystemException {
      __logTx.debug("Tx commit");
      _tx.commit();
    }

    public boolean delistResource(XAResource xaResource, int i) throws IllegalStateException, SystemException {
      return _tx.delistResource(xaResource, i);
    }

    public boolean enlistResource(XAResource xaResource) throws IllegalStateException, RollbackException, SystemException {
      return _tx.enlistResource(xaResource);
    }

    public int getStatus() throws SystemException {
      return _tx.getStatus();
    }

    public void registerSynchronization(Synchronization synchronization) throws IllegalStateException, RollbackException, SystemException {
      __logTx.debug("Synchronization registration on " + synchronization.getClass().getName());
      _tx.registerSynchronization(synchronization);
    }

    public void rollback() throws IllegalStateException, SystemException {
      __logTx.debug("Tx rollback");
      _tx.rollback();
    }

    public void setRollbackOnly() throws IllegalStateException, SystemException {
      __logTx.debug("Tx set rollback");
      _tx.setRollbackOnly();
    }
  }

  public Scheduler getScheduler()
  {
    return _scheduler;
  }

  public ExecutorService getExecutorService()
  {
    return _executorService;
  }

  public CronScheduler getCronScheduler()
  {
    return _cronScheduler;
  }

  public OdeConfigProperties getOdeConfig()
  {
    return _odeConfig;
  }
  
  public BpelServerImpl getBpelServer() {
	  return _bpelServer;
  }
}
