org.eclipse.smila.ode.ODEServer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.smila.ode.ODEServer.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2009 empolis GmbH and brox IT Solutions GmbH. All rights reserved. This program and the
 * accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this
 * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors: Juergen Schumacher (empolis GmbH) - initial API and implementation
 *******************************************************************************/

package org.eclipse.smila.ode;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

import javax.sql.DataSource;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import javax.xml.namespace.QName;

import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ode.bpel.dao.BpelDAOConnectionFactory;
import org.apache.ode.bpel.engine.BpelServerImpl;
import org.apache.ode.bpel.iapi.BpelEngineException;
import org.apache.ode.bpel.iapi.BpelEventListener;
import org.apache.ode.bpel.iapi.BpelServer;
import org.apache.ode.bpel.iapi.InvocationStyle;
import org.apache.ode.bpel.iapi.Message;
import org.apache.ode.bpel.iapi.MyRoleMessageExchange;
import org.apache.ode.bpel.iapi.ProcessConf;
import org.apache.ode.bpel.iapi.Scheduler;
import org.apache.ode.bpel.rtrep.common.extension.AbstractExtensionBundle;
import org.apache.ode.il.EmbeddedGeronimoFactory;
import org.apache.ode.il.config.OdeConfigProperties;
import org.apache.ode.il.dbutil.Database;
import org.apache.ode.il.dbutil.DatabaseConfigException;
import org.apache.ode.scheduler.simple.JdbcDelegate;
import org.apache.ode.scheduler.simple.SimpleScheduler;
import org.apache.ode.store.ProcessStoreImpl;
import org.apache.ode.utils.GUID;
import org.w3c.dom.Element;

/**
 * very simple ODE integration.
 * 
 * @author jschumacher
 * 
 */
public class ODEServer {

    /** config property for transaction timeout = "pipeline.timeout". */
    public static final String PROP_PIPELINE_TIMEOUT = "pipeline.timeout";

    /** default transaction timeout for transaction manager in seconds = 5 minutes. */
    public static final String DEFAULT_PIPELINE_TIMEOUT = "300";

    /** for conversion of timeout seconds to millis ... prevent a magic number. */
    public static final int MILLIS_PER_SECOND = 1000;

    /** path to SQL script that prepares the in-memory HSQLDB instance for the scheduler. */
    private static final String RESOURCE_SCHEDULER_HSQLDB_SQL = "/sql/scheduler-hsqldb.sql";

    /** path to SQL script that prepares the in-memory Derby instance for the scheduler. */
    private static final String RESOURCE_SCHEDULER_DERBY_SQL = "/sql/scheduler-derby.sql";

    /** logger for this class. */
    private final Log _log = LogFactory.getLog(getClass());

    /** configuration of ODE. */
    private ODEConfigProperties _odeConfig;

    /** the BPEL server. */
    private BpelServerImpl _server;

    /** store for deployed processes. */
    private ProcessStoreImpl _store;

    /** transaction manager. */
    private TransactionManager _txManager;

    /** factory for database related objects (datasources, DAO connection factories, etc.). */
    private Database _database;

    /** data source to use by BPEL engine. */
    private DataSource _dataSource;

    /** BPEL job scheduler. */
    private Scheduler _scheduler;

    /** DAO connection factory for BPEL objects. */
    private BpelDAOConnectionFactory _daoCF;

    /** timeout for BPEL pipelines in seconds. */
    private int _txTimeoutMillis;

    /** mapping of workflow names to BPEl files. */
    private final Map<QName, File> _bpelFiles = new HashMap<QName, File>();

    /**
     * initialize ODE BPEL engine with settings in specified config properties.
     * 
     * @param odeConfig
     *          config properties for engine.
     * @param contextFactory
     *          context factory to create the needed context objects.
     * @throws ODEServerException
     *           error in initialization
     */
    public ODEServer(final ODEConfigProperties odeConfig, final ODEServerContextFactory contextFactory)
            throws ODEServerException {
        final ClassLoader tcclBackup = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(ODEServer.class.getClassLoader());
        try {
            _odeConfig = odeConfig;
            _server = new BpelServerImpl();
            createTransactionManager();
            createDataSource();
            createScheduler();
            createProcessStore(contextFactory);
            initBPELServer(contextFactory);
            _server.start();
        } catch (final Exception ex) {
            // _log.error("error in ODE initialization", ex);
            throw new ODEServerException("error in ODE initialization" + ex.getMessage(), ex);
        } finally {
            Thread.currentThread().setContextClassLoader(tcclBackup);
        }
    }

