/** * 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; /** RNG, for delays */ private Random _random = new Random(System.currentTimeMillis()); private static double _delayMean = 0; /** * Set of processes that are registered with the server. Includes hydrated and dehydrated * processes. Guarded by _mngmtLock.writeLock(). */ private final ConcurrentHashMap<QName, ODEProcess> _registeredProcesses = new ConcurrentHashMap<QName, ODEProcess>(); /** Mapping from myrole service name to active process. */ private final HashMap<QName, List<ODEProcess>> _serviceMap = new HashMap<QName, List<ODEProcess>>(); /** Weak-reference cache of all the my-role message exchange objects. */ private final MyRoleMessageExchangeCache _myRoleMexCache = new MyRoleMessageExchangeCache(); private State _state = State.SHUTDOWN; Contexts _contexts = new Contexts(); private DehydrationPolicy _dehydrationPolicy; private OdeConfigProperties _properties; private ExecutorService _exec; BpelDatabase _db; /** The last time we started a {@link ServerCallable}. Useful for keeping track of idleness. */ private final AtomicLong _lastTimeOfServerCallable = new AtomicLong(System.currentTimeMillis()); /** Mapping from a potentially shared endpoint to its EPR */ private SharedEndpoints _sharedEps; 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() {} protected void waitForQuiessence() { do { long ltime = _lastTimeOfServerCallable.get(); try { Thread.sleep(150); } catch (InterruptedException e) {; } try { Thread.sleep(150); } catch (InterruptedException ie) {; } if (_lastTimeOfServerCallable.get() == ltime) return; } while (true); } public void start() { if (!checkState(State.INIT, State.RUNNING)) { __log.debug("start() ignored -- already started"); return; } __log.debug("BPEL SERVER starting."); if (_exec == null) { ThreadFactory threadFactory = new ThreadFactory() { int threadNumber = 0; public Thread newThread(Runnable r) { threadNumber += 1; Thread t = new Thread(r, "ODEServerImpl-" + threadNumber); t.setDaemon(true); return t; } }; _exec = Executors.newCachedThreadPool(threadFactory); } if (_contexts.txManager == null) { String errmsg = "Transaction manager not specified; call setTransactionManager(...)!"; __log.fatal(errmsg); throw new IllegalStateException(errmsg); } if (_contexts.scheduler == null) { String errmsg = "Scheduler not specified; call setScheduler(...)!"; __log.fatal(errmsg); throw new IllegalStateException(errmsg); } _contexts.scheduler.start(); _state = State.RUNNING; __log.info(__msgs.msgServerStarted()); if (_dehydrationPolicy != null) new Thread(new ProcessDefReaper()).start(); } public BpelDatabase getBpelDb() { return _db; } 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) { listener.startup(_properties.getProperties()); // Do not synchronize, eventListeners is copy-on-write array. _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. if (_contexts.eventListeners.remove(listener)) { try { listener.shutdown(); } catch (Exception e) { __log.warn( "Stopping BPEL event listener " + listener.getClass().getName() + " failed, nevertheless it has been unregistered.", e); } } } private void unregisterBpelEventListeners() { for (BpelEventListener l : _contexts.eventListeners) { unregisterBpelEventListener(l); } } public void registerExtensionBundle(ExtensionBundleRuntime bundle) { _contexts.extensionRegistry.put(bundle.getNamespaceURI(), bundle); bundle.registerExtensionActivities(); } public void unregisterExtensionBundle(String nsURI) { _contexts.extensionRegistry.remove(nsURI); } public void stop() { if (!checkState(State.RUNNING, State.INIT)) { __log.debug("stop() ignored -- already stopped"); return; } __log.debug("BPEL SERVER STOPPING"); _contexts.scheduler.stop(); _state = State.INIT; __log.info(__msgs.msgServerStopped()); } public void init() throws BpelEngineException { if (!checkState(State.SHUTDOWN, State.INIT)) return; __log.debug("BPEL SERVER initializing "); _db = new BpelDatabase(_contexts); _state = State.INIT; _sharedEps = new SharedEndpoints(); _sharedEps.init(); } public void shutdown() throws BpelEngineException { stop(); unregisterBpelEventListeners(); _sharedEps = null; _db = null; _state = State.SHUTDOWN; } 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. // If the process is already active, do nothing. if (_registeredProcesses.containsKey(conf.getProcessId())) { __log.debug( "skipping doRegister" + conf.getProcessId() + ") -- process is already registered"); return; } __log.debug("Registering process " + conf.getProcessId() + " with server."); ODEProcess process = new ODEProcess(this, conf, null, _myRoleMexCache); for (Endpoint e : process.getServiceNames()) { __log.debug("Register process: serviceId=" + e + ", process=" + process); // Get the list of processes associated with the given service List<ODEProcess> processes = _serviceMap.get(e.serviceName); if (processes == null) { // Create an empty list, if no processes were associated _serviceMap.put(e.serviceName, processes = new ArrayList<ODEProcess>()); } // Remove any older version of the process from the list for (int i = 0; i < processes.size(); i++) { ODEProcess cachedVersion = processes.get(i); __log.debug( "cached version " + cachedVersion.getPID() + " vs registering version " + process.getPID()); if (cachedVersion.getProcessType().equals(process.getProcessType())) { processes.remove(cachedVersion); } } // Add the given process to the list associated with the given service processes.add(process); } process.activate(_contexts); _registeredProcesses.put(process.getPID(), process); if (_dehydrationPolicy == null) process.hydrate(); __log.info(__msgs.msgProcessRegistered(conf.getProcessId())); } public void unregister(QName pid) throws BpelEngineException { if (__log.isTraceEnabled()) __log.trace("unregister: " + pid); try { ODEProcess p = _registeredProcesses.remove(pid); if (p == null) return; // TODO Looks like there are some possible bugs here, if a new version of a process gets // deployed, the service will be removed. p.deactivate(); // Remove the process from any services that might reference it. // However, don't remove the service itself from the map. for (List<ODEProcess> processes : _serviceMap.values()) { __log.debug( "removing process " + pid + "; handle " + p + "; exists " + processes.contains(p)); processes.remove(p); } __log.info(__msgs.msgProcessUnregistered(pid)); } catch (Exception ex) { __log.error(__msgs.msgProcessUnregisterFailed(pid), ex); throw new BpelEngineException(ex); } } public void cleanupProcess(QName pid) throws BpelEngineException { deleteProcessDAO(pid); } /** * 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.globalIntereceptors.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.globalIntereceptors.remove(interceptor); } /** * Route to a process using the service id. Note, that we do not need the endpoint name here, we * are assuming that two processes would not be registered under the same service qname but * different endpoint. * * @param service target service id * @param request request message * @return process corresponding to the targetted service, or <code>null</code> if service * identifier is not recognized. */ List<ODEProcess> route(QName service, Message request) { // TODO: use the message to route to the correct service if more than // one service is listening on the same endpoint. return _serviceMap.get(service); } /** Check a state transition from state "i" to state "j". */ private final boolean checkState(State i, State j) { if (_state == i) return true; if (_state == j) return false; return false; } /* TODO: We need to have a method of cleaning up old deployment data. */ private boolean deleteProcessDAO(final QName pid) { try { // Delete it from the database. return _db.exec( new BpelDatabase.Callable<Boolean>() { public Boolean run(BpelDAOConnection conn) throws Exception { ProcessDAO proc = conn.getProcess(pid); if (proc != null) { proc.delete(); return true; } return false; } }); } catch (Exception ex) { String errmsg = "DbError"; __log.error(errmsg, ex); throw new BpelEngineException(errmsg, ex); } } public void onScheduledJob(final JobInfo jobInfo) throws JobProcessorException { try { final WorkEvent we = new WorkEvent(jobInfo.jobDetail); ODEProcess process = _registeredProcesses.get(we.getProcessId()); if (process == null) { // If the process is not active, it means that we should not be // doing any work on its behalf, therefore we will reschedule the // events for some time in the future (1 minute). // 31-05-2010 // According to the bpel server logic, we won't find a process with matching // process id in future for this type of case where we can't find the process at // first job invocation. Because all the processes(both hydrated and dehydrated) are there // in _registeredProcesses Map. And even we deploy a new version of the process, it's // QName will be different because of the versioning. // So I am removing scheduling part. // TODO: Let's revert the logic if an issue occurred because of this. _contexts.execTransaction( new Callable<Void>() { public Void call() throws Exception { _contexts.scheduler.jobCompleted(jobInfo.jobName); // Date future = new Date(System.currentTimeMillis() + (60 * 1000)); // __log.info(__msgs.msgReschedulingJobForInactiveProcess(we.getProcessId(), // jobInfo.jobName, future)); // _contexts.scheduler.schedulePersistedJob(we.getDetail(), future); return null; } }); return; } if (we.getType().equals(WorkEvent.Type.INVOKE_CHECK)) { if (__log.isDebugEnabled()) __log.debug("handleWorkEvent: InvokeCheck event for mexid " + we.getMexId()); PartnerRoleMessageExchange mex = (PartnerRoleMessageExchange) getMessageExchange(we.getMexId()); if (mex.getStatus() == MessageExchange.Status.ASYNC || mex.getStatus() == MessageExchange.Status.ACK) { String msg = "Dangling invocation (mexId=" + we.getMexId() + "), forcing it into a failed state."; if (__log.isDebugEnabled()) __log.debug(msg); mex.replyWithFailure(MessageExchange.FailureType.COMMUNICATION_ERROR, msg, null); } return; } process.handleWorkEvent(jobInfo); } catch (Exception ex) { throw new JobProcessorException(ex, jobInfo.jobDetail.get("inmem") == null); } } public void setTransactionManager(TransactionManager txm) { _contexts.txManager = txm; } public void setDehydrationPolicy(DehydrationPolicy dehydrationPolicy) { _dehydrationPolicy = dehydrationPolicy; } public void setConfigProperties(OdeConfigProperties properties) { _properties = properties; } public OdeConfigProperties getConfigProperties() { return _properties; } public void setMessageExchangeContext(MessageExchangeContext mexContext) throws BpelEngineException { _contexts.mexContext = mexContext; } public void setScheduler(Scheduler scheduler) throws BpelEngineException { _contexts.scheduler = scheduler; } 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 { _contexts.dao = daoCF; } public void setBindingContext(BindingContext bc) { _contexts.bindingContext = bc; } public SharedEndpoints getSharedEndpoints() { return _sharedEps; } public void setExecutor(ExecutorService exec) { _exec = exec; } public MyRoleMessageExchange createMessageExchange( final InvocationStyle istyle, final QName targetService, final String operation, final String clientKey) throws BpelEngineException { // Obtain the list of processes that this service is potentially targeted at final List<ODEProcess> targets = route(targetService, null); if (targets == null || targets.size() == 0) throw new BpelEngineException("NoSuchService: " + targetService); if (targets.size() == 1) { // If the number of targets is one, create and return a simple MEX ODEProcess target = targets.get(0); return createNewMyRoleMex(target, istyle, targetService, operation, clientKey); } else { // If the number of targets is greater than one, create and return // a brokered MEX that embeds the simple MEXs for each of the targets ArrayList<MyRoleMessageExchange> meps = new ArrayList<MyRoleMessageExchange>(); for (ODEProcess target : targets) { meps.add(createNewMyRoleMex(target, istyle, targetService, operation, clientKey)); } return createNewMyRoleMex(targets.get(0), meps, istyle); } } /** * Return a simple type of MEX for a given process target * * @param process * @param istyle * @param targetService * @param operation * @param clientKey * @return */ private MyRoleMessageExchange createNewMyRoleMex( ODEProcess process, final InvocationStyle istyle, final QName targetService, final String operation, final String clientKey) { if (istyle == InvocationStyle.RELIABLE || istyle == InvocationStyle.TRANSACTED) assertTransaction(); else assertNoTransaction(); return process.createNewMyRoleMex(istyle, targetService, operation); } /** * Return a brokered MEX that delegates invocations to each of the embedded MEXs contained in the * <code>meps</code> list, using the appropriate style. * * @param target * @param meps * @param istyle * @return * @throws BpelEngineException */ private MyRoleMessageExchange createNewMyRoleMex( ODEProcess target, List<MyRoleMessageExchange> meps, InvocationStyle istyle) throws BpelEngineException { String mexId = new GUID().toString(); MyRoleMessageExchange template = meps.get(0); switch (istyle) { case RELIABLE: return new BrokeredReliableMyRoleMessageExchangeImpl(target, meps, mexId, template); case TRANSACTED: return new BrokeredTransactedMyRoleMessageExchangeImpl(target, meps, mexId, template); case UNRELIABLE: return new BrokeredUnreliableMyRoleMessageExchangeImpl(target, meps, mexId, template); case P2P: default: throw new BpelEngineException("Unsupported Invocation Style: " + istyle); } } public MessageExchange getMessageExchange(final String mexId) throws BpelEngineException { final MessageExchangeDAO inmemdao = getInMemMexDAO(mexId); Callable<MessageExchange> loadMex = new Callable<MessageExchange>() { public MessageExchange call() { MessageExchangeDAO mexdao = (inmemdao == null) ? mexdao = _contexts.dao.getConnection().getMessageExchange(mexId) : inmemdao; if (mexdao == null) return null; ProcessDAO pdao = mexdao.getProcess(); ODEProcess process = pdao == null ? null : _registeredProcesses.get(pdao.getProcessId()); if (process == null) { String errmsg = __msgs.msgProcessNotActive(pdao.getProcessId()); __log.error(errmsg); // TODO: Perhaps we should define a checked exception for this // condition. throw new BpelEngineException(errmsg); } InvocationStyle istyle = mexdao.getInvocationStyle(); if (istyle == InvocationStyle.RELIABLE || istyle == InvocationStyle.TRANSACTED) assertTransaction(); switch (mexdao.getDirection()) { case MessageExchangeDAO.DIR_BPEL_INVOKES_PARTNERROLE: return process.createPartnerRoleMex(mexdao); case MessageExchangeDAO.DIR_PARTNER_INVOKES_MYROLE: return process.lookupMyRoleMex(mexdao); default: String errmsg = "BpelEngineImpl: internal error, invalid MexDAO direction: " + mexId; __log.fatal(errmsg); throw new BpelEngineException(errmsg); } } }; try { if (inmemdao != null || _contexts.isTransacted()) return loadMex.call(); else return enqueueTransaction(loadMex).get(); } catch (ContextException e) { throw new BpelEngineException(e); } catch (Exception e) { throw new BpelEngineException(e); } } public MessageExchange getMessageExchangeByForeignKey(String foreignKey) throws BpelEngineException { // TODO Auto-generated method stub return null; } /* * Calculate the invocation style of the target(s) associated with the given service * If more than one target exists, then take the intersection of their individual styles. * * (non-Javadoc) * @see org.apache.ode.bpel.iapi.BpelServer#getSupportedInvocationStyle(javax.xml.namespace.QName) */ public Set<InvocationStyle> getSupportedInvocationStyle(QName serviceId) { List<ODEProcess> processes = route(serviceId, null); if (processes == null || processes.size() == 0) throw new BpelEngineException("No such service: " + serviceId); // Compute the intersection of the styles of all providing processes Set<InvocationStyle> istyles = new HashSet<InvocationStyle>(); for (ODEProcess process : processes) { Set<InvocationStyle> pistyles = process.getSupportedInvocationStyle(serviceId); if (istyles.isEmpty()) { istyles.addAll(pistyles); } else { for (InvocationStyle istyle : istyles) { if (!pistyles.contains(istyle)) { istyles.remove(istyle); } } } } return istyles; } MessageExchangeDAO getInMemMexDAO(String mexId) { for (ODEProcess p : _registeredProcesses.values()) { MessageExchangeDAO mexDao = p.getInMemMexDAO(mexId); if (mexDao != null) return mexDao; } return null; } public ProcessModel getProcessModel(QName processId) { ODEProcess process = _registeredProcesses.get(processId); if (process == null) return null; return process.getProcessModel(); } <T> Future<T> enqueueTransaction(final Callable<T> transaction) throws ContextException { return _exec.submit(new ServerCallable<T>(new TransactedCallable<T>(transaction))); } void enqueueRunnable(final Runnable runnable) { _exec.submit(new ServerRunnable(runnable)); } /** * Schedule a {@link Runnable} object for execution after the completion of the current * transaction. * * @param runnable */ void scheduleRunnable(final Runnable runnable) { assertTransaction(); _contexts.registerCommitSynchronizer( new Runnable() { public void run() { _exec.submit(new ServerRunnable(runnable)); } }); } protected void assertTransaction() { if (!_contexts.isTransacted()) throw new BpelEngineException("Operation must be performed in a transaction!"); } protected void assertNoTransaction() { if (_contexts.isTransacted()) throw new BpelEngineException("Operation must be performed outside of a transaction!"); } void fireEvent(BpelEvent event) { // Note that the eventListeners list is a copy-on-write array, so need // to mess with synchronization. for (org.apache.ode.bpel.iapi.BpelEventListener l : _contexts.eventListeners) { l.onEvent(event); } } /** * Block the thread for random amount of time. Used for testing for races and the like. The delay * generated is exponentially distributed with the mean obtained from the <code>ODE_DEBUG_TX_DELAY * </code> environment variable. */ private void debuggingDelay() { // Do a delay for debugging purposes. if (_delayMean != 0) try { long delay = randomExp(_delayMean); // distribution // with mean // _delayMean __log.warn("Debugging delay has been activated; delaying transaction for " + delay + "ms."); Thread.sleep(delay); } catch (InterruptedException e) {; // ignore } } private long randomExp(double mean) { double u = _random.nextDouble(); // Uniform return (long) (-Math.log(u) * mean); } private class ProcessDefReaper implements Runnable { public void run() { __log.debug("Starting process definition reaper thread."); long pollingTime = 10000; try { while (true) { Thread.sleep(pollingTime); // Copying the runnning process list to avoid synchronizatMessageExchangeInterion // problems and a potential mess if a policy modifies the list List<ODEProcess> candidates = new ArrayList<ODEProcess>(_registeredProcesses.values()); CollectionsX.remove_if( candidates, new MemberOfFunction<ODEProcess>() { public boolean isMember(ODEProcess o) { return !o.hintIsHydrated(); } }); // And the happy winners are... List<ODEProcess> ripped = _dehydrationPolicy.markForDehydration(candidates); // Bye bye for (ODEProcess process : ripped) { __log.debug("Dehydrating process " + process.getPID()); process.dehydrate(); } } } catch (InterruptedException e) { __log.info(e); } } } public ODEProcess getBpelProcess(QName processId) { return _registeredProcesses.get(processId); } private void ticktock() { _lastTimeOfServerCallable.set(System.currentTimeMillis()); } class ServerRunnable implements Runnable { final Runnable _work; ServerRunnable(Runnable work) { _work = work; } public void run() { ticktock(); try { ticktock(); _work.run(); ticktock(); } catch (Throwable ex) { ticktock(); __log.fatal("Internal Error", ex); } } } class ServerCallable<T> implements Callable<T> { final Callable<T> _work; ServerCallable(Callable<T> work) { _work = work; } public T call() throws Exception { ticktock(); try { ticktock(); return _work.call(); } catch (Exception ex) { ticktock(); __log.fatal("Internal Error", ex); throw ex; } finally { ticktock(); } } } class TransactedCallable<T> implements Callable<T> { Callable<T> _work; TransactedCallable(Callable<T> work) { _work = work; } public T call() throws Exception { return _contexts.execTransaction(_work); } } class TransactedRunnable implements Runnable { Runnable _work; TransactedRunnable(Runnable work) { _work = work; } public void run() { _contexts.execTransaction(_work); } } public void setTransacted(boolean atomicScope) {} }
/** @author Matthieu Riou <mriou at apache dot org> */ public class PartnerLinkMyRoleImpl extends PartnerLinkRoleImpl { private static final Log __log = LogFactory.getLog(BpelProcess.class); private static final Messages __msgs = MessageBundle.getMessages(Messages.class); /** The local endpoint for this "myrole". */ public Endpoint _endpoint; PartnerLinkMyRoleImpl(BpelProcess process, OPartnerLink plink, Endpoint endpoint) { super(process, plink); _endpoint = endpoint; } public String toString() { StringBuffer buf = new StringBuffer("{PartnerLinkRole-"); buf.append(_plinkDef.name); buf.append('.'); buf.append(_plinkDef.myRoleName); buf.append(" on "); buf.append(_endpoint); buf.append('}'); return buf.toString(); } public boolean isCreateInstance(MyRoleMessageExchangeImpl mex) { Operation operation = getMyRoleOperation(mex.getOperationName()); return _plinkDef.isCreateInstanceOperation(operation); } public List<RoutingInfo> findRoute(MyRoleMessageExchangeImpl mex) { List<RoutingInfo> routingInfos = new ArrayList<RoutingInfo>(); if (__log.isTraceEnabled()) { __log.trace( ObjectPrinter.stringifyMethodEnter( this + ":inputMsgRcvd", new Object[] {"messageExchange", mex})); } Operation operation = getMyRoleOperation(mex.getOperationName()); if (operation == null) { __log.error( __msgs.msgUnknownOperation(mex.getOperationName(), _plinkDef.myRolePortType.getQName())); mex.setFailure(MessageExchange.FailureType.UNKNOWN_OPERATION, mex.getOperationName(), null); return null; } setMexRole(mex); // now, the tricks begin: when a message arrives we have to see if there // is anyone waiting for it. Get the correlator, a persisted communication-reduction // data structure supporting correlation correlationKey matching! String correlatorId = BpelProcess.genCorrelatorId(_plinkDef, operation.getName()); CorrelatorDAO correlator = _process.getProcessDAO().getCorrelator(correlatorId); CorrelationKeySet keySet; // We need to compute the correlation keys (based on the operation // we can infer which correlation keys to compute - this is merely a set // consisting of each correlationKey used in each correlation sets // that is ever referenced in an <receive>/<onMessage> on this // partnerlink/operation. try { keySet = computeCorrelationKeys(mex); } catch (InvalidMessageException ime) { // We'd like to do a graceful exit here, no sense in rolling back due to a // a message format problem. __log.debug("Unable to evaluate correlation keys, invalid message format. ", ime); mex.setFailure(MessageExchange.FailureType.FORMAT_ERROR, ime.getMessage(), null); return null; } String mySessionId = mex.getProperty(MessageExchange.PROPERTY_SEP_MYROLE_SESSIONID); String partnerSessionId = mex.getProperty(MessageExchange.PROPERTY_SEP_PARTNERROLE_SESSIONID); if (__log.isDebugEnabled()) { __log.debug( "INPUTMSG: " + correlatorId + ": MSG RCVD keys=" + keySet + " mySessionId=" + mySessionId + " partnerSessionId=" + partnerSessionId); } // Try to find a route for one of our keys. List<MessageRouteDAO> messageRoutes = correlator.findRoute(keySet); if (messageRoutes != null && messageRoutes.size() > 0) { for (MessageRouteDAO messageRoute : messageRoutes) { if (__log.isDebugEnabled()) { __log.debug( "INPUTMSG: " + correlatorId + ": ckeySet " + messageRoute.getCorrelationKeySet() + " route is to " + messageRoute); } routingInfos.add( new RoutingInfo(messageRoute, messageRoute.getCorrelationKeySet(), correlator, keySet)); } } if (routingInfos.size() == 0) { routingInfos.add(new RoutingInfo(null, null, correlator, keySet)); } return routingInfos; } public static class RoutingInfo { public MessageRouteDAO messageRoute; public CorrelationKeySet matchedKeySet; public CorrelatorDAO correlator; // CorrelationKey[] keys; public CorrelationKeySet wholeKeySet; public RoutingInfo( MessageRouteDAO messageRoute, CorrelationKeySet matchedKeySet, CorrelatorDAO correlator, CorrelationKeySet wholeKeySet) { this.messageRoute = messageRoute; this.matchedKeySet = matchedKeySet; this.correlator = correlator; this.wholeKeySet = wholeKeySet; } } public void invokeNewInstance(MyRoleMessageExchangeImpl mex, RoutingInfo routing) { Operation operation = getMyRoleOperation(mex.getOperationName()); if (__log.isDebugEnabled()) { __log.debug( "INPUTMSG: " + routing.correlator.getCorrelatorId() + ": routing failed, CREATING NEW INSTANCE"); } ProcessDAO processDAO = _process.getProcessDAO(); if (_process._pconf.getState() == ProcessState.RETIRED) { throw new InvalidProcessException( "Process is retired.", InvalidProcessException.RETIRED_CAUSE_CODE); } if (!_process.processInterceptors(mex, InterceptorInvoker.__onNewInstanceInvoked)) { __log.debug("Not creating a new instance for mex " + mex + "; interceptor prevented!"); throw new InvalidProcessException( "Cannot instantiate process '" + _process.getPID() + "' any more.", InvalidProcessException.TOO_MANY_INSTANCES_CAUSE_CODE); } ProcessInstanceDAO newInstance = processDAO.createInstance(routing.correlator); BpelRuntimeContextImpl instance = _process.createRuntimeContext(newInstance, new PROCESS(_process.getOProcess()), mex); // send process instance event NewProcessInstanceEvent evt = new NewProcessInstanceEvent( new QName(_process.getOProcess().targetNamespace, _process.getOProcess().getName()), _process.getProcessDAO().getProcessId(), newInstance.getInstanceId()); evt.setPortType(mex.getPortType().getQName()); evt.setOperation(operation.getName()); evt.setMexId(mex.getMessageExchangeId()); _process._debugger.onEvent(evt); _process.saveEvent(evt, newInstance); mex.setCorrelationStatus(MyRoleMessageExchange.CorrelationStatus.CREATE_INSTANCE); mex.getDAO().setInstance(newInstance); if (mex.getDAO().getCreateTime() == null) mex.getDAO().setCreateTime(instance.getCurrentEventDateTime()); _process._engine.acquireInstanceLock(newInstance.getInstanceId()); instance.execute(); } public void invokeInstance(MyRoleMessageExchangeImpl mex, RoutingInfo routing) { Operation operation = getMyRoleOperation(mex.getOperationName()); if (__log.isDebugEnabled()) { __log.debug( "INPUTMSG: " + routing.correlator.getCorrelatorId() + ": ROUTING to existing instance " + routing.messageRoute.getTargetInstance().getInstanceId()); } ProcessInstanceDAO instanceDao = routing.messageRoute.getTargetInstance(); BpelProcess process2 = _process._engine._activeProcesses.get(instanceDao.getProcess().getProcessId()); // Reload process instance for DAO. BpelRuntimeContextImpl instance = process2.createRuntimeContext(instanceDao, null, null); instance.inputMsgMatch(routing.messageRoute.getGroupId(), routing.messageRoute.getIndex(), mex); // Kill the route so some new message does not get routed to // same process instance. routing.correlator.removeRoutes(routing.messageRoute.getGroupId(), instanceDao); // send process instance event CorrelationMatchEvent evt = new CorrelationMatchEvent( new QName(process2.getOProcess().targetNamespace, process2.getOProcess().getName()), process2.getProcessDAO().getProcessId(), instanceDao.getInstanceId(), routing.matchedKeySet); evt.setPortType(mex.getPortType().getQName()); evt.setOperation(operation.getName()); evt.setMexId(mex.getMessageExchangeId()); process2._debugger.onEvent(evt); // store event process2.saveEvent(evt, instanceDao); mex.setCorrelationStatus(MyRoleMessageExchange.CorrelationStatus.MATCHED); mex.getDAO().setInstance(routing.messageRoute.getTargetInstance()); if (mex.getDAO().getCreateTime() == null) mex.getDAO().setCreateTime(instance.getCurrentEventDateTime()); instance.execute(); } public void noRoutingMatch(MyRoleMessageExchangeImpl mex, List<RoutingInfo> routings) { if (!mex.isAsynchronous()) { mex.setFailure( MessageExchange.FailureType.NOMATCH, "No process instance matching correlation keys.", null); } else { // enqueue message with the last message route, as per the comments in caller (@see // BpelProcess.invokeProcess()) RoutingInfo routing = (routings != null && routings.size() > 0) ? routings.get(routings.size() - 1) : null; if (routing != null) { if (__log.isDebugEnabled()) { __log.debug( "INPUTMSG: " + routing.correlator.getCorrelatorId() + ": SAVING to DB (no match) "); } // send event CorrelationNoMatchEvent evt = new CorrelationNoMatchEvent( mex.getPortType().getQName(), mex.getOperation().getName(), mex.getMessageExchangeId(), routing.wholeKeySet); evt.setProcessId(_process.getProcessDAO().getProcessId()); evt.setProcessName( new QName(_process.getOProcess().targetNamespace, _process.getOProcess().getName())); _process._debugger.onEvent(evt); mex.setCorrelationStatus(MyRoleMessageExchange.CorrelationStatus.QUEUED); // No match, means we add message exchange to the queue. routing.correlator.enqueueMessage(mex.getDAO(), routing.wholeKeySet); } } } private void setMexRole(MyRoleMessageExchangeImpl mex) { Operation operation = getMyRoleOperation(mex.getOperationName()); mex.getDAO().setPartnerLinkModelId(_plinkDef.getId()); mex.setPortOp(_plinkDef.myRolePortType, operation); mex.setPattern( operation.getOutput() == null ? MessageExchange.MessageExchangePattern.REQUEST_ONLY : MessageExchange.MessageExchangePattern.REQUEST_RESPONSE); } private Operation getMyRoleOperation(String operationName) { return _plinkDef.getMyRoleOperation(operationName); } private CorrelationKeySet computeCorrelationKeys(MyRoleMessageExchangeImpl mex) { CorrelationKeySet keySet = new CorrelationKeySet(); Operation operation = mex.getOperation(); Element msg = mex.getRequest().getMessage(); javax.wsdl.Message msgDescription = operation.getInput().getMessage(); Set<OScope.CorrelationSet> csets = _plinkDef.getNonInitiatingCorrelationSetsForOperation(operation); for (OScope.CorrelationSet cset : csets) { CorrelationKey key = computeCorrelationKey( cset, _process.getOProcess().messageTypes.get(msgDescription.getQName()), msg); keySet.add(key); } csets = _plinkDef.getJoinningCorrelationSetsForOperation(operation); for (OScope.CorrelationSet cset : csets) { CorrelationKey key = computeCorrelationKey( cset, _process.getOProcess().messageTypes.get(msgDescription.getQName()), msg); keySet.add(key); } // Let's creata a key based on the sessionId String mySessionId = mex.getProperty(MessageExchange.PROPERTY_SEP_MYROLE_SESSIONID); if (mySessionId != null) keySet.add(new CorrelationKey("-1", new String[] {mySessionId})); return keySet; } @SuppressWarnings("unchecked") private CorrelationKey computeCorrelationKey( OScope.CorrelationSet cset, OMessageVarType messagetype, Element msg) { CorrelationKey key = null; String[] values = new String[cset.properties.size()]; int jIdx = 0; for (Iterator j = cset.properties.iterator(); j.hasNext(); ++jIdx) { OProcess.OProperty property = (OProcess.OProperty) j.next(); OProcess.OPropertyAlias alias = property.getAlias(messagetype); if (alias == null) { // TODO: Throw a real exception! And catch this at compile // time. throw new IllegalArgumentException( "No alias matching property '" + property.name + "' with message type '" + messagetype + "'"); } String value; try { value = _process.extractProperty(msg, alias, msg.toString()); } catch (FaultException fe) { String emsg = __msgs.msgPropertyAliasDerefFailedOnMessage(alias.getDescription(), fe.getMessage()); __log.error(emsg, fe); throw new InvalidMessageException(emsg, fe); } values[jIdx] = value; } if (cset.hasJoinUseCases) { key = new OptionalCorrelationKey(cset.name, values); } else { key = new CorrelationKey(cset.name, values); } return key; } @SuppressWarnings("unchecked") public boolean isOneWayOnly() { PortType portType = _plinkDef.myRolePortType; if (portType == null) { return false; } for (Operation operation : (List<Operation>) portType.getOperations()) { if (operation.getOutput() != null) { return false; } } return true; } }
/** @author mriou <mriou at apache dot org> */ public class JaxpFunctionResolver implements XPathFunctionResolver { private static final XPathMessages __msgs = MessageBundle.getMessages(XPathMessages.class); private CompilerContext _cctx; private OXPath20ExpressionBPEL20 _out; private NSContext _nsContext; private String _bpelNS; public JaxpFunctionResolver( CompilerContext cctx, OXPath20ExpressionBPEL20 out, NSContext nsContext, String bpelNS) { _cctx = cctx; _bpelNS = bpelNS; _nsContext = nsContext; _bpelNS = bpelNS; _out = out; } public XPathFunction resolveFunction(QName functionName, int arity) { if (functionName.getNamespaceURI() == null) { throw new WrappedResolverException("Undeclared namespace for " + functionName); } else if (functionName.getNamespaceURI().equals(_bpelNS)) { String localName = functionName.getLocalPart(); if (Constants.EXT_FUNCTION_GETVARIABLEPROPERTY.equals(localName)) { return new GetVariableProperty(); } else if (Constants.EXT_FUNCTION_DOXSLTRANSFORM.equals(localName)) { return new DoXslTransform(); } else { throw new WrappedResolverException(__msgs.errUnknownBpelFunction(localName)); } } else if (functionName.getNamespaceURI().equals(Namespaces.ODE_EXTENSION_NS)) { String localName = functionName.getLocalPart(); if (Constants.NON_STDRD_FUNCTION_SPLIT_TO_ELEMENTS.equals(localName) || Constants.NON_STDRD_FUNCTION_DEPRECATED_SPLIT_TO_ELEMENTS.equals(localName)) { return new SplitToElements(); } else if (Constants.NON_STDRD_FUNCTION_COMBINE_URL.equals(localName) || Constants.NON_STDRD_FUNCTION_DEPRECATED_COMBINE_URL.equals(localName)) { return new CombineUrl(); } else if (Constants.NON_STDRD_FUNCTION_COMPOSE_URL.equals(localName) || Constants.NON_STDRD_FUNCTION_EXPAND_TEMPLATE.equals(localName) || Constants.NON_STDRD_FUNCTION_DEPRECATED_COMPOSE_URL.equals(localName) || Constants.NON_STDRD_FUNCTION_DEPRECATED_EXPAND_TEMPLATE.equals(localName)) { return new ComposeUrl(); } else if (Constants.NON_STDRD_FUNCTION_DOM_TO_STRING.equals(localName) || Constants.NON_STDRD_FUNCTION_DEPRECATED_DOM_TO_STRING.equals(localName)) { return new DomToString(); } else if (Constants.NON_STDRD_FUNCTION_INSERT_AFTER.equals(localName)) { return new InsertAfter(); } else if (Constants.NON_STDRD_FUNCTION_INSERT_AS_FIRST_INTO.equals(localName)) { return new InsertAsFirstInto(); } else if (Constants.NON_STDRD_FUNCTION_INSERT_AS_LAST_INTO.equals(localName)) { return new InsertAsLastInto(); } else if (Constants.NON_STDRD_FUNCTION_INSERT_BEFORE.equals(localName)) { return new InsertBefore(); } else if (Constants.NON_STDRD_FUNCTION_DELETE.equals(localName)) { return new Delete(); } else if (Constants.NON_STDRD_FUNCTION_RENAME.equals(localName)) { return new Rename(); } else if (Constants.NON_STDRD_FUNCTION_PROCESS_PROPERTY.equals(localName)) { return new ProcessProperty(); } } else if (functionName.getNamespaceURI().equals(Namespaces.DEPRECATED_XDT_NS)) { String localName = functionName.getLocalPart(); if (Constants.NON_STDRD_FUNCTION_DAY_TIME_DURATION.equals(localName)) { return new DayTimeDuration(); } else if (Constants.NON_STDRD_FUNCTION_YEAR_MONTH_DURATION.equals(localName)) { return new YearMonthDuration(); } } return null; } public class GetVariableProperty implements XPathFunction { public Object evaluate(List params) throws XPathFunctionException { if (params.size() != 2) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.EXT_FUNCTION_GETVARIABLEPROPERTY)); } String varName = (String) params.get(0); OScope.Variable v = _cctx.resolveVariable(varName); _out.getVars().put(varName, v); String propName = (String) params.get(1); QName qname = _nsContext.derefQName(propName); if (qname == null) throw new CompilationException(__msgs.errInvalidQName(propName)); OProcess.OProperty property = _cctx.resolveProperty(qname); // Make sure we can... _cctx.resolvePropertyAlias(v, qname); _out.getProperties().put(propName, property); _out.getVars().put(varName, v); return ""; } } public class DoXslTransform implements XPathFunction { public Object evaluate(List params) throws XPathFunctionException { if (params.size() < 2 || params.size() % 2 != 0) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.EXT_FUNCTION_DOXSLTRANSFORM)); } String xslUri = (String) params.get(0); OXslSheet xslSheet = _cctx.compileXslt(xslUri); try { XslTransformHandler.getInstance() .parseXSLSheet( _cctx.getOProcess().getQName(), xslSheet.getUri(), xslSheet.getSheetBody(), new XslCompileUriResolver(_cctx, _out)); } catch (Exception e) { throw new CompilationException(__msgs.errXslCompilation(xslUri, e.toString())); } _out.setXslSheet(xslSheet.getUri(), xslSheet); return ""; } } /** Compile time checking for the non standard ode:splitToElements function. */ public static class SplitToElements implements XPathFunction { public Object evaluate(List params) throws XPathFunctionException { if (params.size() < 3 || params.size() > 4) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_SPLIT_TO_ELEMENTS)); } return ""; } } public static class CombineUrl implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { if (args.size() != 2) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_COMBINE_URL)); } return ""; } } public static class DomToString implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { if (args.size() != 1) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_DOM_TO_STRING)); } return ""; } } public static class ComposeUrl implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { boolean separareParameteters; if (args.size() != 2 && (args.size() <= 2 || args.size() % 2 != 1)) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_COMPOSE_URL)); } return ""; } } public static class InsertInto implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { if (args.size() != 3) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_INSERT_AFTER)); } return ""; } } public static class InsertAfter implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { if (args.size() < 2 || args.size() > 3) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_INSERT_AFTER)); } return ""; } } public static class InsertBefore implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { if (args.size() < 2 || args.size() > 3) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_INSERT_BEFORE)); } return ""; } } public static class InsertAsFirstInto implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { if (args.size() != 2) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_INSERT_AS_FIRST_INTO)); } return ""; } } public static class InsertAsLastInto implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { if (args.size() != 2) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_INSERT_AS_LAST_INTO)); } return ""; } } public static class Delete implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { if (args.size() < 1 || args.size() > 2) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_DELETE)); } return ""; } } public static class Rename implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { if (args.size() < 2) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_RENAME)); } return ""; } } public static class ProcessProperty implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { if (args.size() != 1) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_PROCESS_PROPERTY)); } return ""; } } public static class DayTimeDuration implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { if (args.size() != 1) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_DAY_TIME_DURATION)); } return ""; } } public static class YearMonthDuration implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { if (args.size() != 1) { throw new CompilationException( __msgs.errInvalidNumberOfArguments(Constants.NON_STDRD_FUNCTION_YEAR_MONTH_DURATION)); } return ""; } } }
public class DbConfStoreConnectionFactory implements ConfStoreConnectionFactory { private static final Log __log = LogFactory.getLog(DbConfStoreConnectionFactory.class); private static final Messages __msgs = MessageBundle.getMessages(Messages.class); private static final String _guid = new GUID().toString(); private static final Map<String, DataSource> _dataSources = new ConcurrentHashMap<String, DataSource>(); private TransactionManager _txMgr; private final DataSource _ds; final SessionFactory _sessionFactory; public DbConfStoreConnectionFactory( DataSource ds, Properties initialProps, boolean createDatamodel, String txFactoryClassName) { _ds = ds; // Don't want to pollute original properties Properties properties = new Properties(); for (Object prop : initialProps.keySet()) { properties.put(prop, initialProps.get(prop)); } __log.debug("using data source: " + ds); _dataSources.put(_guid, ds); if (createDatamodel) { properties.put(Environment.HBM2DDL_AUTO, "create-drop"); } // Note that we don't allow the following properties to be overriden by the client. if (properties.containsKey(Environment.CONNECTION_PROVIDER)) __log.warn("Ignoring user-specified Hibernate property: " + Environment.CONNECTION_PROVIDER); if (properties.containsKey(Environment.TRANSACTION_MANAGER_STRATEGY)) __log.warn( "Ignoring user-specified Hibernate property: " + Environment.TRANSACTION_MANAGER_STRATEGY); if (properties.containsKey(Environment.SESSION_FACTORY_NAME)) __log.warn("Ignoring user-specified Hibernate property: " + Environment.SESSION_FACTORY_NAME); properties.put(SessionManager.PROP_GUID, _guid); properties.put(Environment.CONNECTION_PROVIDER, DataSourceConnectionProvider.class.getName()); properties.put( Environment.TRANSACTION_MANAGER_STRATEGY, HibernateTransactionManagerLookup.class.getName()); properties.put( Environment.TRANSACTION_STRATEGY, "org.hibernate.transaction.JTATransactionFactory"); properties.put(Environment.CURRENT_SESSION_CONTEXT_CLASS, "jta"); if (__log.isDebugEnabled()) __log.debug("Store connection properties: " + properties); initTxMgr(txFactoryClassName); SessionManager.registerTransactionManager(_guid, _txMgr); _sessionFactory = getDefaultConfiguration().setProperties(properties).buildSessionFactory(); } public ConfStoreConnectionHib getConnection() { return new ConfStoreConnectionHib(_sessionFactory.getCurrentSession()); } @SuppressWarnings("unchecked") private void initTxMgr(String txFactoryClassName) { __log.info("ProcessStore initializing transaction manager using " + txFactoryClassName); try { Class<?> txFactClass = getClass().getClassLoader().loadClass(txFactoryClassName); Object txFact = txFactClass.newInstance(); _txMgr = (TransactionManager) txFactClass.getMethod("getTransactionManager", (Class[]) null).invoke(txFact); } catch (Exception e) { __log.fatal( "Couldn't initialize a transaction manager with factory: " + txFactoryClassName, e); throw new RuntimeException( "Couldn't initialize a transaction manager with factory: " + txFactoryClassName, e); } } public void beginTransaction() { try { _txMgr.begin(); } catch (Exception e) { throw new RuntimeException(e); } } public void commitTransaction() { try { _txMgr.commit(); } catch (Exception e) { throw new RuntimeException(e); } } public void rollbackTransaction() { try { _txMgr.rollback(); } catch (Exception e) { throw new RuntimeException(e); } } static Configuration getDefaultConfiguration() throws MappingException { return new Configuration() .addClass(ProcessConfDaoImpl.class) .addClass(DeploymentUnitDaoImpl.class) .addClass(VersionTrackerDAOImpl.class); } public static class DataSourceConnectionProvider implements ConnectionProvider { private String _guid; public DataSourceConnectionProvider() {} public void configure(Properties props) throws HibernateException { _guid = props.getProperty(SessionManager.PROP_GUID); } public Connection getConnection() throws SQLException { return _dataSources.get(_guid).getConnection(); } public void closeConnection(Connection arg0) throws SQLException { arg0.close(); } public void close() throws HibernateException {} public boolean supportsAggressiveRelease() { return true; } } }
/** Generates code for <code><reply></code> activities. */ class ReplyGenerator extends DefaultActivityGenerator { private static final CommonCompilationMessages _cmsgsGeneral = MessageBundle.getMessages(CommonCompilationMessages.class); private static final ReplyGeneratorMessages __cmsgsLocal = MessageBundle.getMessages(ReplyGeneratorMessages.class); public OActivity newInstance(Activity src) { return new OReply(_context.getOProcess(), _context.getCurrent()); } public void compile(OActivity output, Activity src) { ReplyActivity replyDef = (ReplyActivity) src; OReply oreply = (OReply) output; oreply.isFaultReply = replyDef.getFaultName() != null; oreply.partnerLink = _context.resolvePartnerLink(replyDef.getPartnerLink()); oreply.messageExchangeId = replyDef.getMessageExchangeId(); if (replyDef.getVariable() != null) { oreply.variable = _context.resolveVariable(replyDef.getVariable()); if (!(oreply.variable.type instanceof OMessageVarType)) throw new CompilationException( _cmsgsGeneral.errMessageVariableRequired(oreply.variable.name)); } if (oreply.partnerLink.myRolePortType == null) throw new CompilationException( _cmsgsGeneral.errPartnerLinkDoesNotDeclareMyRole(oreply.partnerLink.getName())); // The portType on the reply is not necessary, so we check its validty only when present. if (replyDef.getPortType() != null && !oreply.partnerLink.myRolePortType.getQName().equals(replyDef.getPortType())) throw new CompilationException( _cmsgsGeneral.errPortTypeMismatch( replyDef.getPortType(), oreply.partnerLink.myRolePortType.getQName())); oreply.operation = _context.resolveMyRoleOperation(oreply.partnerLink, replyDef.getOperation()); if (oreply.operation.getOutput() == null) throw new CompilationException( _cmsgsGeneral.errTwoWayOperationExpected(oreply.operation.getName())); if (oreply.isFaultReply) { Fault flt = null; if (replyDef .getFaultName() .getNamespaceURI() .equals(oreply.partnerLink.myRolePortType.getQName().getNamespaceURI())) flt = oreply.operation.getFault(replyDef.getFaultName().getLocalPart()); if (flt == null) throw new CompilationException( __cmsgsLocal.errUndeclaredFault( replyDef.getFaultName().getLocalPart(), oreply.operation.getName())); if (oreply.variable != null && !((OMessageVarType) oreply.variable.type) .messageType.equals(flt.getMessage().getQName())) throw new CompilationException( _cmsgsGeneral.errVariableTypeMismatch( oreply.variable.name, flt.getMessage().getQName(), ((OMessageVarType) oreply.variable.type).messageType)); oreply.fault = replyDef.getFaultName(); } else /* !oreply.isFaultReply */ { assert oreply.fault == null; if (oreply.variable == null) throw new CompilationException(__cmsgsLocal.errOutputVariableMustBeSpecified()); if (!((OMessageVarType) oreply.variable.type) .messageType.equals(oreply.operation.getOutput().getMessage().getQName())) throw new CompilationException( _cmsgsGeneral.errVariableTypeMismatch( oreply.variable.name, oreply.operation.getOutput().getMessage().getQName(), ((OMessageVarType) oreply.variable.type).messageType)); } for (Correlation correlation : replyDef.getCorrelations()) { OScope.CorrelationSet cset = _context.resolveCorrelationSet(correlation.getCorrelationSet()); switch (correlation.getInitiate()) { case UNSET: case NO: oreply.assertCorrelations.add(cset); break; case YES: oreply.initCorrelations.add(cset); break; default: // TODO: Make error for this. throw new AssertionError(); } for (Iterator<OProcess.OProperty> j = cset.properties.iterator(); j.hasNext(); ) { OProcess.OProperty property = j.next(); // Force resolution of alias, to make sure that we have one for this variable-property pair. _context.resolvePropertyAlias(oreply.variable, property.name); } } } }
/** * 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); } }
/** * Implementation of {@link OdeInternalInstance} for the "modern" runtime. This class also serves as a repository for kitchen sink type * methods that the activities all use. A lot of these methods are simply deferals to similar methods on * {@link OdeRTInstanceContext}; however here these methods use representation-specific classes (e.g. * {@link OPartnerLink) while the {@link OdeRTInstanceContext} methods use only the general (non-representation specific) interfaces * (e.g. {@link PartnerLink}. * * @author Maciej Szefler * */ public class RuntimeInstanceImpl implements OdeInternalInstance, OdeRTInstance { private static final Log __log = LogFactory.getLog(RuntimeInstanceImpl.class); private static final Messages __msgs = MessageBundle.getMessages(Messages.class); private OdeRTInstanceContext _brc; /** JACOB VPU */ protected JacobVPU _vpu; /** JACOB ExecutionQueue (state) */ protected ExecutionQueueImpl _soup; private RuntimeImpl _runtime; public RuntimeInstanceImpl(RuntimeImpl runtime, ExecutionQueueImpl soup) { _runtime = runtime; _vpu = new JacobVPU(); _vpu.registerExtension(OdeRTInstanceContext.class, this); if (soup == null) { _soup = new ExecutionQueueImpl(getClass().getClassLoader()); _soup.setGlobalData(new OutstandingRequestManager()); } else { _soup = soup; } _soup.setReplacementMap(_runtime._replacementMap); _vpu.setContext(_soup); } public ProcessModel getProcessModel() { return _runtime._oprocess; } public boolean isCorrelationInitialized(CorrelationSetInstance correlationSet) { return _brc.isCorrelationInitialized(correlationSet); } public boolean isVariableInitialized(VariableInstance var) { return _brc.isVariableInitialized(var); } public boolean isPartnerRoleEndpointInitialized(PartnerLinkInstance pLink) { return _brc.isPartnerRoleEndpointInitialized(pLink); } public void completedFault(FaultData faultData) { cleanupOutstandingMyRoleExchanges(faultData); _brc.completedFault(faultData); } public void completedOk() { cleanupOutstandingMyRoleExchanges(null); _brc.completedOk(); } public Long createScopeInstance(Long parentScopeId, String name, int modelId) { return _brc.createScopeInstance(parentScopeId, name, modelId); } public void initializePartnerLinks(Long parentScopeId, Collection<OPartnerLink> partnerLinks) { _brc.initializePartnerLinks(parentScopeId, partnerLinks); } public void cancelOutstandingRequests(String channelId) { getORM().cancel(channelId); } public void select( PickResponseChannel pickResponseChannel, Date timeout, boolean createInstance, Selector[] selectors) throws FaultException { final String pickResponseChannelStr = pickResponseChannel.export(); int conflict = getORM().findConflict(selectors); if (conflict != -1) throw new FaultException( _runtime._oprocess.constants.qnConflictingReceive, selectors[conflict].toString()); getORM().register(pickResponseChannelStr, selectors); _brc.select(pickResponseChannelStr, timeout, selectors); } public CorrelationKey readCorrelation(CorrelationSetInstance cset) { return _brc.readCorrelation(cset); } public Node fetchVariableData( VariableInstance variable, ScopeFrame scopeFrame, boolean forWriting) throws FaultException { if (variable.declaration.extVar != null) { // Note, that when using external variables, the database will not contain the value of the // variable, instead we need to go the external variable subsystems. Element reference = (Element) _brc.fetchVariableData( scopeFrame.resolve(variable.declaration.extVar.related), false); try { Node ret = _brc.readExtVar(variable, reference); if (ret == null) { throw new FaultException( _runtime._oprocess.constants.qnUninitializedVariable, "The external variable \"" + variable.declaration.name + "\" has not been initialized."); } return ret; } catch (IncompleteKeyException ike) { // This indicates that the external variable needed to be written do, put has not been. __log.error( "External variable could not be read due to incomplete key; the following key " + "components were missing: " + ike.getMissing()); throw new FaultException( _runtime._oprocess.constants.qnUninitializedVariable, "The extenral variable \"" + variable.declaration.name + "\" has not been properly initialized;" + "the following key compoenents were missing:" + ike.getMissing()); } catch (ExternalVariableModuleException e) { throw new BpelEngineException(e); } } else /* not external */ { Node data = _brc.fetchVariableData(variable, forWriting); if (data == null) { // Special case of messageType variables with no part if (variable.declaration.type instanceof OMessageVarType) { OMessageVarType msgType = (OMessageVarType) variable.declaration.type; if (msgType.parts.size() == 0) { Document doc = DOMUtils.newDocument(); Element root = doc.createElement("message"); doc.appendChild(root); return root; } } throw new FaultException( _runtime._oprocess.constants.qnUninitializedVariable, "The variable " + variable.declaration.name + " isn't properly initialized."); } return data; } } public Node fetchVariableData( VariableInstance var, ScopeFrame scopeFrame, OMessageVarType.Part part, boolean forWriting) throws FaultException { Node val = fetchVariableData(var, scopeFrame, forWriting); if (part != null) return getPartData((Element) val, part); return val; } public void writeCorrelation(CorrelationSetInstance cset, CorrelationKey ckeyVal) throws FaultException { OScope.CorrelationSet csetdef = cset.declaration; QName[] propNames = new QName[csetdef.properties.size()]; for (int m = 0; m < csetdef.properties.size(); m++) { OProcess.OProperty oProperty = csetdef.properties.get(m); propNames[m] = oProperty.name; } ckeyVal.setUnique(cset.declaration.isUnique()); _brc.writeCorrelation(cset, propNames, ckeyVal); } /** * Proxy to {@link OdeRTInstanceContext#sendEvent(org.apache.ode.bpel.evt.ProcessInstanceEvent)}. * * @param event */ public void sendEvent(ScopeEvent event) { _brc.sendEvent(event); } public void unregisterActivityForRecovery(ActivityRecoveryChannel recoveryChannel) { _brc.unregisterActivityForRecovery(recoveryChannel.export()); } /** * Proxy to {@link RecoveryContext#registerActivityForRecovery(String, long, String, Date, * Element, String[], int)}. */ public void registerActivityForRecovery( ActivityRecoveryChannel recoveryChannel, long id, String reason, Date dateTime, Element details, String[] actions, int retryCount) { _brc.registerActivityForRecovery( recoveryChannel.export(), id, reason, dateTime, details, actions, retryCount); } /** Proxy to {@link IOContext#registerTimer(String, Date)} . */ public void registerTimer(TimerResponseChannel timerChannel, Date future) { _brc.registerTimer(timerChannel.export(), future); } /** Proxy to {@link VariableContext#readVariableProperty(Variable, QName)}. */ public String readProperty(VariableInstance variable, OProcess.OProperty property) throws FaultException { try { return _brc.readVariableProperty(variable, property.name); } catch (UninitializedVariableException e) { throw new FaultException(_runtime._oprocess.constants.qnUninitializedVariable); } } /** Proxy to {@link OdeRTInstanceContext#genId() }. */ public long genId() { return _brc.genId(); } /** * Proxy to {@link OdeRTInstanceContext#initializeVariable(Variable, Node)} then write properties. */ public Node initializeVariable(VariableInstance var, ScopeFrame scopeFrame, Node val) throws ExternalVariableModuleException { try { if (var.declaration.extVar != null) /* external variable */ { if (__log.isDebugEnabled()) __log.debug( "Initialize external variable: name=" + var.declaration + " value=" + DOMUtils.domToString(val)); Node reference = null; try { reference = fetchVariableData(var, scopeFrame, true); } catch (FaultException fe) { // In this context this is not necessarily a problem, since the assignment may re-init the // related var } if (reference != null) val = _brc.readExtVar(var, reference); return val; } else /* normal variable */ { if (__log.isDebugEnabled()) __log.debug( "Initialize variable: name=" + var.declaration + " value=" + DOMUtils.domToString(val)); return _brc.initializeVariable(var, val); } } finally { writeProperties(var, val); } } /** Proxy to {@link VariableContext#fetchMyRoleEndpointReferenceData(PartnerLink)}. */ public Node fetchMyRoleEndpointReferenceData(PartnerLinkInstance link) { return _brc.fetchMyRoleEndpointReferenceData(link); } public Node fetchPartnerRoleEndpointReferenceData(PartnerLinkInstance link) throws FaultException { Element epr = _brc.fetchPartnerRoleEndpointReferenceData(link); if (epr == null) { throw new FaultException(_runtime._oprocess.constants.qnUninitializedPartnerRole); } return epr; } /** Proxy to {@link OdeRTInstanceContext#convertEndpointReference(Element, Node) }. */ public Node convertEndpointReference(Element epr, Node lvaluePtr) { return _brc.convertEndpointReference(epr, lvaluePtr); } public void commitChanges(VariableInstance var, ScopeFrame scopeFrame, Node value) throws ExternalVariableModuleException { if (var.declaration.extVar != null) /* external variable */ { if (__log.isDebugEnabled()) __log.debug( "Write external variable: name=" + var.declaration + " value=" + DOMUtils.domToString(value)); VariableInstance related = scopeFrame.resolve(var.declaration.extVar.related); Node reference = null; try { reference = fetchVariableData(var, scopeFrame, true); } catch (FaultException fe) { // In this context this is not necessarily a problem, since the assignment may re-init the // related var } VariableContext.ValueReferencePair vrp = _brc.writeExtVar(var, reference, value); commitChanges(related, scopeFrame, vrp.reference); } else /* normal variable */ { if (__log.isDebugEnabled()) __log.debug( "Write variable: name=" + var.declaration + " value=" + DOMUtils.domToString(value)); _brc.commitChanges(var, value); } writeProperties(var, value); } /** Proxy to {@link BpelRuntimeContext# }. */ public void writeEndpointReference(PartnerLinkInstance plval, Element element) { _brc.writeEndpointReference(plval, element); } /** Proxy to {@link OdeRTInstanceContext#createScopeInstance(Long, String, int)}. */ public Long createScopeInstance(Long scopeInstanceId, OScope scopedef) { return _brc.createScopeInstance(scopeInstanceId, scopedef.name, scopedef.getId()); } /** Proxy to {@link BpelRuntimeContext# }. */ public String fetchMySessionId(PartnerLinkInstance linkInstance) { return _brc.fetchMySessionId(linkInstance); } /** Proxy to {@link BpelRuntimeContext# }. */ public void cancel(PickResponseChannel responseChannel) { final String id = responseChannel.export(); _brc.cancelSelect(id); getORM().cancel(id); _vpu.inject( new JacobRunnable() { private static final long serialVersionUID = 6157913683737696396L; public void run() { TimerResponseChannel responseChannel = importChannel(id, TimerResponseChannel.class); responseChannel.onCancel(); } }); } /** Proxy to {@link BpelRuntimeContext# }. */ public Element getMyRequest(String mexId) { return _brc.getMyRequest(mexId); } /** Proxy to {@link BpelRuntimeContext# }. */ public void initializePartnersSessionId(PartnerLinkInstance instance, String partnersSessionId) { _brc.initializePartnersSessionId(instance, partnersSessionId); } /** Proxy to {@link IOContext#getSourceSessionId(String) }. */ public String getSourceSessionId(String mexId) { return _brc.getSourceSessionId(mexId); } public Node getSourceEPR(String mexId) { return _brc.getSourceEPR(mexId); } public ExtensionOperation createExtensionActivityImplementation(QName name) { if (name == null) return null; ExtensionBundleRuntime bundle = _runtime._extensionRegistry.get(name.getNamespaceURI()); if (bundle == null) { return null; } else { try { return bundle.getExtensionOperationInstance(name.getLocalPart()); } catch (Exception e) { return null; } } } /** Proxy to {@link ProcessControlContext# }. */ public Long getPid() { return _brc.getPid(); } /** Proxy to {@link IOContext#getPartnerResponse(String)}. */ public Element getPartnerResponse(String mexId) { return _brc.getPartnerResponse(mexId); } /** Proxy to {@link IOContext#releasePartnerMex(String) }. */ public void releasePartnerMex(String mexId) { _brc.releasePartnerMex(mexId); } /** Proxy to {@link IOContext#getPartnerFault(String) }. */ public QName getPartnerFault(String mexId) { return _brc.getPartnerFault(mexId); } /** Proxy to {@link IOContext#getPartnerResponseType(String) }. */ public QName getPartnerResponseType(String mexId) { return _brc.getPartnerResponseType(mexId); } /** Proxy to {@link IOContext#getPartnerFaultExplanation(String) }. */ public String getPartnerFaultExplanation(String mexId) { return _brc.getPartnerFaultExplanation(mexId); } /** * Proxy to {@link OdeRTInstanceContext#sendEvent(org.apache.ode.bpel.evt.ProcessInstanceEvent) }. */ public void sendEvent(ProcessInstanceStartedEvent evt) { _brc.sendEvent(evt); } /** Proxy to {@link IOContext#reply(PartnerLink, String, String, Element, QName) }. */ public void reply( PartnerLinkInstance plink, String opName, String bpelmex, Element element, QName fault) throws FaultException { String mexid = getORM().release(plink, opName, bpelmex); if (mexid == null) throw new FaultException(_runtime._oprocess.constants.qnMissingRequest); try { _brc.reply(mexid, plink, opName, element, fault); } catch (NoSuchOperationException e) { // reply to operation that is either not defined or one-way. Perhaps this should be detected // at compile time? throw new FaultException( _runtime._oprocess.constants.qnMissingRequest, "Undefined two-way operation \"" + opName + "\"."); } } /** Proxy to {@link ProcessControlContext#forceFlush() }. */ public void forceFlush() { _brc.forceFlush(); } /** Proxy to {@link ProcessControlContext#forceRollback() }. */ public void forceRollback() { _brc.forceRollback(); } /** Proxy to {@link ProcessControlContext#terminate()}. */ public void terminate() { cleanupOutstandingMyRoleExchanges(null); _brc.terminate(); } /** Record all values of properties of a 'MessageType' variable for efficient lookup. */ private void writeProperties(VariableInstance variable, Node value) { if (variable.declaration.type instanceof OMessageVarType) { for (OProcess.OProperty property : variable.declaration.getOwner().properties) { OProcess.OPropertyAlias alias = property.getAlias(variable.declaration.type); if (alias != null) { try { String val = extractProperty((Element) value, alias, variable.declaration.getDescription()); if (val != null) _brc.writeVariableProperty(variable, property.name, val); } catch (UninitializedVariableException uve) { // This really should not happen, since we are writing to a variable that we just // modified. __log.fatal( "Couldn't extract property '" + property.toString() + "' in property pre-extraction: " + uve); throw new RuntimeException(uve); } catch (FaultException e) { // This will fail as we're basically trying to extract properties on all received // messages // for optimization purposes. if (__log.isDebugEnabled()) __log.debug( "Couldn't extract property '" + property.toString() + "' in property pre-extraction: " + e.toString()); } } } } } /** * Extract the value of a BPEL property from a BPEL messsage variable. * * @param msgData message variable data * @param alias alias to apply * @param target description of the data (for error logging only) * @return value of the property * @throws FaultException */ String extractProperty(Element msgData, OProcess.OPropertyAlias alias, String target) throws FaultException { PropertyAliasEvaluationContext ectx = new PropertyAliasEvaluationContext(msgData, alias); Node lValue = ectx.getRootNode(); if (alias.location != null) lValue = _runtime._expLangRuntimeRegistry.evaluateNode(alias.location, ectx); if (lValue == null) { String errmsg = __msgs.msgPropertyAliasReturnedNullSet(alias.getDescription(), target); if (__log.isErrorEnabled()) { __log.error(errmsg); } throw new FaultException(_runtime._oprocess.constants.qnSelectionFailure, errmsg); } if (lValue.getNodeType() == Node.ELEMENT_NODE) { // This is a bit hokey, we concatenate all the children's values; we really should be checking // to make sure that we are only dealing with text and attribute nodes. StringBuffer val = new StringBuffer(); NodeList nl = lValue.getChildNodes(); for (int i = 0; i < nl.getLength(); ++i) { Node n = nl.item(i); val.append(n.getNodeValue()); } return val.toString(); } else if (lValue.getNodeType() == Node.TEXT_NODE) { return ((Text) lValue).getWholeText(); } else return null; } public Node getPartData(Element message, OMessageVarType.Part part) { // borrowed from ASSIGN.evalQuery() Node ret = DOMUtils.findChildByName(message, new QName(null, part.name)); if (part.type instanceof OElementVarType) { QName elName = ((OElementVarType) part.type).elementType; ret = DOMUtils.findChildByName((Element) ret, elName); } else if (part.type == null) { // Special case of header parts never referenced in the WSDL def if (ret != null && ret.getNodeType() == Node.ELEMENT_NODE && ((Element) ret).getAttribute("headerPart") != null && DOMUtils.getTextContent(ret) == null) ret = DOMUtils.getFirstChildElement((Element) ret); // The needed part isn't there, dynamically creating it if (ret == null) { ret = message.getOwnerDocument().createElementNS(null, part.name); ((Element) ret).setAttribute("headerPart", "true"); message.appendChild(ret); } } return ret; } /** * @param instance * @param operation * @param outboundMsg * @param object */ public String invoke( String invokeId, PartnerLinkInstance instance, Operation operation, Element outboundMsg, Object object) throws FaultException { try { return _brc.invoke(invokeId, instance, operation, outboundMsg); } catch (UninitializedPartnerEPR e) { throw new FaultException(_runtime._oprocess.constants.qnUninitializedPartnerRole); } } /** @return */ public ExpressionLanguageRuntimeRegistry getExpLangRuntime() { return _runtime._expLangRuntimeRegistry; } /* * (non-Javadoc) * * @see org.apache.ode.bpel.engine.rapi.OdeInternalInstance#onMyRoleMessageExchange(java.lang.String, java.lang.String) */ public void onSelectEvent( final String selectId, final String messageExchangeId, final int selectorIdx) { getORM().associate(selectId, messageExchangeId); _vpu.inject( new JacobRunnable() { private static final long serialVersionUID = 3168964409165899533L; public void run() { // NOTE: we chose the selectId to be the exported representation of the pick response // channel! PickResponseChannel responseChannel = importChannel(selectId, PickResponseChannel.class); responseChannel.onRequestRcvd(selectorIdx, messageExchangeId); } }); } /* * (non-Javadoc) * * @see org.apache.ode.bpel.engine.rapi.OdeInternalInstance#onTimerEvent(java.lang.String) */ public void onTimerEvent(final String timerId) { getORM().cancel(timerId); _vpu.inject( new JacobRunnable() { private static final long serialVersionUID = -7767141033611036745L; public void run() { // NOTE: note short cut, we chose timer id to be the same as the exported channel // representation. TimerResponseChannel responseChannel = importChannel(timerId, TimerResponseChannel.class); responseChannel.onTimeout(); } }); } /* * (non-Javadoc) * * @see org.apache.ode.bpel.engine.rapi.OdeInternalInstance#execute() */ public boolean execute() { return _vpu.execute(); } /* * (non-Javadoc) * * @see org.apache.ode.bpel.engine.rapi.OdeInternalInstance#onInvokeResponse(java.lang.String, java.lang.String) */ public void onInvokeResponse(final String invokeId, InvokeResponseType irt, final String mexid) { // NOTE: do the switch outside the inject, since we don't want to end up serializing // InvokeResponseType objects! switch (irt) { case REPLY: _vpu.inject( new BpelJacobRunnable() { private static final long serialVersionUID = -1095444335740879981L; public void run() { importChannel(invokeId, InvokeResponseChannel.class).onResponse(); } }); break; case FAULT: _vpu.inject( new BpelJacobRunnable() { private static final long serialVersionUID = -1095444335740879981L; public void run() { importChannel(invokeId, InvokeResponseChannel.class).onFault(); } }); break; case FAILURE: _vpu.inject( new BpelJacobRunnable() { private static final long serialVersionUID = -1095444335740879981L; public void run() { importChannel(invokeId, InvokeResponseChannel.class).onFailure(); } }); break; } } public void recoverActivity( final String channel, final long activityId, final String action, FaultInfo fault) { // TODO: better translation here? final FaultData fdata = (fault != null) ? new FaultData(fault.getFaultName(), null, fault.getExplanation()) : null; _vpu.inject( new JacobRunnable() { private static final long serialVersionUID = 3168964409165899533L; public void run() { ActivityRecoveryChannel recovery = importChannel(channel, ActivityRecoveryChannel.class); __log.info( "ActivityRecovery: Recovering activity " + activityId + " with action " + action + " on channel " + recovery); if (recovery != null) { if ("cancel".equals(action)) recovery.cancel(); else if ("retry".equals(action)) recovery.retry(); else if ("fault".equals(action)) recovery.fault(fdata); } } }); } private OutstandingRequestManager getORM() { return (OutstandingRequestManager) _soup.getGlobalData(); } /** Called when the process completes to clean up any outstanding message exchanges. */ private void cleanupOutstandingMyRoleExchanges(FaultInfo optionalFaultData) { // TODO: all this should be moved into the engine. We don't really need the ORM to find // these mexs, we can just scan the database String[] mexRefs = getORM().releaseAll(); for (String mexId : mexRefs) { _brc.noreply(mexId, optionalFaultData); } } /* (non-Javadoc) * @see org.apache.ode.bpel.engine.rapi.OdeInternalInstance#saveState() */ public Object saveState(OutputStream bos) throws IOException { if (bos != null) _soup.write(bos); return _soup; } /* (non-Javadoc) * @see org.apache.ode.bpel.engine.rapi.OdeInternalInstance#createInstance(java.lang.String) */ public void onCreateInstance(String messageExchangeId) { _vpu.inject(new PROCESS(_runtime._oprocess)); } /* (non-Javadoc) * @see org.apache.ode.bpel.engine.rapi.OdeInternalInstance#setContext(org.apache.ode.bpel.engine.rapi.OdeRTInstanceContext) */ public void setContext(OdeRTInstanceContext ctx) { _brc = ctx; } public URI getBaseResourceURI() { return _runtime._pconf.getBaseURI(); } public int getRetryDelay() { return _brc.getAtomicScopeRetryDelay(); } public boolean isFirstTry() { return _brc.isAtomicScopeFirstTry(); } public boolean isRetryable() { return _brc.isAtomicScopeRetryable(); } public void setRetriedOnce() { _brc.setAtomicScopeRetriedOnce(); } public void setRetriesDone() { _brc.setAtomicScopeRetriesDone(); } public void setAtomicScope(boolean atomicScope) { _brc.setAtomicScope(atomicScope); } public Node getProcessProperty(QName propertyName) { return _brc.getProcessProperty(propertyName); } }
/** * JDBC-based implementation of a process store. Also provides an "in-memory" store by way of H2 * database. * * <p>The philsophy here is to keep things simple. Process store operations are relatively * infrequent. Performance of the public methods is not a concern. However, note that the {@link * org.apache.ode.bpel.iapi.ProcessConf} objects returned by the class are going to be used from * within the engine runtime, and hence their performance needs to be very good. Similarly, these * objects should be immutable so as not to confuse the engine. * * <p>Note the way that the database is used in this class, it is more akin to a recovery log, this * is intentional: we want to start up, load stuff from the database and then pretty much forget * about it when it comes to reads. * * @author Maciej Szefler <mszefler at gmail dot com> * @author mriou <mriou at apache dot org> */ public class ProcessStoreImpl implements ProcessStore { private static final Log __log = LogFactory.getLog(ProcessStoreImpl.class); private static final Messages __msgs = MessageBundle.getMessages(Messages.class); private final CopyOnWriteArrayList<ProcessStoreListener> _listeners = new CopyOnWriteArrayList<ProcessStoreListener>(); private Map<QName, ProcessConfImpl> _processes = new HashMap<QName, ProcessConfImpl>(); private Map<String, DeploymentUnitDir> _deploymentUnits = new HashMap<String, DeploymentUnitDir>(); /** Guards access to the _processes and _deploymentUnits */ private final ReadWriteLock _rw = new ReentrantReadWriteLock(); private ConfStoreConnectionFactory _cf; private EndpointReferenceContext eprContext; private boolean generateProcessEventsAll; protected File _deployDir; protected File _configDir; /** * Executor used to process DB transactions. Allows us to isolate the TX context, and to ensure * that only one TX gets executed a time. We don't really care to parallelize these operations * because: i) HSQL does not isolate transactions and we don't want to get confused ii) we're * already serializing all the operations with a read/write lock. iii) we don't care about * performance, these are infrequent operations. */ private ExecutorService _executor = Executors.newSingleThreadExecutor(new SimpleThreadFactory()); /** * In-memory DataSource, or <code>null</code> if we are using a real DS. We need this to shutdown * the DB. */ private DataSource _inMemDs; private static final ThreadLocal<Long> _currentVersion = new ThreadLocal<Long>(); public ProcessStoreImpl() { this(null, null, "", new OdeConfigProperties(new Properties(), ""), true); } public ProcessStoreImpl( EndpointReferenceContext eprContext, DataSource ds, String persistenceType, OdeConfigProperties props, boolean createDatamodel) { this.eprContext = eprContext; this.generateProcessEventsAll = props.getProperty("generateProcessEvents", "all").equals("all"); if (ds != null) { // ugly hack if (persistenceType.toLowerCase().indexOf("hib") != -1) { _cf = new org.apache.ode.store.hib.DbConfStoreConnectionFactory( ds, props.getProperties(), createDatamodel, props.getTxFactoryClass()); } else { _cf = new org.apache.ode.store.jpa.DbConfStoreConnectionFactory( ds, props.getProperties(), createDatamodel, props.getTxFactoryClass()); } } else { // If the datasource is not provided, then we create a H2-based // in-memory database. Makes testing a bit simpler. DataSource h2 = createInternalDS(new GUID().toString()); if ("hibernate".equalsIgnoreCase(persistenceType)) { _cf = new org.apache.ode.store.hib.DbConfStoreConnectionFactory( h2, props.getProperties(), createDatamodel, props.getTxFactoryClass()); } else { _cf = new org.apache.ode.store.jpa.DbConfStoreConnectionFactory( h2, props.getProperties(), createDatamodel, props.getTxFactoryClass()); } _inMemDs = h2; } } /** Constructor that hardwires OpenJPA on a new in-memory database. Suitable for tests. */ public ProcessStoreImpl(EndpointReferenceContext eprContext, DataSource inMemDs) { this.eprContext = eprContext; DataSource h2 = createInternalDS(new GUID().toString()); // when in memory we always create the model as we are starting from scratch _cf = new org.apache.ode.store.jpa.DbConfStoreConnectionFactory( h2, true, OdeConfigProperties.DEFAULT_TX_FACTORY_CLASS_NAME); _inMemDs = h2; } public void shutdown() { if (_inMemDs != null) { shutdownInternalDB(_inMemDs); _inMemDs = null; } if (_executor != null) { _executor.shutdownNow(); _executor = null; } } @Override protected void finalize() throws Throwable { // force a shutdown so that HSQL cleans up its mess. try { shutdown(); } catch (Throwable t) {; // we tried, no worries. } super.finalize(); } /** Deploys a process. */ public Collection<QName> deploy( final File deploymentUnitDirectory, boolean autoincrementVersion) { return deploy(deploymentUnitDirectory, true, null, autoincrementVersion); } public Collection<QName> deploy(final File deploymentUnitDirectory) { return deploy(deploymentUnitDirectory, true, null, OdeGlobalConfig.autoincrementVersion()); } /** Deploys a process. */ public Collection<QName> deploy( final File deploymentUnitDirectory, boolean activate, String duName, boolean autoincrementVersion) { __log.info(__msgs.msgDeployStarting(deploymentUnitDirectory)); final Date deployDate = new Date(); // Create the DU and compile/scan it before acquiring lock. final DeploymentUnitDir du = new DeploymentUnitDir(deploymentUnitDirectory); if (duName != null) { // Override the package name if given from the parameter du.setName(duName); } long version; if (autoincrementVersion || du.getStaticVersion() == -1) { // Process and DU use a monotonically increased single version number by default. try { version = getCurrentVersion(); } finally { // we need to reset the current version thread local value. _currentVersion.set(null); } } else { version = du.getStaticVersion(); } du.setVersion(version); try { du.compile(); } catch (CompilationException ce) { String errmsg = __msgs.msgDeployFailCompileErrors(ce); __log.error(errmsg, ce); throw new ContextException(errmsg, ce); } du.scan(); final DeployDocument dd = du.getDeploymentDescriptor(); final ArrayList<ProcessConfImpl> processes = new ArrayList<ProcessConfImpl>(); Collection<QName> deployed; _rw.writeLock().lock(); try { if (_deploymentUnits.containsKey(du.getName())) { String errmsg = __msgs.msgDeployFailDuplicateDU(du.getName()); __log.error(errmsg); throw new ContextException(errmsg); } retirePreviousPackageVersions(du); for (TDeployment.Process processDD : dd.getDeploy().getProcessArray()) { QName pid = toPid(processDD.getName(), version); if (_processes.containsKey(pid)) { String errmsg = __msgs.msgDeployFailDuplicatePID(processDD.getName(), du.getName()); __log.error(errmsg); throw new ContextException(errmsg); } QName type = processDD.getType() != null ? processDD.getType() : processDD.getName(); CBPInfo cbpInfo = du.getCBPInfo(type); if (cbpInfo == null) { String errmsg = __msgs.msgDeployFailedProcessNotFound(processDD.getName(), du.getName()); __log.error(errmsg); throw new ContextException(errmsg); } ProcessConfImpl pconf = new ProcessConfImpl( pid, processDD.getName(), version, du, processDD, deployDate, calcInitialProperties(du.getProperties(), processDD), calcInitialState(processDD), eprContext, _configDir, generateProcessEventsAll); processes.add(pconf); } _deploymentUnits.put(du.getName(), du); for (ProcessConfImpl process : processes) { __log.info(__msgs.msgProcessDeployed(du.getDeployDir(), process.getProcessId())); _processes.put(process.getProcessId(), process); } } finally { _rw.writeLock().unlock(); } // Do the deployment in the DB. We need this so that we remember deployments across system // shutdowns. // We don't fail if there is a DB error, simply print some errors. deployed = exec( new Callable<Collection<QName>>() { public Collection<QName> call(ConfStoreConnection conn) { // Check that this deployment unit is not deployed. DeploymentUnitDAO dudao = conn.getDeploymentUnit(du.getName()); if (dudao != null) { String errmsg = "Database out of synch for DU " + du.getName(); __log.warn(errmsg); dudao.delete(); } dudao = conn.createDeploymentUnit(du.getName()); try { dudao.setDeploymentUnitDir(deploymentUnitDirectory.getCanonicalPath()); } catch (IOException e1) { String errmsg = "Error getting canonical path for " + du.getName() + "; deployment unit will not be available after restart!"; __log.error(errmsg); } ArrayList<QName> deployed = new ArrayList<QName>(); // Going trough each process declared in the dd for (ProcessConfImpl pc : processes) { try { ProcessConfDAO newDao = dudao.createProcess(pc.getProcessId(), pc.getType(), pc.getVersion()); newDao.setState(pc.getState()); for (Map.Entry<QName, Node> prop : pc.getProcessProperties().entrySet()) { newDao.setProperty(prop.getKey(), DOMUtils.domToString(prop.getValue())); } deployed.add(pc.getProcessId()); } catch (Throwable e) { String errmsg = "Error persisting deployment record for " + pc.getProcessId() + "; process will not be available after restart!"; __log.error(errmsg, e); } } return deployed; } }); _rw.readLock().lock(); boolean readLockHeld = true; try { for (ProcessConfImpl process : processes) { fireEvent( new ProcessStoreEvent( ProcessStoreEvent.Type.DEPLOYED, process.getProcessId(), process.getDeploymentUnit().getName())); fireStateChange( process.getProcessId(), process.getState(), process.getDeploymentUnit().getName()); } } catch (Exception e) { // need to unlock as undeploy operation will need a writeLock _rw.readLock().unlock(); readLockHeld = false; // A problem at that point means that engine deployment failed, we don't want the store to // keep the du __log.warn("Deployment failed within the engine, store undeploying process.", e); undeploy(deploymentUnitDirectory); if (e instanceof ContextException) throw (ContextException) e; else throw new ContextException("Deployment failed within the engine. " + e.getMessage(), e); } finally { if (readLockHeld) _rw.readLock().unlock(); } return deployed; } /** * Retire all the other versions of the same DU: first take the DU name and insert version regexp, * than try to match the this string against names of already deployed DUs. For instance if we are * deploying DU "AbsenceRequest-2/AbsenceRequest.ode" and there's already version 2 than regexp * "AbsenceRequest([-\\.](\d)+)?/AbsenceRequest.ode" will be matched against * "AbsenceRequest-2/AbsenceRequest.ode" and setRetirePackage() will be called accordingly. */ private void retirePreviousPackageVersions(DeploymentUnitDir du) { // retire all the other versions of the same DU String[] nameParts = du.getName().split("/"); /* Replace the version number (if any) with regexp to match any version number */ nameParts[0] = nameParts[0].replaceAll("([-\\Q.\\E](\\d)+)?\\z", ""); nameParts[0] += "([-\\Q.\\E](\\d)+)?"; StringBuilder duNameRegExp = new StringBuilder(du.getName().length() * 2); for (int i = 0, n = nameParts.length; i < n; i++) { if (i > 0) duNameRegExp.append("/"); duNameRegExp.append(nameParts[i]); } Pattern duNamePattern = Pattern.compile(duNameRegExp.toString()); for (String deployedDUname : _deploymentUnits.keySet()) { Matcher matcher = duNamePattern.matcher(deployedDUname); if (matcher.matches()) { setRetiredPackage(deployedDUname, true); } } } public Collection<QName> undeploy(final File dir) { return undeploy(dir.getName()); } public Collection<QName> undeploy(final String duName) { try { exec( new Callable<Collection<QName>>() { public Collection<QName> call(ConfStoreConnection conn) { DeploymentUnitDAO dudao = conn.getDeploymentUnit(duName); if (dudao != null) dudao.delete(); return null; } }); } catch (Exception ex) { __log.error( "Error synchronizing with data store; " + duName + " may be reappear after restart!"); } Collection<QName> undeployed = Collections.emptyList(); DeploymentUnitDir du; _rw.writeLock().lock(); try { du = _deploymentUnits.remove(duName); if (du != null) { undeployed = toPids(du.getProcessNames(), du.getVersion()); } for (QName pn : undeployed) { fireEvent(new ProcessStoreEvent(ProcessStoreEvent.Type.UNDEPLOYED, pn, du.getName())); __log.info(__msgs.msgProcessUndeployed(pn)); } _processes.keySet().removeAll(undeployed); } finally { _rw.writeLock().unlock(); } return undeployed; } public Collection<String> getPackages() { _rw.readLock().lock(); try { return new ArrayList<String>(_deploymentUnits.keySet()); } finally { _rw.readLock().unlock(); } } public List<QName> listProcesses(String packageName) { _rw.readLock().lock(); try { DeploymentUnitDir du = _deploymentUnits.get(packageName); if (du == null) return null; return toPids(du.getProcessNames(), du.getVersion()); } finally { _rw.readLock().unlock(); } } public void setState(final QName pid, final ProcessState state) { __log.debug("Changing process state for " + pid + " to " + state); final ProcessConfImpl pconf; _rw.readLock().lock(); try { pconf = _processes.get(pid); if (pconf == null) { String msg = __msgs.msgProcessNotFound(pid); __log.info(msg); throw new ContextException(msg); } } finally { _rw.readLock().unlock(); } final DeploymentUnitDir dudir = pconf.getDeploymentUnit(); // Update in the database. ProcessState old = exec( new Callable<ProcessState>() { public ProcessState call(ConfStoreConnection conn) { DeploymentUnitDAO dudao = conn.getDeploymentUnit(dudir.getName()); if (dudao == null) { String errmsg = __msgs.msgProcessNotFound(pid); __log.error(errmsg); throw new ContextException(errmsg); } ProcessConfDAO dao = dudao.getProcess(pid); if (dao == null) { String errmsg = __msgs.msgProcessNotFound(pid); __log.error(errmsg); throw new ContextException(errmsg); } Set processKeys = _processes.keySet(); Iterator processConfQNameItr = processKeys.iterator(); while (processConfQNameItr.hasNext()) { ProcessConf cachedProcessConf = _processes.get(processConfQNameItr.next()); if (dao.getType().equals(cachedProcessConf.getType())) { if (ProcessState.ACTIVE == cachedProcessConf.getState() && ProcessState.RETIRED == dao.getState() && ProcessState.ACTIVE == state) { String errorMsg = "Can't activate the process with PID: " + dao.getPID() + " with version " + dao.getVersion() + ", as another version of the process with PID : " + cachedProcessConf.getProcessId() + " with version " + cachedProcessConf.getVersion() + " is already active."; __log.error(errorMsg); throw new ContextException(errorMsg); } } } ProcessState old = dao.getState(); dao.setState(state); pconf.setState(state); return old; } }); pconf.setState(state); if (old != null && old != state) fireStateChange(pid, state, pconf.getDeploymentUnit().getName()); } public void setRetiredPackage(String packageName, boolean retired) { DeploymentUnitDir duDir = _deploymentUnits.get(packageName); if (duDir == null) throw new ContextException("Could not find package " + packageName); for (QName processName : duDir.getProcessNames()) { setState( toPid(processName, duDir.getVersion()), retired ? ProcessState.RETIRED : ProcessState.ACTIVE); } } public ProcessConf getProcessConfiguration(final QName processId) { _rw.readLock().lock(); try { return _processes.get(processId); } finally { _rw.readLock().unlock(); } } public void setProperty(final QName pid, final QName propName, final Node value) { setProperty(pid, propName, DOMUtils.domToStringLevel2(value)); } public void setProperty(final QName pid, final QName propName, final String value) { if (__log.isDebugEnabled()) __log.debug("Setting property " + propName + " on process " + pid); ProcessConfImpl pconf = _processes.get(pid); if (pconf == null) { String msg = __msgs.msgProcessNotFound(pid); __log.info(msg); throw new ContextException(msg); } final DeploymentUnitDir dudir = pconf.getDeploymentUnit(); exec( new ProcessStoreImpl.Callable<Object>() { public Object call(ConfStoreConnection conn) { DeploymentUnitDAO dudao = conn.getDeploymentUnit(dudir.getName()); if (dudao == null) return null; ProcessConfDAO proc = dudao.getProcess(pid); if (proc == null) return null; proc.setProperty(propName, value); return null; } }); fireEvent(new ProcessStoreEvent(ProcessStoreEvent.Type.PROPERTY_CHANGED, pid, dudir.getName())); } /** Load all the deployment units out of the store. Called on start-up. */ public void loadAll() { final ArrayList<ProcessConfImpl> loaded = new ArrayList<ProcessConfImpl>(); exec( new Callable<Object>() { public Object call(ConfStoreConnection conn) { Collection<DeploymentUnitDAO> dus = conn.getDeploymentUnits(); for (DeploymentUnitDAO du : dus) try { loaded.addAll(load(du)); } catch (Exception ex) { __log.error("Error loading DU from store: " + du.getName(), ex); } return null; } }); // Dispatch DISABLED, RETIRED and ACTIVE events in that order Collections.sort( loaded, new Comparator<ProcessConf>() { public int compare(ProcessConf o1, ProcessConf o2) { return stateValue(o1.getState()) - stateValue(o2.getState()); } int stateValue(ProcessState state) { if (ProcessState.DISABLED.equals(state)) return 0; if (ProcessState.RETIRED.equals(state)) return 1; if (ProcessState.ACTIVE.equals(state)) return 2; throw new IllegalStateException("Unexpected process state: " + state); } }); for (ProcessConfImpl p : loaded) { try { fireStateChange(p.getProcessId(), p.getState(), p.getDeploymentUnit().getName()); } catch (Exception except) { __log.error( "Error while activating process: pid=" + p.getProcessId() + " package=" + p.getDeploymentUnit().getName(), except); } } } public List<QName> getProcesses() { _rw.readLock().lock(); try { return new ArrayList<QName>(_processes.keySet()); } finally { _rw.readLock().unlock(); } } public long getCurrentVersion() { if (_currentVersion.get() != null) { return _currentVersion.get(); } long version = exec( new Callable<Long>() { public Long call(ConfStoreConnection conn) { return conn.getNextVersion(); } }); _currentVersion.set(version); return _currentVersion.get(); } protected void fireEvent(ProcessStoreEvent pse) { __log.debug("firing event: " + pse); for (ProcessStoreListener psl : _listeners) psl.onProcessStoreEvent(pse); } private void fireStateChange(QName processId, ProcessState state, String duname) { switch (state) { case ACTIVE: fireEvent(new ProcessStoreEvent(ProcessStoreEvent.Type.ACTVIATED, processId, duname)); break; case DISABLED: fireEvent(new ProcessStoreEvent(ProcessStoreEvent.Type.DISABLED, processId, duname)); break; case RETIRED: fireEvent(new ProcessStoreEvent(ProcessStoreEvent.Type.RETIRED, processId, duname)); break; } } public void registerListener(ProcessStoreListener psl) { __log.debug("Registering listener " + psl); _listeners.add(psl); } public void unregisterListener(ProcessStoreListener psl) { __log.debug("Unregistering listener " + psl); _listeners.remove(psl); } /** * Execute database transactions in an isolated context. * * @param <T> return type * @param callable transaction * @return */ synchronized <T> T exec(Callable<T> callable) { // We want to submit db jobs to an executor to isolate // them from the current thread, Future<T> future = _executor.submit(callable); try { return future.get(); } catch (Exception e) { throw new ContextException("DbError", e); } } private ConfStoreConnection getConnection() { return _cf.getConnection(); } /** * Create a property mapping based on the initial values in the deployment descriptor. * * @param dd * @return */ public static Map<QName, Node> calcInitialProperties( Properties properties, TDeployment.Process dd) { HashMap<QName, Node> ret = new HashMap<QName, Node>(); for (Object key1 : properties.keySet()) { String key = (String) key1; Document doc = DOMUtils.newDocument(); doc.appendChild(doc.createElementNS(null, "temporary-simple-type-wrapper")); doc.getDocumentElement().appendChild(doc.createTextNode(properties.getProperty(key))); ret.put(new QName(key), doc.getDocumentElement()); } for (TDeployment.Process.Property property : dd.getPropertyArray()) { Element elmtContent = DOMUtils.getElementContent(property.getDomNode()); if (elmtContent != null) { // We'll need DOM Level 3 Document doc = DOMUtils.newDocument(); doc.appendChild(doc.importNode(elmtContent, true)); ret.put(property.getName(), doc.getDocumentElement()); } else ret.put(property.getName(), property.getDomNode().getFirstChild()); } return ret; } /** * Figure out the initial process state from the state in the deployment descriptor. * * @param dd deployment descriptor * @return */ private static ProcessState calcInitialState(TDeployment.Process dd) { ProcessState state = ProcessState.ACTIVE; if (dd.isSetActive() && dd.getActive() == false) state = ProcessState.DISABLED; if (dd.isSetRetired() && dd.getRetired() == true) state = ProcessState.RETIRED; return state; } /** * Load a deployment unit record stored in the db into memory. * * @param dudao */ protected List<ProcessConfImpl> load(DeploymentUnitDAO dudao) { __log.debug("Loading deployment unit record from db: " + dudao.getName()); File dudir = findDeployDir(dudao); if (dudir == null || !dudir.exists()) throw new ContextException( "Deployed directory " + (dudir == null ? "(unknown)" : dudir) + " no longer there!"); DeploymentUnitDir dud = new DeploymentUnitDir(dudir); // set the name with the one from database dud.setName(dudao.getName()); dud.scan(); ArrayList<ProcessConfImpl> loaded = new ArrayList<ProcessConfImpl>(); _rw.writeLock().lock(); try { _deploymentUnits.put(dud.getName(), dud); long version = 0; for (ProcessConfDAO p : dudao.getProcesses()) { TDeployment.Process pinfo = dud.getProcessDeployInfo(p.getType()); if (pinfo == null) { __log.warn("Cannot load " + p.getPID() + "; cannot find descriptor."); continue; } Map<QName, Node> props = calcInitialProperties(dud.getProperties(), pinfo); // TODO: update the props based on the values in the DB. ProcessConfImpl pconf = new ProcessConfImpl( p.getPID(), p.getType(), p.getVersion(), dud, pinfo, dudao.getDeployDate(), props, p.getState(), eprContext, _configDir, generateProcessEventsAll); version = p.getVersion(); _processes.put(pconf.getProcessId(), pconf); loaded.add(pconf); } // All processes and the DU have the same version dud.setVersion(version); } finally { _rw.writeLock().unlock(); } return loaded; } protected File findDeployDir(DeploymentUnitDAO dudao) { File f = new File(dudao.getDeploymentUnitDir()); if (f.exists()) return f; f = new File(_deployDir, dudao.getName()); if (f.exists()) { try { dudao.setDeploymentUnitDir(f.getCanonicalPath()); } catch (IOException e) { __log.warn("Could not update deployment unit directory for " + dudao.getName(), e); } return f; } return null; } /** * Make sure that the deployment unit is loaded. * * @param duName deployment unit name */ protected boolean load(final String duName) { _rw.writeLock().lock(); try { if (_deploymentUnits.containsKey(duName)) return true; } finally { _rw.writeLock().unlock(); } try { return exec( new Callable<Boolean>() { public Boolean call(ConfStoreConnection conn) { DeploymentUnitDAO dudao = conn.getDeploymentUnit(duName); if (dudao == null) return false; load(dudao); return true; } }); } catch (Exception ex) { __log.error("Error loading deployment unit: " + duName); return false; } } /** * Wrapper for database transactions. * * @author Maciej Szefler * @param <V> return type */ abstract class Callable<V> implements java.util.concurrent.Callable<V> { public V call() { boolean success = false; // in JTA, transaction is bigger than the session _cf.beginTransaction(); ConfStoreConnection conn = getConnection(); try { V r = call(conn); _cf.commitTransaction(); success = true; return r; } finally { if (!success) try { _cf.rollbackTransaction(); } catch (Exception ex) { __log.error("DbError", ex); } } // session is closed automatically when committed or rolled back under JTA } abstract V call(ConfStoreConnection conn); } public void setDeployDir(File depDir) { if (depDir != null) { if (!depDir.exists()) { depDir.mkdirs(); __log.warn( "Deploy directory: " + depDir.getAbsolutePath() + " does not exist; created it."); } else if (!depDir.isDirectory()) { throw new IllegalArgumentException("Deploy directory is not a directory: " + depDir); } } _deployDir = depDir; } public File getDeployDir() { return _deployDir; } public File getConfigDir() { return _configDir; } public void setConfigDir(File configDir) { if (configDir != null && !configDir.isDirectory()) throw new IllegalArgumentException( "Config directory is not a directory or does not exist: " + configDir); this._configDir = configDir; } public static DataSource createInternalDS(String guid) { JdbcDataSource h2 = new JdbcDataSource(); h2.setURL("jdbc:h2:mem:" + new GUID().toString() + ";DB_CLOSE_DELAY=-1"); h2.setUser("sa"); return h2; } public static void shutdownInternalDB(DataSource ds) { try { ds.getConnection().createStatement().execute("SHUTDOWN;"); } catch (SQLException e) { __log.error("Error shutting down.", e); } } private List<QName> toPids(Collection<QName> processTypes, long version) { ArrayList<QName> result = new ArrayList<QName>(); for (QName pqName : processTypes) { result.add(toPid(pqName, version)); } return result; } private QName toPid(QName processType, long version) { return new QName(processType.getNamespaceURI(), processType.getLocalPart() + "-" + version); } private class SimpleThreadFactory implements ThreadFactory { int threadNumber = 0; public Thread newThread(Runnable r) { threadNumber += 1; Thread t = new Thread(r, "ProcessStoreImpl-" + threadNumber); t.setDaemon(true); return t; } } public void refreshSchedules(String packageName) { List<QName> pids = listProcesses(packageName); if (pids != null) { for (QName pid : pids) { fireEvent( new ProcessStoreEvent( ProcessStoreEvent.Type.SCHEDULE_SETTINGS_CHANGED, pid, packageName)); } } } }