/* * 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.runtime; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.xml.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ode.bpel.evt.ScopeFaultEvent; import org.apache.ode.bpel.evt.ScopeStartEvent; import org.apache.ode.bpel.evt.VariableModificationEvent; import org.apache.ode.bpel.o.OBase; import org.apache.ode.bpel.o.OCatch; import org.apache.ode.bpel.o.OElementVarType; import org.apache.ode.bpel.o.OEventHandler; import org.apache.ode.bpel.o.OFailureHandling; import org.apache.ode.bpel.o.OFaultHandler; import org.apache.ode.bpel.o.OLink; import org.apache.ode.bpel.o.OMessageVarType; import org.apache.ode.bpel.o.OScope; import org.apache.ode.bpel.o.OVarType; import org.apache.ode.bpel.runtime.channels.CompensationChannel; import org.apache.ode.bpel.runtime.channels.EventHandlerControlChannel; import org.apache.ode.bpel.runtime.channels.FaultData; import org.apache.ode.bpel.runtime.channels.ParentScopeChannel; import org.apache.ode.bpel.runtime.channels.ParentScopeChannelListener; import org.apache.ode.bpel.runtime.channels.TerminationChannel; import org.apache.ode.bpel.runtime.channels.TerminationChannelListener; import org.apache.ode.bpel.runtime.history.ODEPolicyViolationHandler; import org.apache.ode.bpel.runtime.history.ODEWriteVariableFact; import org.apache.ode.jacob.ChannelListener; import org.apache.ode.jacob.SynchChannel; import org.apache.ode.utils.DOMUtils; import org.w3c.dom.Element; import tud.st.bpel.history.PolicyViolatedException; /** * An active scope. */ class SCOPE extends ACTIVITY { private static final long serialVersionUID = 6111903798996023525L; private static final Log __log = LogFactory.getLog(SCOPE.class); private OScope _oscope; private ActivityInfo _child; private Set<EventHandlerInfo> _eventHandlers = new HashSet<EventHandlerInfo>(); /** Constructor. */ public SCOPE(ActivityInfo self, ScopeFrame frame, LinkFrame linkFrame) { super(self, frame, linkFrame); _oscope = (OScope) self.o; assert _oscope.activity != null; } public void run() { // Start the child activity. _child = new ActivityInfo(genMonotonic(), _oscope.activity, newChannel(TerminationChannel.class), newChannel(ParentScopeChannel.class)); instance(createChild(_child, _scopeFrame, _linkFrame)); if (_oscope.eventHandler != null) { for (Iterator<OEventHandler.OAlarm> i = _oscope.eventHandler.onAlarms.iterator(); i.hasNext(); ) { OEventHandler.OAlarm alarm = i.next(); EventHandlerInfo ehi = new EventHandlerInfo(alarm, newChannel(EventHandlerControlChannel.class), newChannel(ParentScopeChannel.class), newChannel(TerminationChannel.class)); _eventHandlers.add(ehi); instance(new EH_ALARM(ehi.psc,ehi.tc, ehi.cc, alarm, _scopeFrame)); } for (Iterator<OEventHandler.OEvent> i = _oscope.eventHandler.onMessages.iterator(); i.hasNext(); ) { OEventHandler.OEvent event = i.next(); EventHandlerInfo ehi = new EventHandlerInfo(event, newChannel(EventHandlerControlChannel.class), newChannel(ParentScopeChannel.class), newChannel(TerminationChannel.class)); _eventHandlers.add(ehi); instance(new EH_EVENT(ehi.psc,ehi.tc, ehi.cc, event, _scopeFrame)); } } getBpelRuntimeContext().initializePartnerLinks(_scopeFrame.scopeInstanceId, _oscope.partnerLinks.values()); sendEvent(new ScopeStartEvent()); instance(new ACTIVE()); } private List<CompensationHandler> findCompensationData(OScope scope) { List<CompensationHandler> out = new ArrayList<CompensationHandler>(); for (Iterator<CompensationHandler> i = _scopeFrame.availableCompensations.iterator(); i.hasNext(); ) { CompensationHandler ch = i.next(); if (null == scope || ch.compensated.oscope.equals(scope)) out.add(ch); } // TODO: sort out in terms of completion order return out; } class ACTIVE extends ACTIVITY { private static final long serialVersionUID = -5876892592071965346L; /** Links collected. */ private boolean _terminated; private FaultData _fault; private long _startTime; private final HashSet<CompensationHandler> _compensations = new HashSet<CompensationHandler>(); private boolean _childTermRequested; ACTIVE() { super(SCOPE.this._self, SCOPE.this._scopeFrame, SCOPE.this._linkFrame); _startTime = System.currentTimeMillis(); } public void run() { if (_child != null || !_eventHandlers.isEmpty()) { HashSet<ChannelListener> mlSet = new HashSet<ChannelListener>(); // Listen to messages from our parent. mlSet.add(new TerminationChannelListener(_self.self) { private static final long serialVersionUID = 1913414844895865116L; public void terminate() { _terminated = true; // Forward the termination request to the nested activity. if (_child != null && !_childTermRequested) { replication(_child.self).terminate(); _childTermRequested = true; } // Forward the termination request to our event handlers. terminateEventHandlers(); instance(ACTIVE.this); } }); // Handle messages from the child if it is still alive if (_child != null) { mlSet.add(new ParentScopeChannelListener(_child.parent) { private static final long serialVersionUID = -6934246487304813033L; public void compensate(OScope scope, SynchChannel ret) { // If this scope does not have available compensations, defer to // parent scope, otherwise do compensation. if (_scopeFrame.availableCompensations == null) _self.parent.compensate(scope, ret); else { // TODO: Check if we are doing duplicate compensation List<CompensationHandler> compensations = findCompensationData(scope); _scopeFrame.availableCompensations.removeAll(compensations); instance(new ORDEREDCOMPENSATOR(compensations, ret)); } instance(ACTIVE.this); } public void completed(FaultData flt, Set<CompensationHandler> compenstations) { // Set the fault to the activity's choice, if and only if no previous fault // has been detected (first fault wins). if (flt != null && _fault == null) _fault = flt; _child = null; _compensations.addAll(compenstations); if (flt == null) stopEventHandlers(); else terminateEventHandlers(); instance(ACTIVE.this); } public void cancelled() { // Implicit scope holds links of the enclosed activity, // they only get cancelled when we propagate upwards. if (_oscope.implicitScope) _self.parent.cancelled(); else completed(null, CompensationHandler.emptySet()); } public void failure(String reason, Element data) { completed(createFault(OFailureHandling.FAILURE_FAULT_NAME, _self.o, null), CompensationHandler.emptySet()); } }); } // Similarly, handle messages from the event handler, if one exists // and if it has not completed. for (Iterator<EventHandlerInfo> i = _eventHandlers.iterator();i.hasNext();) { final EventHandlerInfo ehi = i.next(); mlSet.add(new ParentScopeChannelListener(ehi.psc) { private static final long serialVersionUID = -4694721357537858221L; public void compensate(OScope scope, SynchChannel ret) { // ACTIVE scopes do not compensate, send request up to parent. _self.parent.compensate(scope, ret); instance(ACTIVE.this); } public void completed(FaultData flt, Set<CompensationHandler> compenstations) { // Set the fault to the activity's choice, if and only if no previous fault // has been detected (first fault wins). if (flt != null && _fault == null) _fault = flt; _eventHandlers.remove(ehi); _compensations.addAll(compenstations); if (flt != null) { // Terminate child if we get a fault from the event handler. if (_child != null && !_childTermRequested) { replication(_child.self).terminate(); _childTermRequested = true; } terminateEventHandlers(); } else stopEventHandlers(); instance(ACTIVE.this); } public void cancelled() { completed(null, CompensationHandler.emptySet()); } public void failure(String reason, Element data) { completed(null, CompensationHandler.emptySet()); } }); } object(false, mlSet); } else /* nothing to wait for... */ { // Any compensation handlers that were available but not activated will be forgotten. Set<CompensationHandler> unreachableCompensationHandlers = _scopeFrame.availableCompensations; if (unreachableCompensationHandlers != null) for (Iterator<CompensationHandler> i = unreachableCompensationHandlers.iterator(); i.hasNext(); ) { CompensationHandler ch = i.next(); ch.compChannel.forget(); } _scopeFrame.availableCompensations = null; // Maintain a set of links needing dead-path elimination. Set<OLink> linksNeedingDPE = new HashSet<OLink>(); if (_oscope.faultHandler != null) for (Iterator<OCatch> i = _oscope.faultHandler.catchBlocks.iterator(); i.hasNext(); ) linksNeedingDPE.addAll(i.next().outgoingLinks); // We're done with the main work, if we were terminated, we will // need to load the termination handler: if (_terminated) { __log.debug("Scope: " + _oscope + " was terminated."); // ??? Should we forward _self.parent.completed(null,_compensations); } else if (_fault != null) { sendEvent(new ScopeFaultEvent(_fault.getFaultName(), _fault.getFaultLineNo(),_fault.getExplanation())); // Find a fault handler for our fault. // AL: added PID parameter OCatch catchBlock = _oscope.faultHandler == null ? null : findCatch(getBpelRuntimeContext().getPid(), _oscope.faultHandler, _fault.getFaultName(), _fault.getFaultType()); // Collect all the compensation data for completed child scopes. assert !!_eventHandlers.isEmpty(); assert _child == null; if (catchBlock == null) { // If we cannot find a catch block for this fault, then we simply propagate the fault // to the parent. NOTE: the "default" fault handler as described in the BPEL spec // must be generated by the compiler. if (__log.isDebugEnabled()) __log.debug(_self + ": has no fault handler for " + _fault.getFaultName() + "; scope will propagate FAULT!"); _self.parent.completed(_fault, _compensations); } else /* catchBlock != null */ { if (__log.isDebugEnabled()) __log.debug(_self + ": has a fault handler for " + _fault.getFaultName() + ": "+ catchBlock); linksNeedingDPE.removeAll(catchBlock.outgoingLinks); // We have to create a scope for the catch block. BpelRuntimeContext ntive = getBpelRuntimeContext(); ActivityInfo faultHandlerActivity = new ActivityInfo(genMonotonic(), catchBlock, newChannel(TerminationChannel.class,"FH"), newChannel(ParentScopeChannel.class,"FH")); ScopeFrame faultHandlerScopeFrame = new ScopeFrame(catchBlock, ntive.createScopeInstance(_scopeFrame.scopeInstanceId, catchBlock), _scopeFrame, _compensations, _fault); if (catchBlock.faultVariable != null) { try { VariableInstance vinst = faultHandlerScopeFrame.resolve(catchBlock.faultVariable); // AL: SetVar try { history.setVar(getBpelRuntimeContext().getPid(), new ODEWriteVariableFact(_oscope, vinst, _fault.getFaultMessage()), new ODEPolicyViolationHandler(_self)); } catch(PolicyViolatedException e) { return; } initializeVariable(vinst, _fault.getFaultMessage()); // Generating event VariableModificationEvent se = new VariableModificationEvent(vinst.declaration.name); se.setNewValue(_fault.getFaultMessage()); if (_oscope.debugInfo != null) se.setLineNo(_oscope.debugInfo.startLine); sendEvent(se); } catch (Exception ex) { __log.fatal(ex); throw new InvalidProcessException(ex); } } // Create the fault handler scope. instance(new SCOPE(faultHandlerActivity,faultHandlerScopeFrame, SCOPE.this._linkFrame)); object(new ParentScopeChannelListener(faultHandlerActivity.parent) { private static final long serialVersionUID = -6009078124717125270L; public void compensate(OScope scope, SynchChannel ret) { // This should never happen. throw new AssertionError("received compensate request!"); } public void completed(FaultData fault, Set<CompensationHandler> compensations) { // The compensations that have been registered here, will never be activated, // so we'll forget them as soon as possible. for(Iterator<CompensationHandler> i = compensations.iterator();i.hasNext();) i.next().compChannel.forget(); _self.parent.completed(fault, CompensationHandler.emptySet()); } public void cancelled() { completed(null, CompensationHandler.emptySet()); } public void failure(String reason, Element data) { completed(null, CompensationHandler.emptySet()); } }); } } else /* completed ok */ { if (_oscope.compensationHandler != null) { CompensationHandler compensationHandler = new CompensationHandler( _scopeFrame, newChannel(CompensationChannel.class), _startTime, System.currentTimeMillis()); _self.parent.completed(null, Collections.singleton(compensationHandler)); instance(new COMPENSATIONHANDLER_(compensationHandler, _compensations)); } else /* no compensation handler */ { _self.parent.completed(null, _compensations); } } // DPE links needing DPE (i.e. the unselected catch blocks). dpe(linksNeedingDPE); } } private void terminateEventHandlers() { for (Iterator<EventHandlerInfo> i = _eventHandlers.iterator();i.hasNext(); ) { EventHandlerInfo ehi = i.next(); if (!ehi.terminateRequested && !ehi.stopRequested) { replication(ehi.tc).terminate(); ehi.terminateRequested = true; } } } private void stopEventHandlers() { for (Iterator<EventHandlerInfo> i = _eventHandlers.iterator();i.hasNext();) { EventHandlerInfo ehi = i.next(); if (!ehi.stopRequested && !ehi.terminateRequested) { ehi.cc.stop(); ehi.stopRequested = true; } } } } private static OCatch findCatch(Long pid, OFaultHandler fh, QName faultName, OVarType faultType) { // AL: if a policy was violated and allowFaultHandlers==false, return null if(!history.getAllowFaultHandlers() && history.isPolicyViolated(pid)) { return null; } OCatch bestMatch = null; for (Iterator<OCatch> i = fh.catchBlocks.iterator(); i.hasNext();) { OCatch c = i.next(); // First we try to eliminate this catch block based on fault-name mismatches: if (c.faultName != null) { if (faultName == null) continue; if (!faultName.equals(c.faultName)) continue; } // Then we try to eliminate this catch based on type incompatibility: if (c.faultVariable != null) { if (faultType == null) continue; else if (c.faultVariable.type instanceof OMessageVarType) { if (faultType instanceof OMessageVarType && ((OMessageVarType)faultType).equals(c.faultVariable.type)) { // Don't eliminate. } else if (faultType instanceof OElementVarType && ((OMessageVarType)c.faultVariable.type).docLitType !=null && !((OMessageVarType)c.faultVariable.type).docLitType.equals(faultType)) { // Don't eliminate. } else { continue; // Eliminate. } } else if (c.faultVariable.type instanceof OElementVarType) { if (faultType instanceof OElementVarType && faultType.equals(c.faultVariable.type)) { // Don't eliminate } else if (faultType instanceof OMessageVarType && ((OMessageVarType)faultType).docLitType != null && ((OMessageVarType)faultType).docLitType.equals(c.faultVariable.type)) { // Don't eliminate } else { continue; // eliminate } } else { continue; // Eliminate } } // If we got to this point we did not eliminate this catch block. However, we don't just // use the first non-eliminated catch, we instead try to find the best match. if (bestMatch == null) { // Obviously something is better then nothing. bestMatch = c; } else { // Otherwise we prefer name and variable matches but prefer name-only matches to // variable-only matches. int existingScore = (bestMatch.faultName == null ? 0 : 2) + (bestMatch.faultVariable == null ? 0 : 1); int currentScore = (c.faultName == null ? 0 : 2) + (c.faultVariable == null ? 0 : 1); if (currentScore > existingScore) { bestMatch = c; } } } return bestMatch; } static final class EventHandlerInfo implements Serializable { private static final long serialVersionUID = -9046603073542446478L; final OBase o; final EventHandlerControlChannel cc; final ParentScopeChannel psc; final TerminationChannel tc; boolean terminateRequested; boolean stopRequested; EventHandlerInfo(OBase o, EventHandlerControlChannel cc, ParentScopeChannel psc, TerminationChannel tc) { this.o = o; this.cc = cc; this.psc = psc; this.tc = tc; } } }