    /**
     * deploy BPEL processes in given directory.
     * 
     * @param deploymentUnitDirectory
     *          directory to search for BPEL processes.
     * @return names of deployed processes
     */
    public Collection<QName> deploy(final File deploymentUnitDirectory) {
        final ClassLoader tcclBackup = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(ODEServer.class.getClassLoader());
        try {
            final Collection<QName> pids = _store.deploy(deploymentUnitDirectory);
            final Collection<QName> names = new ArrayList<QName>(pids.size());
            for (final QName pid : pids) {
                final ProcessConf processConf = _store.getProcessConfiguration(pid);
                final QName processName = processConf.getType();
                names.add(processName);
                _bpelFiles.put(processName, new File(deploymentUnitDirectory, processConf.getBpelDocument()));
                _server.register(processConf);
            }
            _log.info("Deployed BPEL processes: " + _bpelFiles);
            return names;
        } finally {
            Thread.currentThread().setContextClassLoader(tcclBackup);
        }
    }

    /**
     * @param processId
     *          a process Id as returned by {@link #deploy(File)}.
     * @return the BPEL document defining this process, if it exists. Else null.
     * @throws IOException error reading file. 
     */
    public String getBpelDocument(final QName processId) throws IOException {
        final File bpelFile = _bpelFiles.get(processId);
        if (bpelFile == null) {
            return null;
        }
        return FileUtils.readFileToString(bpelFile);
    }

    /**
     * get the configuration of the named process.
     * 
     * @param processId
     *          qname of proces
     * @return definition of process.
     */
    public ProcessConf getProcessConfiguration(final QName processId) {
        return _store.getProcessConfiguration(processId);
    }

    /**
     * register an extension bundle with the BPEL server.
     * 
     * @param bundle
     *          an extension bundle.
     */
    public void registerExtensionBundle(final AbstractExtensionBundle bundle) {
        _server.registerExtensionBundle(bundle);
        _store.setExtensionValidators(bundle.getExtensionValidators());
    }

    /**
     * register an extension bundle with the BPEL server.
     * 
     * @param bundle
     *          an extension bundle.
     */
    public void unregisterExtensionBundle(final AbstractExtensionBundle bundle) {
        _server.unregisterExtensionBundle(bundle.getNamespaceURI());
    }

    /**
     * register an listener to {@link org.apache.ode.bpel.evt.BpelEvent} issued by the ODE engine during execution of
     * processes.
     * 
     * @param listener
     *          BPEL event listener
     */
    public void registerEventListener(final BpelEventListener listener) {
        _server.registerBpelEventListener(listener);
    }

    /**
     * unregister an listener to {@link org.apache.ode.bpel.evt.BpelEvent} issued by the ODE engine during execution of
     * processes.
     * 
     * @param listener
     *          BPEL event listener
     */
    public void unregisterEventListener(final BpelEventListener listener) {
        _server.unregisterBpelEventListener(listener);
    }

    /**
     * invoke a BPEL process.
     * 
     * @param serviceName
     *          name of BPEL process as returned by deploy()
     * @param opName
     *          name of the operation to execute
     * @param message
     *          message to send to process
     * @return result message
     * @throws ODEServerException
     *           error in invocation
     */
    public Element invoke(final QName serviceName, final String opName, final Element message)
            throws ODEServerException {
        final ClassLoader tcclBackup = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(ODEServer.class.getClassLoader());
        try {
            MyRoleMessageExchange mex = null;
            try {
                mex = invokeProcess(serviceName, opName, message);
                return processResponse(mex);
            } catch (final ODEServerException ex) {
                throw ex;
            } catch (final RuntimeException ex) {
                ex.printStackTrace();
                throw new ODEServerException("Runtime exception when invoking BPEL process", ex);
            } finally {
                if (mex != null) {
                    mex.complete();
                    mex.release();
                }
            }
        } finally {
            Thread.currentThread().setContextClassLoader(tcclBackup);
        }
    }

