/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.ode.bpel.engine; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.wsdl.Operation; import javax.wsdl.PortType; import javax.xml.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ode.agents.memory.SizingAgent; import org.apache.ode.bpel.common.FaultException; import org.apache.ode.bpel.common.ProcessState; import org.apache.ode.bpel.engine.extvar.ExternalVariableConf; import org.apache.ode.bpel.engine.extvar.ExternalVariableManager; import org.apache.ode.bpel.evt.ProcessInstanceEvent; import org.apache.ode.bpel.explang.ConfigurationException; import org.apache.ode.bpel.explang.EvaluationException; import org.apache.ode.bpel.iapi.BpelEngineException; import org.apache.ode.bpel.iapi.Endpoint; import org.apache.ode.bpel.iapi.EndpointReference; import org.apache.ode.bpel.iapi.MessageExchange; import org.apache.ode.bpel.iapi.PartnerRoleChannel; import org.apache.ode.bpel.iapi.ProcessConf; import org.apache.ode.bpel.iapi.Scheduler; import org.apache.ode.bpel.iapi.ProcessConf.CLEANUP_CATEGORY; import org.apache.ode.bpel.iapi.Scheduler.JobDetails; import org.apache.ode.bpel.iapi.Scheduler.JobType; import org.apache.ode.bpel.intercept.InstanceCountThrottler; import org.apache.ode.bpel.intercept.InterceptorInvoker; import org.apache.ode.bpel.intercept.MessageExchangeInterceptor; import org.apache.ode.bpel.o.OElementVarType; import org.apache.ode.bpel.o.OExpressionLanguage; import org.apache.ode.bpel.o.OMessageVarType; import org.apache.ode.bpel.o.OPartnerLink; import org.apache.ode.bpel.o.OProcess; import org.apache.ode.bpel.o.Serializer; import org.apache.ode.bpel.runtime.ExpressionLanguageRuntimeRegistry; import org.apache.ode.bpel.runtime.InvalidProcessException; import org.apache.ode.bpel.runtime.PROCESS; import org.apache.ode.bpel.runtime.PropertyAliasEvaluationContext; import org.apache.ode.bpel.runtime.channels.FaultData; import org.apache.ode.bpel.wstx.WebServiceTransaction; import org.apache.ode.dao.bpel.BpelDAOConnection; import org.apache.ode.dao.bpel.DeferredProcessInstanceCleanable; import org.apache.ode.dao.bpel.ProcessDAO; import org.apache.ode.dao.bpel.ProcessInstanceDAO; import org.apache.ode.jacob.soup.ReplacementMap; import org.apache.ode.utils.ObjectPrinter; import org.apache.ode.utils.Properties; import org.apache.ode.utils.msg.MessageBundle; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; /** * Entry point into the runtime of a BPEL process. * * @author mszefler * @author Matthieu Riou <mriou at apache dot org> */ public class BpelProcess { private static final String ODE_PROCESS_CHECKGUID = "ode.process.checkguid"; static final Log __log = LogFactory.getLog(BpelProcess.class); private static final Messages __msgs = MessageBundle.getMessages(Messages.class); private volatile Map<OPartnerLink, PartnerLinkPartnerRoleImpl> _partnerRoles; private volatile Map<OPartnerLink, PartnerLinkMyRoleImpl> _myRoles; /** Mapping from a myrole to a {"Service Name" (QNAME) / port}. It's actually more a tuple than a map as * it's important to note that the same process with the same endpoint can have 2 different myroles. */ private volatile Map<PartnerLinkMyRoleImpl, Endpoint> _endpointToMyRoleMap; /** Mapping from a potentially shared endpoint to its EPR */ private SharedEndpoints _sharedEps; // Backup hashmaps to keep initial endpoints handy after dehydration private Map<Endpoint, EndpointReference> _myEprs = new HashMap<Endpoint, EndpointReference>(); private Map<Endpoint, EndpointReference> _partnerEprs = new HashMap<Endpoint, EndpointReference>(); private Map<Endpoint, PartnerRoleChannel> _partnerChannels = new HashMap<Endpoint, PartnerRoleChannel>(); final QName _pid; private volatile OProcess _oprocess; // Has the process already been hydrated before? private boolean _hydratedOnce = false; /** Last time the process was used. */ private volatile long _lastUsed; BpelEngineImpl _engine; ClassLoader _classLoader = getClass().getClassLoader(); DebuggerSupport _debugger; ExpressionLanguageRuntimeRegistry _expLangRuntimeRegistry; private ReplacementMap _replacementMap; final ProcessConf _pconf; /** {@link MessageExchangeInterceptor}s registered for this process. */ private final List<MessageExchangeInterceptor> _mexInterceptors = new ArrayList<MessageExchangeInterceptor>(); /** Latch-like thing to control hydration/dehydration. */ private HydrationLatch _hydrationLatch; /** Deploy-time configuraton for external variables. */ private ExternalVariableConf _extVarConf; private ExternalVariableManager _evm; /** Determine if BPEL events should be persisted, as well as distributed to event listeners */ private boolean _persistBpelEvents=true; public static final QName PROP_PATH = new QName("PATH"); public static final QName PROP_SVG = new QName("SVG"); public static final QName PROP_LAZY_HYDRATE = new QName("process.hydration.lazy"); public static final QName PROP_MAX_INSTANCES = new QName("process.instance.throttled.maximum.count"); // The ratio of in-memory vs serialized size of compiled bpel object. private static final int PROCESS_MEMORY_TO_SERIALIZED_SIZE_RATIO = 5; private boolean _checkGuid=true; /** Web service transaction of process instance */ private Map<Long, WebServiceTransaction> _wstMap; public BpelProcess(ProcessConf conf) { _pid = conf.getProcessId(); _pconf = conf; _hydrationLatch = new HydrationLatch(); } /** * Retrives the base URI to use for local resource resolution. * * @return URI - instance representing the absolute file path to the physical location of the process definition folder. */ public URI getBaseResourceURI() { return this._pconf.getBaseURI(); } /** * Intiialize the external variable configuration/engine manager. This is called from hydration logic, so it * is possible to change the external variable configuration at runtime. * */ void initExternalVariables() { List<Element> conf = _pconf.getExtensionElement(ExternalVariableConf.EXTVARCONF_ELEMENT); _extVarConf = new ExternalVariableConf(conf); _evm = new ExternalVariableManager(_pid, _extVarConf, _engine._contexts.externalVariableEngines, _oprocess); } public String toString() { return "BpelProcess[" + _pid + "]"; } public ExternalVariableManager getEVM() { return _evm; } public void recoverActivity(ProcessInstanceDAO instanceDAO, String channel, long activityId, String action, FaultData fault) { if (__log.isDebugEnabled()) __log.debug("Recovering activity in process " + instanceDAO.getInstanceId() + " with action " + action); markused(); BpelRuntimeContextImpl processInstance = createRuntimeContext(instanceDAO, null, null); processInstance.recoverActivity(channel, activityId, action, fault); } protected DebuggerSupport createDebuggerSupport() { return new DebuggerSupport(this); } protected DebuggerSupport getDebuggerSupport() { return _debugger; } static String generateMessageExchangeIdentifier(String partnerlinkName, String operationName) { StringBuffer sb = new StringBuffer(partnerlinkName); sb.append('.'); sb.append(operationName); return sb.toString(); } public interface InvokeHandler { boolean invoke(PartnerLinkMyRoleImpl target, PartnerLinkMyRoleImpl.RoutingInfo routingInfo, boolean createInstance); } public void invokeProcess(MyRoleMessageExchangeImpl mex, InvokeHandler invokeHandler) { boolean routed = false; try { _hydrationLatch.latch(1); List<PartnerLinkMyRoleImpl> targets = getMyRolesForService(mex.getServiceName()); if (targets.isEmpty()) { String errmsg = __msgs.msgMyRoleRoutingFailure(mex.getMessageExchangeId()); __log.error(errmsg); mex.setFailure(MessageExchange.FailureType.UNKNOWN_ENDPOINT, errmsg, null); return; } mex.getDAO().setProcess(getProcessDAO()); if (!processInterceptors(mex, InterceptorInvoker.__onProcessInvoked)) { __log.debug("Aborting processing of mex " + mex + " due to interceptors."); return; } markused(); // Ideally, if Java supported closure, the routing code would return null or the appropriate // closure to handle the route. List<PartnerLinkMyRoleImpl.RoutingInfo> routings = null; for (PartnerLinkMyRoleImpl target : targets) { if (target.getMyRoleOperation(mex.getOperationName()) != null) { routings = target.findRoute(mex); if (routings != null) { boolean createInstance = target.isCreateInstance(mex); if (mex.getStatus() != MessageExchange.Status.FAILURE) { for (PartnerLinkMyRoleImpl.RoutingInfo routing : routings) { routed = routed || invokeHandler.invoke(target, routing, createInstance); } } if (routed) { break; } } } } // Nothing found, saving for later if (!routed) { // TODO this is kind of hackish when no match and more than one myrole is selected. // we save the routing on the last myrole // actually the message queue should be attached to the instance instead of the correlator targets.get(targets.size()-1).noRoutingMatch(mex, routings); } else { // Now we have to update our message exchange status. If the <reply> was not hit during the // invocation, then we will be in the "REQUEST" phase which means that either this was a one-way // or a two-way that needs to delivery the reply asynchronously. if (mex.getStatus() == MessageExchange.Status.REQUEST) { mex.setStatus(MessageExchange.Status.ASYNC); } markused(); } } finally { _hydrationLatch.release(1); } // For a one way, once the engine is done, the mex can be safely released. // Sean: not really, if route is not found, we cannot delete the mex yet if (mex.getPattern().equals(MessageExchange.MessageExchangePattern.REQUEST_ONLY) && routed && getCleanupCategories(false).contains(CLEANUP_CATEGORY.MESSAGES)) { mex.release(); } } /** * Entry point for message exchanges aimed at the my role. * * @param mex */ void invokeProcess(final MyRoleMessageExchangeImpl mex) { invokeProcess(mex, new InvokeHandler() { public boolean invoke(PartnerLinkMyRoleImpl target, PartnerLinkMyRoleImpl.RoutingInfo routing, boolean createInstance) { if (routing.messageRoute == null && createInstance) { // No route but we can create a new instance target.invokeNewInstance(mex, routing); return true; } else if (routing.messageRoute != null) { // Found a route, hitting it _engine.acquireInstanceLock(routing.messageRoute.getTargetInstance().getInstanceId()); target.invokeInstance(mex, routing); return true; } return false; } }); } /** Several myroles can use the same service in a given process */ private List<PartnerLinkMyRoleImpl> getMyRolesForService(QName serviceName) { List<PartnerLinkMyRoleImpl> myRoles = new ArrayList<PartnerLinkMyRoleImpl>(5); for (Map.Entry<PartnerLinkMyRoleImpl,Endpoint> e : getEndpointToMyRoleMap().entrySet()) { if (e.getValue().serviceName.equals(serviceName)) myRoles.add(e.getKey()); } return myRoles; } void initMyRoleMex(MyRoleMessageExchangeImpl mex) { markused(); PartnerLinkMyRoleImpl target = null; PortType portType = null; Operation operation = null; for (Map.Entry<PartnerLinkMyRoleImpl,Endpoint> e : getEndpointToMyRoleMap().entrySet()) { if (e.getValue().serviceName.equals(mex.getServiceName())) { // First one is fine as we're only interested in the portType and operation here and // even if a process has 2 myrole partner links target = e.getKey(); if (target != null) { portType = target._plinkDef.myRolePortType; operation = target._plinkDef.getMyRoleOperation(mex.getOperationName()); if (operation != null) { mex.setPortOp(portType, operation); break; } } } } if (target != null) { if (operation == null) { throw new BpelEngineException("Operation[" + mex.getOperationName() + "] could not be found on the portType [" + portType.getQName() + "]"); } } else { __log.warn("Couldn't find endpoint from service " + mex.getServiceName() + " when initializing a myRole mex."); } } /** * 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 { markused(); PropertyAliasEvaluationContext ectx = new PropertyAliasEvaluationContext(msgData, alias); Node lValue = ectx.getRootNode(); if (alias.location != null) { try { lValue = _expLangRuntimeRegistry.evaluateNode(alias.location, ectx); } catch (EvaluationException ec) { throw new FaultException(getOProcess().constants.qnSelectionFailure, alias.getDescription()); } } if (lValue == null) { String errmsg = __msgs.msgPropertyAliasReturnedNullSet(alias.getDescription(), target); if (__log.isErrorEnabled()) { __log.error(errmsg); } throw new FaultException(getOProcess().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; } /** * Get the element name for a given WSDL part. If the part is an <em>element</em> part, the name of that element is returned. * If the part is an XML schema typed part, then the name of the part is returned in the null namespace. * * @param part * WSDL {@link javax.wsdl.Part} * @return name of element containing said part */ static QName getElementNameForPart(OMessageVarType.Part part) { return (part.type instanceof OElementVarType) ? ((OElementVarType) part.type).elementType : new QName(null, part.name); } /** * Process the message-exchange interceptors. * * @param mex * message exchange * @return <code>true</code> if execution should continue, <code>false</code> otherwise */ public boolean processInterceptors(MyRoleMessageExchangeImpl mex, InterceptorInvoker invoker) { InterceptorContextImpl ictx = new InterceptorContextImpl(_engine._contexts.dao.getConnection(), getProcessDAO(), _pconf, _engine, this); for (MessageExchangeInterceptor i : _mexInterceptors) if (!mex.processInterceptor(i, mex, ictx, invoker)) return false; for (MessageExchangeInterceptor i : getEngine().getGlobalInterceptors()) if (!mex.processInterceptor(i, mex, ictx, invoker)) return false; return true; } /** * @see org.apache.ode.bpel.engine.BpelProcess#handleJobDetails(java.util.Map<java.lang.String,java.lang.Object>) */ public void handleJobDetails(JobDetails jobData) { try { _hydrationLatch.latch(1); markused(); if (__log.isDebugEnabled()) { __log.debug(ObjectPrinter.stringifyMethodEnter("handleJobDetails", new Object[] { "jobData", jobData })); } JobDetails we = jobData; // Process level events if (we.getType().equals(JobType.INVOKE_INTERNAL)) { if (__log.isDebugEnabled()) { __log.debug("InvokeInternal event for mexid " + we.getMexId()); } MyRoleMessageExchangeImpl mex = (MyRoleMessageExchangeImpl) _engine.getMessageExchange(we.getMexId()); invokeProcess(mex); } else { // Instance level events ProcessInstanceDAO procInstance = getProcessDAO().getInstance(we.getInstanceId()); if (procInstance == null) { if (__log.isDebugEnabled()) { __log.debug("handleJobDetails: no ProcessInstance found with iid " + we.getInstanceId() + "; ignoring."); } return; } BpelRuntimeContextImpl processInstance = createRuntimeContext(procInstance, null, null); switch (we.getType()) { case TIMER: if (__log.isDebugEnabled()) { __log.debug("handleJobDetails: TimerWork event for process instance " + processInstance); } processInstance.timerEvent(we.getChannel()); break; case RESUME: if (__log.isDebugEnabled()) { __log.debug("handleJobDetails: ResumeWork event for iid " + we.getInstanceId()); } processInstance.execute(); break; case INVOKE_RESPONSE: if (__log.isDebugEnabled()) { __log.debug("InvokeResponse event for iid " + we.getInstanceId()); } processInstance.invocationResponse(we.getMexId(), we.getChannel()); processInstance.execute(); break; case MATCHER: if (__log.isDebugEnabled()) { __log.debug("Matcher event for iid " + we.getInstanceId()); } if( procInstance.getState() == ProcessState.STATE_COMPLETED_OK || procInstance.getState() == ProcessState.STATE_COMPLETED_WITH_FAULT ) { __log.debug("A matcher event was aborted. The process is already completed."); return; } processInstance.matcherEvent(we.getCorrelatorId(), we.getCorrelationKeySet()); } } } finally { _hydrationLatch.release(1); } } private void setRoles(OProcess oprocess) { _partnerRoles = new HashMap<OPartnerLink, PartnerLinkPartnerRoleImpl>(); _myRoles = new HashMap<OPartnerLink, PartnerLinkMyRoleImpl>(); _endpointToMyRoleMap = new HashMap<PartnerLinkMyRoleImpl, Endpoint>(); // Create myRole endpoint name mapping (from deployment descriptor) HashMap<OPartnerLink, Endpoint> myRoleEndpoints = new HashMap<OPartnerLink, Endpoint>(); for (Map.Entry<String, Endpoint> provide : _pconf.getProvideEndpoints().entrySet()) { OPartnerLink plink = oprocess.getPartnerLink(provide.getKey()); if (plink == null) { String errmsg = "Error in deployment descriptor for process " + _pid + "; reference to unknown partner link " + provide.getKey(); __log.error(errmsg); throw new BpelEngineException(errmsg); } myRoleEndpoints.put(plink, provide.getValue()); } // Create partnerRole initial value mapping for (Map.Entry<String, Endpoint> invoke : _pconf.getInvokeEndpoints().entrySet()) { OPartnerLink plink = oprocess.getPartnerLink(invoke.getKey()); if (plink == null) { String errmsg = "Error in deployment descriptor for process " + _pid + "; reference to unknown partner link " + invoke.getKey(); __log.error(errmsg); throw new BpelEngineException(errmsg); } __log.debug("Processing <invoke> element for process " + _pid + ": partnerlink " + invoke.getKey() + " --> " + invoke.getValue()); } for (OPartnerLink pl : oprocess.getAllPartnerLinks()) { if (pl.hasMyRole()) { Endpoint endpoint = myRoleEndpoints.get(pl); if (endpoint == null) throw new IllegalArgumentException("No service name for myRole plink " + pl.getName()); PartnerLinkMyRoleImpl myRole = new PartnerLinkMyRoleImpl(this, pl, endpoint); _myRoles.put(pl, myRole); _endpointToMyRoleMap.put(myRole, endpoint); } if (pl.hasPartnerRole()) { Endpoint endpoint = _pconf.getInvokeEndpoints().get(pl.getName()); if (endpoint == null && pl.initializePartnerRole) throw new IllegalArgumentException(pl.getName() + " must be bound to an endpoint in deploy.xml"); PartnerLinkPartnerRoleImpl partnerRole = new PartnerLinkPartnerRoleImpl(this, pl, endpoint); _partnerRoles.put(pl, partnerRole); } } } public ProcessDAO getProcessDAO() { return _pconf.isTransient() ? _engine._contexts.inMemDao.getConnection().getProcess(_pid) : getEngine()._contexts.dao .getConnection().getProcess(_pid); } static String genCorrelatorId(OPartnerLink plink, String opName) { return plink.getName() + "." + opName; } /** * De-serialize the compiled process representation from a stream. * * @param is * input stream * @return process information from configuration database */ private OProcess deserializeCompiledProcess(InputStream is) throws Exception { OProcess compiledProcess; Serializer ofh = new Serializer(is); compiledProcess = ofh.readOProcess(); return compiledProcess; } /** * Get all the services that are implemented by this process. * * @return list of qualified names corresponding to the myroles. */ public Set<Endpoint> getServiceNames() { Set<Endpoint> endpoints = new HashSet<Endpoint>(); for (Endpoint provide : _pconf.getProvideEndpoints().values()) { endpoints.add(provide); } return endpoints; } public void setPersistBpelEvents(boolean persist) { _persistBpelEvents = persist; __log.debug("Should persist BPEL events? "+persist); } void activate(BpelEngineImpl engine) { _engine = engine; _sharedEps = _engine.getSharedEndpoints(); _debugger = createDebuggerSupport(); // TODO: Make it configurable if possible - from ODE-2.x-fixes, where the server is available //_checkGuid = server.getConfigProperties().getProperties().getProperty(ODE_PROCESS_CHECKGUID, // Boolean.TRUE.toString()).equalsIgnoreCase(Boolean.TRUE.toString()); _checkGuid = false; __log.debug("Should check GUID? "+_checkGuid); if (getInstanceMaximumCount() < Integer.MAX_VALUE) registerMessageExchangeInterceptor(new InstanceCountThrottler()); __log.debug("Activating " + _pid); // Activate all the my-role endpoints. for (Map.Entry<String, Endpoint> entry : _pconf.getProvideEndpoints().entrySet()) { Endpoint endpoint = entry.getValue(); EndpointReference initialEPR = null; if (isShareable(endpoint)) { // Check if the EPR already exists for the given endpoint initialEPR = _sharedEps.getEndpointReference(endpoint); if (initialEPR == null) { // Create an EPR by physically activating the endpoint initialEPR = _engine._contexts.bindingContext.activateMyRoleEndpoint(_pid, endpoint); _sharedEps.addEndpoint(endpoint, initialEPR); __log.debug("Activated " + _pid + " myrole " + entry.getKey() + ": EPR is " + initialEPR); } // Increment the reference count on the endpoint _sharedEps.incrementReferenceCount(endpoint); } else { // Create an EPR by physically activating the endpoint initialEPR = _engine._contexts.bindingContext.activateMyRoleEndpoint(_pid, endpoint); __log.debug("Activated " + _pid + " myrole " + entry.getKey() + ": EPR is " + initialEPR); } _myEprs.put(endpoint, initialEPR); } __log.debug("Activated " + _pid); markused(); } void deactivate() { // Deactivate all the my-role endpoints. for (Endpoint endpoint : _myEprs.keySet()) { // Deactivate the EPR only if there are no more references // to this endpoint from any (active) BPEL process. if (isShareable(endpoint)) { if(__log.isDebugEnabled()) __log.debug("deactivating shared endpoint " + endpoint+ " for pid "+ _pid); if (!_sharedEps.decrementReferenceCount(endpoint)) { _engine._contexts.bindingContext.deactivateMyRoleEndpoint(endpoint); _sharedEps.removeEndpoint(endpoint); } } else { if(__log.isDebugEnabled()) __log.debug("deactivating non-shared endpoint " + endpoint + " for pid "+ _pid); _engine._contexts.bindingContext.deactivateMyRoleEndpoint(endpoint); } } } private boolean isShareable(Endpoint endpoint) { return _pconf.isSharedService(endpoint.serviceName); } protected EndpointReference getInitialPartnerRoleEPR(OPartnerLink link) { try { _hydrationLatch.latch(1); PartnerLinkPartnerRoleImpl prole = _partnerRoles.get(link); if (prole == null) throw new IllegalStateException("Unknown partner link " + link); return prole.getInitialEPR(); } finally { _hydrationLatch.release(1); } } protected Endpoint getInitialPartnerRoleEndpoint(OPartnerLink link) { try { _hydrationLatch.latch(1); PartnerLinkPartnerRoleImpl prole = _partnerRoles.get(link); if (prole == null) throw new IllegalStateException("Unknown partner link " + link); return prole._initialPartner; } finally { _hydrationLatch.release(1); } } protected EndpointReference getInitialMyRoleEPR(OPartnerLink link) { try { _hydrationLatch.latch(1); PartnerLinkMyRoleImpl myRole = _myRoles.get(link); if (myRole == null) throw new IllegalStateException("Unknown partner link " + link); return myRole.getInitialEPR(); } finally { _hydrationLatch.release(1); } } public QName getPID() { return _pid; } protected PartnerRoleChannel getPartnerRoleChannel(OPartnerLink partnerLink) { try { _hydrationLatch.latch(1); PartnerLinkPartnerRoleImpl prole = _partnerRoles.get(partnerLink); if (prole == null) throw new IllegalStateException("Unknown partner link " + partnerLink); return prole._channel; } finally { _hydrationLatch.release(1); } } public void saveEvent(ProcessInstanceEvent event, ProcessInstanceDAO instanceDao) { saveEvent(event, instanceDao, null); } public void saveEvent(ProcessInstanceEvent event, ProcessInstanceDAO instanceDao, List<String> scopeNames) { markused(); if (_pconf.isEventEnabled(scopeNames, event.getType())) { // notify the listeners _engine.fireEvent(event); if (instanceDao != null) { if (_persistBpelEvents) { instanceDao.insertBpelEvent(event); } } else { __log.debug("Couldn't find instance to save event, no event generated!"); } } } /** * Ask the process to dehydrate. */ void dehydrate() { try { _hydrationLatch.latch(0); // We don't actually need to do anything, the latch will run the doDehydrate method // when necessary.. } finally { _hydrationLatch.release(0); } } void hydrate() { try { _hydrationLatch.latch(1); // We don't actually need to do anything, the latch will run the doHydrate method // when necessary.. } finally { _hydrationLatch.release(1); } } public OProcess getOProcess() { try { _hydrationLatch.latch(1); return _oprocess; } finally { _hydrationLatch.release(1); } } private Map<PartnerLinkMyRoleImpl,Endpoint> getEndpointToMyRoleMap() { try { _hydrationLatch.latch(1); return _endpointToMyRoleMap; } finally { _hydrationLatch.release(1); } } public ReplacementMap getReplacementMap(QName processName) { try { _hydrationLatch.latch(1); if (processName.equals(_pid)) return _replacementMap; else try { // We're asked for an older version of this process, fetching it OProcess oprocess = _engine.getOProcess(processName); if (oprocess == null) { String errmsg = "The process " + _pid + " is not available anymore."; __log.error(errmsg); throw new InvalidProcessException(errmsg, InvalidProcessException.RETIRED_CAUSE_CODE); } // Older versions may ventually need more expression languages registerExprLang(oprocess); return new ReplacementMapImpl(oprocess); } catch (Exception e) { String errmsg = "The process " + _pid + " is not available anymore."; __log.error(errmsg, e); throw new InvalidProcessException(errmsg, InvalidProcessException.RETIRED_CAUSE_CODE); } } finally { _hydrationLatch.release(1); } } protected BpelEngineImpl getEngine() { return _engine; } public boolean isInMemory() { return _pconf.isTransient(); } public long getLastUsed() { return _lastUsed; } QName getProcessType() { return _pconf.getType(); } /** * Get a hint as to whether this process is hydrated. Note this is only a hint, since things could change. */ public boolean hintIsHydrated() { return _oprocess != null; } /** Keep track of the time the process was last used. */ private final void markused() { _lastUsed = System.currentTimeMillis(); } /** Create a version-appropriate runtime context. */ protected BpelRuntimeContextImpl createRuntimeContext(ProcessInstanceDAO dao, PROCESS template, MyRoleMessageExchangeImpl instantiatingMessageExchange) { return new BpelRuntimeContextImpl(this, dao, template, instantiatingMessageExchange); } private class HydrationLatch extends NStateLatch { HydrationLatch() { super(new Runnable[2]); _transitions[0] = new Runnable() { public void run() { doDehydrate(); } }; _transitions[1] = new Runnable() { public void run() { doHydrate(); } }; } private void doDehydrate() { if (_oprocess != null) { _oprocess.dehydrate(); _oprocess = null; } if (_myRoles != null) { _myRoles.clear(); } if (_endpointToMyRoleMap != null) { _endpointToMyRoleMap.clear(); } if (_partnerRoles != null) { _partnerRoles.clear(); } // Don't clear stuff you can't re-populate // if (_myEprs != null) { // _myEprs.clear(); // } // if (_partnerChannels != null) { // _partnerChannels.clear(); // } // if (_partnerEprs != null) { // _partnerEprs.clear(); // } _replacementMap = null; _expLangRuntimeRegistry = null; } private void doHydrate() { markused(); __log.debug("Rehydrating process " + _pconf.getProcessId()); try { InputStream inputStream = _pconf.getCBPInputStream(); try { _oprocess = deserializeCompiledProcess(inputStream); } finally { inputStream.close(); } } catch (Exception e) { String errmsg = "The process " + _pid + " is no longer available."; __log.error(errmsg, e); throw new BpelEngineException(errmsg, e); } if (_partnerRoles == null) { _partnerRoles = new HashMap<OPartnerLink, PartnerLinkPartnerRoleImpl>(); } if (_myRoles == null) { _myRoles = new HashMap<OPartnerLink, PartnerLinkMyRoleImpl>(); } if (_endpointToMyRoleMap == null) { _endpointToMyRoleMap = new HashMap<PartnerLinkMyRoleImpl, Endpoint>(); } if (_myEprs == null) { _myEprs = new HashMap<Endpoint, EndpointReference>(); } if (_partnerChannels == null) { _partnerChannels = new HashMap<Endpoint, PartnerRoleChannel>(); } if (_partnerEprs == null) { _partnerEprs = new HashMap<Endpoint, EndpointReference>(); } _replacementMap = new ReplacementMapImpl(_oprocess); // Create an expression language registry for this process _expLangRuntimeRegistry = new ExpressionLanguageRuntimeRegistry(); registerExprLang(_oprocess); setRoles(_oprocess); initExternalVariables(); if (!_hydratedOnce) { for (PartnerLinkPartnerRoleImpl prole : _partnerRoles.values()) { // Null for initializePartnerRole = false if (prole._initialPartner != null) { PartnerRoleChannel channel = _engine._contexts.bindingContext.createPartnerRoleChannel(_pid, prole._plinkDef.partnerRolePortType, prole._initialPartner); prole._channel = channel; _partnerChannels.put(prole._initialPartner, prole._channel); EndpointReference epr = channel.getInitialEndpointReference(); if (epr != null) { prole._initialEPR = epr; _partnerEprs.put(prole._initialPartner, epr); } __log.debug("Activated " + _pid + " partnerrole " + prole.getPartnerLinkName() + ": EPR is " + prole._initialEPR); } } _engine.setProcessSize(_pid, true); _hydratedOnce = true; } for (PartnerLinkMyRoleImpl myrole : _myRoles.values()) { myrole._initialEPR = _myEprs.get(myrole._endpoint); } for (PartnerLinkPartnerRoleImpl prole : _partnerRoles.values()) { prole._channel = _partnerChannels.get(prole._initialPartner); if (_partnerEprs.get(prole._initialPartner) != null) { prole._initialEPR = _partnerEprs.get(prole._initialPartner); } } /* * If necessary, create an object in the data store to represent the process. We'll re-use an existing object if it already * exists and matches the GUID. */ if (isInMemory()) { createProcessDAO(_engine._contexts.inMemDao.getConnection(), _pid, _pconf.getVersion(), _oprocess); } else if (_engine._contexts.scheduler.isTransacted()) { // If we have a transaction, we do this in the current transaction if(__log.isDebugEnabled()) __log.debug("Creating new process DAO for " + _pid + " (guid=" + _oprocess.guid + ")..."); createProcessDAO(_engine._contexts.dao.getConnection(), _pid, _pconf.getVersion(), _oprocess); if(__log.isDebugEnabled()) __log.debug("Created new process DAO for " + _pid + " (guid=" + _oprocess.guid + ")."); } else { try { _engine._contexts.scheduler.execTransaction(new Callable<Object>() { public Object call() throws Exception { bounceProcessDAOInDB(_engine._contexts.dao.getConnection(), _pid, _pconf.getVersion(), _oprocess); return null; } }); } catch( RuntimeException re ) { throw re; } catch(Exception e) { throw new RuntimeException(e); } } } } private void bounceProcessDAOInMemory(BpelDAOConnection conn, final QName pid, final long version, final OProcess oprocess) { if(__log.isInfoEnabled()) __log.info("Creating new process DAO[mem] for " + pid + " (guid=" + oprocess.guid + ")."); createProcessDAO(conn, pid, version, oprocess); } private void bounceProcessDAOInDB(final BpelDAOConnection conn, final QName pid, final long version, final OProcess oprocess) { if(__log.isDebugEnabled()) __log.debug("Creating new process DAO for " + pid + " (guid=" + oprocess.guid + ")..."); createProcessDAO(conn, pid, version, oprocess); if(__log.isDebugEnabled()) __log.debug("Created new process DAO for " + pid + " (guid=" + oprocess.guid + ")."); } public int getInstanceInUseCount() { return hintIsHydrated() ? _hydrationLatch.getDepth(1) : 0; } private void createProcessDAO(BpelDAOConnection conn, final QName pid, final long version, final OProcess oprocess) { try { boolean create = true; ProcessDAO old = conn.getProcess(pid); if (old != null) { __log.debug("Found ProcessDAO for " + pid + " with GUID " + old.getGuid()); if (oprocess.guid == null) { // No guid, old version assume its good create = false; } else { if (!_checkGuid || old.getGuid().equals(oprocess.guid)) { // Guids match, no need to create create = false; } } } if (create) { if(__log.isDebugEnabled()) __log.debug("Creating process DAO for " + pid + " (guid=" + oprocess.guid + ")"); ProcessDAO newDao = conn.createProcess(pid, oprocess.getQName(), oprocess.guid, (int) version); for (String correlator : oprocess.getCorrelators()) { newDao.addCorrelator(correlator); } if(__log.isDebugEnabled()) __log.debug("Created new process DAO for " + pid + " (guid=" + oprocess.guid + ")"); } } catch (BpelEngineException ex) { throw ex; } catch (Exception dce) { __log.error("DbError", dce); throw new BpelEngineException("DbError", dce); } } private void registerExprLang(OProcess oprocess) { for (OExpressionLanguage elang : oprocess.expressionLanguages) { try { _expLangRuntimeRegistry.registerRuntime(elang); } catch (ConfigurationException e) { String msg = __msgs.msgExpLangRegistrationError(elang.expressionLanguageUri, elang.properties); __log.error(msg, e); throw new BpelEngineException(msg, e); } } } public boolean isCleanupCategoryEnabled(boolean instanceSucceeded, CLEANUP_CATEGORY category) { return _pconf.isCleanupCategoryEnabled(instanceSucceeded, category); } public Set<CLEANUP_CATEGORY> getCleanupCategories(boolean instanceSucceeded) { return _pconf.getCleanupCategories(instanceSucceeded); } public Node getProcessProperty(QName propertyName) { Map<QName, Node> properties = _pconf.getProcessProperties(); if (properties != null) { return properties.get(propertyName); } return null; } public ProcessConf getConf() { return _pconf; } public boolean hasActiveInstances() { try { _hydrationLatch.latch(1); if (isInMemory() || _engine._contexts.scheduler.isTransacted()) { return hasActiveInstances(getProcessDAO()); } else { // If we do not have a transaction we need to create one. try { return (Boolean) _engine._contexts.scheduler.execTransaction(new Callable<Object>() { public Object call() throws Exception { return hasActiveInstances(getProcessDAO()); } }); } catch (Exception e) { String errmsg = "DbError"; __log.error(errmsg, e); return false; } } } finally { _hydrationLatch.release(1); } } private boolean hasActiveInstances(ProcessDAO processDAO) { // Select count of instances instead of all active instances // Collection<ProcessInstanceDAO> activeInstances = processDAO.getActiveInstances(); // return (activeInstances != null && activeInstances.size() > 0); return processDAO.getNumInstances() > 0; } public void registerMessageExchangeInterceptor(MessageExchangeInterceptor interceptor) { _mexInterceptors.add(interceptor); } public void unregisterMessageExchangeInterceptor(MessageExchangeInterceptor interceptor) { _mexInterceptors.remove(interceptor); } public long sizeOf() { // try to get actual size from sizing agent, if enabled long footprint = SizingAgent.deepSizeOf(this); // if unsuccessful, estimate size (this is a inaccurate guess) if (footprint == 0) { footprint = getEstimatedHydratedSize(); } // add the sizes of all the services this process provides for (EndpointReference myEpr : _myEprs.values()) { footprint += _engine._contexts.bindingContext.calculateSizeofService(myEpr); } // return the total footprint return footprint; } public String getProcessProperty(QName property, String defaultValue) { Text text = (Text) getProcessProperty(property); if (text == null) { return defaultValue; } String value = text.getWholeText(); return (value == null) ? defaultValue : value; } public boolean isHydrationLazy() { return Boolean.valueOf(getProcessProperty(PROP_LAZY_HYDRATE, "true")); } public boolean isHydrationLazySet() { return getProcessProperty(PROP_LAZY_HYDRATE) != null; } public int getInstanceMaximumCount() { return Integer.valueOf(getProcessProperty(PROP_MAX_INSTANCES, Integer.toString(_engine.getInstanceThrottledMaximumCount()))); } public long getEstimatedHydratedSize() { return _pconf.getCBPFileSize() * PROCESS_MEMORY_TO_SERIALIZED_SIZE_RATIO; } public long getTimeout(OPartnerLink partnerLink, boolean p2p) { // OPartnerLink, PartnerLinkPartnerRoleImpl final PartnerLinkPartnerRoleImpl linkPartnerRole = _partnerRoles.get(partnerLink); long timeout = Properties.DEFAULT_MEX_TIMEOUT; if (linkPartnerRole._initialEPR != null) { String property = null; String value = null; Map<String, String> props = _pconf.getEndpointProperties(linkPartnerRole._initialEPR); if (p2p) { property = Properties.PROP_P2P_MEX_TIMEOUT; value = props.get(property); } if (value == null) { property = Properties.PROP_MEX_TIMEOUT; value = props.get(property); } if (value != null) { try { timeout = Long.parseLong(value); } catch (NumberFormatException e) { if (__log.isWarnEnabled()) __log.warn("Mal-formatted Property: [" + property + "=" + value + "] Default value (" + timeout + ") will be used"); } } } return timeout; } public long getVersion() { return Long.parseLong(_pid.getLocalPart().substring(_pid.getLocalPart().lastIndexOf('-') + 1)); } public WebServiceTransaction getWebServiceTransaction(Long instanceId) { return (_wstMap != null)?_wstMap.get(instanceId):null; } public void setWebServiceTransaction(Long instanceId, WebServiceTransaction wst) { if (_wstMap == null) { _wstMap = new HashMap<Long, WebServiceTransaction>(); } _wstMap.put(instanceId, wst); } public void removeWebServiceTransaction(Long instanceId) { _wstMap.remove(instanceId); } }