    /**
     * invoke process.
     * 
     * @param serviceName
     *          service name
     * @param opName
     *          operation name
     * @param message
     *          message content
     * @return invocation message exchange
     * @throws ODEServerException
     *           processing error
     */
    private MyRoleMessageExchange invokeProcess(final QName serviceName, final String opName, final Element message)
            throws ODEServerException {
        try {
            final String messageId = new GUID().toString();
            if (_log.isDebugEnabled()) {
                _log.debug("request messageID = " + messageId);
            }
            final MyRoleMessageExchange mex = _server.createMessageExchange(InvocationStyle.UNRELIABLE, serviceName,
                    opName, messageId);
            if (mex.getOperation() == null) {
                throw new ODEServerException("Did not find operation " + opName + " on service " + serviceName);
            }
            final Message request = mex.createMessage(mex.getOperation().getInput().getMessage().getQName());
            request.setMessage(message);
            mex.setRequest(request);
            mex.setTimeout(_txTimeoutMillis);
            mex.invokeBlocking();
            return mex;
        } catch (final TimeoutException ex) {
            throw new ODEServerException("Timeout in execution of pipeline " + serviceName, ex);
        } catch (final BpelEngineException ex) {
            throw new ODEServerException("BPEL error in execution of pipeline " + serviceName, ex);
        }
    }

    /**
     * process response of invocation.
     * 
     * @param mex
     *          invocation mex
     * @return result content.
     * @throws ODEServerException
     *           error in processing.
     */
    private Element processResponse(final MyRoleMessageExchange mex) throws ODEServerException {
        try {
            final QName serviceName = mex.getServiceName();
            final String messageId = mex.getMessageExchangeId();
            if (_log.isDebugEnabled()) {
                _log.debug("response messageID = " + messageId);
            }
            final MyRoleMessageExchange responseMex = (MyRoleMessageExchange) _server.getMessageExchange(messageId);
            switch (responseMex.getAckType()) {
            case FAILURE:
                throw new ODEServerException("BPEL process " + serviceName.getLocalPart()
                        + " completed with failure type " + responseMex.getFailureType() + ", explanation: "
                        + responseMex.getFaultExplanation());
            case FAULT:
                throw new ODEServerException(
                        "BPEL process  " + serviceName.getLocalPart() + " completed with fault "
                                + responseMex.getFault() + ", explanation: " + responseMex.getFaultExplanation());
            case RESPONSE:
            default:
                final Message response = responseMex.getResponse();
                return response.getMessage();
            }
        } finally {
            if (mex != null) {
                mex.complete();
                mex.release();
            }
        }
    }

    /**
     * shutdown the BPEL engine and all used resources.
     * 
     */
    public void shutdown() {
        try {
            _server.stop();
        } catch (final Exception ex) {
            _server = null;
        }
        try {
            _scheduler.stop();
            _scheduler.shutdown();
        } catch (final Exception ex) {
            _scheduler = null;
        }
        try {
            _daoCF.shutdown();
        } catch (final Exception ex) {
            _daoCF = null;
        }
        _dataSource = null;
        try {
            _database.shutdown();
        } catch (final Exception ex) {
            _database = null;
        }
        _txManager = null;
    }

    /**
     * @return integrated BPEL engine
     */
    protected BpelServer getBpelServer() {
        return _server;
    }

    /**
     * intialize BPEL server.
     * 
     * @param contextFactory
     *          context factory creating necessary context objects.
     */
    private void initBPELServer(final ODEServerContextFactory contextFactory) {
        if (_scheduler == null) {
            throw new RuntimeException("No scheduler");
        }
        if (_daoCF == null) {
            throw new RuntimeException("No DAO");
        }
        _server.setDaoConnectionFactory(_daoCF);
        _server.setScheduler(_scheduler);
        _server.setTransactionManager(_txManager);
        _server.setMessageExchangeContext(contextFactory.createMessageExchangeContext());
        _server.setBindingContext(contextFactory.createBindingContext(this));
        _server.setEndpointReferenceContext(contextFactory.createEPRContext());
        _server.setConfigProperties(_odeConfig);
        _server.init();

        final String txTimeoutValue = _odeConfig.getProperties().getProperty(PROP_PIPELINE_TIMEOUT,
                DEFAULT_PIPELINE_TIMEOUT);
        final int txTimeout = Integer.parseInt(txTimeoutValue);
        _log.info("BPEL process execution timeout: " + txTimeout + " seconds.");
        _txTimeoutMillis = txTimeout * MILLIS_PER_SECOND;
    }

    /**
     * create a store for BPEL process management.
     * 
     * @param contextFactory
     *          context factory to create the needed context objects.
     */
    private void createProcessStore(final ODEServerContextFactory contextFactory) {
        _store = new ProcessStoreImpl(contextFactory.createEPRContext(), _dataSource,
                _odeConfig.getDAOConnectionFactory(), _odeConfig, true);
    }

    /**
     * create a TransactionManager for the BPEL engine. Currently hardcoded to use the Geronimo implementation of
     * transactions.
     * 
     * @return a new transaction manager
     * @throws SystemException
     *           error in initialisation.
     */
    private TransactionManager createTransactionManager() throws SystemException {
        final EmbeddedGeronimoFactory factory = new EmbeddedGeronimoFactory();
        _txManager = factory.getTransactionManager();
        return _txManager;
    }

    /**
     * create a data source for persistence operations of the BPEL engine.
     * 
     * @throws DatabaseConfigException
     *           invalid database configuration.
     */
    private void createDataSource() throws DatabaseConfigException {
        if (_txManager == null) {
            throw new RuntimeException("No transaction manager");
        }
        _database = new Database(_odeConfig);
        _database.setTransactionManager(_txManager);
        if (_odeConfig.getDbMode() == OdeConfigProperties.DatabaseMode.EMBEDDED) {
            _database.setWorkRoot(new File(_odeConfig.getWorkingDir()));
        }
        _database.start();
        _dataSource = _database.getDataSource();
        _daoCF = _database.createDaoCF();
        // create dummy connection to setup DB schema.
        // this way errors during schema creation (because tables exist alreay, for example) do not
        // disturb later operaion (they seem to do with EclipseLink, else).
        try {
            _txManager.begin();
            _daoCF.getConnection();
            _txManager.commit();
        } catch (final Exception e) {
            _log.error("error creating initial BPEL DAO connection", e);
        }
    }

    /**
     * create a scheduler for the BPEL engine.
     * 
     * @return a new scheduler
     * @throws ODEServerException
     *           error initializing the scheduler database.
     */
    private Scheduler createScheduler() throws ODEServerException {
        if (_server == null) {
            throw new RuntimeException("No BPEL server");
        }
        if (_txManager == null) {
            throw new RuntimeException("No transaction manager");
        }
        if (_dataSource == null) {
            throw new RuntimeException("No data source");
        }

        prepareSchedulerDb();
        final SimpleScheduler simpleScheduler = new SimpleScheduler(new GUID().toString(),
                new JdbcDelegate(_dataSource), _odeConfig.getProperties());
        simpleScheduler.setTransactionManager(_txManager);
        simpleScheduler.setJobProcessor(_server);
        _scheduler = simpleScheduler;
        // _scheduler = new MockScheduler(_txManager);
        return _scheduler;
    }

    /**
     * create tables and aliases in in-memory HSQLDB.
     * 
     * @throws ODEServerException
     *           error creating tables
     */
    private void prepareSchedulerDb() throws ODEServerException {
        Connection c = null;
        String sqlScriptName = null;
        if ("org.apache.derby.jdbc.EmbeddedDriver".equals(_odeConfig.getDbInternalJdbcDriverClass())) {
            sqlScriptName = RESOURCE_SCHEDULER_DERBY_SQL;
        } else if ("org.hsqldb.jdbcDriver".equals(_odeConfig.getDbInternalJdbcDriverClass())) {
            sqlScriptName = RESOURCE_SCHEDULER_HSQLDB_SQL;
        }
        // init some tables in DB
        if (sqlScriptName != null) {
            try {
                c = _dataSource.getConnection();
                _log.info("Reading SQL commands from " + sqlScriptName + " to prepare DB for scheduler.");
                final InputStream sqlStream = getClass().getResourceAsStream(sqlScriptName);
                if (sqlStream == null) {
                    _log.error("Error reading SQL script " + sqlScriptName);
                    throw new ODEServerException("Error reading SQL script " + sqlScriptName);
                }
                final BufferedReader reader = new BufferedReader(new InputStreamReader(sqlStream));
                String line = null;
                StringBuilder sql = new StringBuilder();
                while ((line = reader.readLine()) != null) {
                    sql.append(line.trim());
                    if (sql.length() > 0 && sql.charAt(sql.length() - 1) == ';') {
                        // cut off ";", Derby doesn't like it.
                        sql.setLength(sql.length() - 1);
                        c.createStatement().execute(sql.toString());
                        sql = new StringBuilder();
                    }
                }
                reader.close();
            } catch (final IOException ex) {
                throw new ODEServerException("Error reading SQL script: " + ex.getMessage(), ex);
            } catch (final SQLException ex) {
                _log.info("Error creating tables in scheduler DB: " + ex.toString());
                _log.info("Usually this means that the DB has been initialized earlier already, "
                        + "in this case everything should be fine.");
            } finally {
                try {
                    if (c != null) {
                        c.close();
                    }
                } catch (SQLException ex) {
                    ex = null; // ignorable.
                }
            }
        }
    }
}