/* * Copyright (c) 2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * Licensed 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.wso2.carbon.bpel.core.ode.integration; import org.apache.axiom.om.OMElement; import org.apache.axiom.soap.SOAPEnvelope; import org.apache.axiom.soap.SOAPFault; import org.apache.axis2.AxisFault; import org.apache.axis2.context.MessageContext; import org.apache.axis2.description.AxisService; import org.apache.axis2.description.TwoChannelAxisOperation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ode.bpel.epr.EndpointFactory; import org.apache.ode.bpel.epr.MutableEndpoint; import org.apache.ode.bpel.epr.WSAEndpoint; import org.apache.ode.bpel.iapi.BpelServer; import org.apache.ode.bpel.iapi.EndpointReference; import org.apache.ode.bpel.iapi.Message; import org.apache.ode.bpel.iapi.MyRoleMessageExchange; import org.apache.ode.bpel.iapi.ProcessConf; import org.apache.ode.il.OMUtils; import org.apache.ode.utils.DOMUtils; import org.apache.ode.utils.GUID; import org.apache.ode.utils.Namespaces; import org.apache.ode.utils.Properties; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.wso2.carbon.bpel.core.ode.integration.axis2.WSDLAwareMessage; import org.wso2.carbon.bpel.core.ode.integration.utils.SOAPUtils; import org.wso2.carbon.context.PrivilegedCarbonContext; import java.util.Map; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import javax.transaction.TransactionManager; import javax.wsdl.Definition; import javax.wsdl.Port; import javax.wsdl.Service; import javax.wsdl.extensions.UnknownExtensibilityElement; import javax.wsdl.extensions.http.HTTPAddress; import javax.wsdl.extensions.soap.SOAPAddress; import javax.wsdl.extensions.soap12.SOAP12Address; import javax.xml.namespace.QName; /** * This will act as a proxy between BPEL Process deployed in ODE Engine and Axis Service * which expose the BPEL Process to the external world. * Once the request comes to the service published by BPEL Process, request will handover * to the BPELProcessProxy via BPELMessageReceiver. */ public class BPELProcessProxy { private static final Log log = LogFactory.getLog(BPELProcessProxy.class); private BpelServer odeBpelServer; private ProcessConf processConfiguration; private TransactionManager transactionManager; // WSDL Definition of the BPEL Process private Definition wsdlDefinition; // QName of the service published by the process private QName serviceName; // Name of the port from WSDL service definition private String port; private WSAEndpoint serviceReference; private AxisService axisService; public BPELProcessProxy(final ProcessConf processConf, final BPELServerImpl bpelServer, final QName serviceName, final String port) { this.processConfiguration = processConf; this.odeBpelServer = bpelServer.getODEBPELServer(); this.serviceName = serviceName; this.port = port; this.transactionManager = bpelServer.getTransactionManager(); this.wsdlDefinition = processConfiguration.getDefinitionForService(serviceName); this.serviceReference = EndpointFactory.convertToWSA(createServiceRef( genEPRfromWSDL(this.wsdlDefinition, serviceName, port))); } public final ProcessConf getProcessConfiguration() { return processConfiguration; } public final Definition getWsdlDefinition() { return wsdlDefinition; } public final QName getServiceName() { return serviceName; } public final String getPort() { return port; } public final AxisService getAxisService() { return axisService; } public final void setAxisService(final AxisService axisService) { this.axisService = axisService; } public final WSAEndpoint getServiceReference() { return serviceReference; } public final void onAxisServiceInvoke(final BPELMessageContext bpelMessageContext) throws AxisFault { boolean success = true; MyRoleMessageExchange odeMessageExchange = null; Future responseFuture = null; // Used to handle exception loosing in when exception occurred // inside finally block. Exception cachedException = null; try { transactionManager.begin(); if (log.isDebugEnabled()) { log.debug("Starting Transaction."); } odeBpelServer.acquireTransactionLocks(); odeMessageExchange = createMessageExchange(bpelMessageContext.getInMessageContext()); if (odeMessageExchange.getOperation() != null) { responseFuture = invokeBPELProcessThroughODEMessageExchange( odeMessageExchange, bpelMessageContext); success = commitODEMessageExchange(odeMessageExchange); } else { success = false; } } catch (Exception e) { cachedException = e; success = false; handleExceptionAtODEInvocation(e); } finally { if (!success) { releaseODEMessageExchangeAndRollbackTransaction( odeMessageExchange, cachedException, success); } } if (odeMessageExchange.getOperation().getOutput() != null) { waitForTheResponse(responseFuture, odeMessageExchange); if (bpelMessageContext.getOutMessageContext() != null) { if (log.isDebugEnabled()) { log.debug("Handling response for MEX " + odeMessageExchange); } setOutMessageContextSOAPEnvelope(bpelMessageContext); boolean commit = false; beginTransactionForTheResponsePath(); try { // Refreshing the message exchange odeMessageExchange = (MyRoleMessageExchange) odeBpelServer.getEngine() .getMessageExchange(odeMessageExchange.getMessageExchangeId()); onResponse(bpelMessageContext, odeMessageExchange, bpelMessageContext.getOutMessageContext()); // Everything went well. So we can commit the transaction now. commit = true; } catch (AxisFault af) { cachedException = af; log.warn("MEX produced a fault " + odeMessageExchange, af); commit = true; throw af; } catch (Exception e) { cachedException = e; log.error("Error processing response for MEX " + odeMessageExchange, e); throw new BPELFault("An exception occurred when invoking ODE.", e); } finally { try { odeMessageExchange.release(commit); } finally { if (commit) { commitTransactionForTheResponsePath(cachedException); } else { rollbackTransactionForTheResponsePath(cachedException); } } } } if (!success) { throw new BPELFault("Message was either un-routable or timed out!"); } } } private void beginTransactionForTheResponsePath() throws BPELFault { try { if (log.isDebugEnabled()) { log.debug("Starting transaction."); } transactionManager.begin(); } catch (Exception ex) { String errorMessage = "Failed to start transaction!"; log.error(errorMessage, ex); throw new BPELFault(errorMessage, ex); } } private void commitTransactionForTheResponsePath(final Exception cachedException) throws BPELFault { try { if (log.isDebugEnabled()) { log.debug("Comitting transaction."); } transactionManager.commit(); } catch (Exception e) { String errorMessage = "Commiting Response Path Transaction Failed."; e.initCause(cachedException); log.error(errorMessage, e); throw new BPELFault(errorMessage, e); } } private void rollbackTransactionForTheResponsePath(final Exception cachedException) throws BPELFault { try { transactionManager.rollback(); } catch (Exception e) { e.initCause(cachedException); throw new BPELFault("Rollback failed!", e); } } private void setOutMessageContextSOAPEnvelope(final BPELMessageContext bpelMessageContext) throws AxisFault { SOAPEnvelope envelope = bpelMessageContext.getSoapFactoryForCurrentMessageFlow() .getDefaultEnvelope(); bpelMessageContext.getOutMessageContext().setEnvelope(envelope); } private MyRoleMessageExchange createMessageExchange(final MessageContext inMessageContext) { Integer tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); MyRoleMessageExchange messageExchange; String messageId = new GUID().toString(); messageExchange = odeBpelServer.getEngine().createMessageExchange("" + messageId, serviceName, inMessageContext.getAxisOperation().getName().getLocalPart(), null, tenantId.toString()); if (log.isDebugEnabled()) { log.debug("ODE routed to portType " + messageExchange.getPortType() + " operation " + messageExchange.getOperation() + " from service " + serviceName); } messageExchange.setProperty("isTwoWay", Boolean.toString( inMessageContext.getAxisOperation() instanceof TwoChannelAxisOperation)); return messageExchange; } private Message createInputMessageToODE( final BPELMessageContext bpelMessageContext, final MyRoleMessageExchange messageExchange) throws AxisFault { // Preparing message to send to ODE Message odeRequest = messageExchange.createMessage( messageExchange.getOperation().getInput().getMessage().getQName()); fillODEMessage(odeRequest, bpelMessageContext.getRequestMessage()); return odeRequest; } private Future invokeBPELProcessThroughODEMessageExchange( final MyRoleMessageExchange odeMessageExchange, final BPELMessageContext bpelMessageContext) throws AxisFault { Message request = createInputMessageToODE(bpelMessageContext, odeMessageExchange); if (log.isDebugEnabled()) { log.debug("Invoking ODE using MEX " + odeMessageExchange); log.debug("Message content: " + DOMUtils.domToString(request.getMessage())); } return odeMessageExchange.invoke(request, bpelMessageContext.getAttachmentIDList()); } private boolean commitODEMessageExchange(final MyRoleMessageExchange odeMessageExchange) { try { if (log.isDebugEnabled()) { log.debug("Committing ODE MEX " + odeMessageExchange); log.debug("Committing transaction."); } transactionManager.commit(); } catch (Exception e) { log.error("Commit failed", e); return false; } return true; } private void handleExceptionAtODEInvocation(final Exception e) throws BPELFault { String errorMessage = "Exception occurred while invoking ODE"; log.error(errorMessage, e); String message = e.getMessage(); if (message == null) { message = errorMessage; } throw new BPELFault(message, e); } public final void releaseODEMessageExchangeAndRollbackTransaction( final MyRoleMessageExchange odeMessageExchange, final Exception cachedException, final boolean isProcessInvokeSuccess) throws BPELFault { try { if (odeMessageExchange != null) { odeMessageExchange.release(isProcessInvokeSuccess); } } finally { try { transactionManager.rollback(); } catch (Exception e) { e.initCause(cachedException); throw new BPELFault("Rollback failed", e); } } } private void waitForTheResponse( final Future responseFuture, final MyRoleMessageExchange odeMessageExchange) throws BPELFault { try { responseFuture.get(getTimeout(), TimeUnit.MILLISECONDS); } catch (Exception e) { String errorMsg = "Timeout or execution error when waiting for response to MEX " + odeMessageExchange + " " + e.toString(); log.error(errorMsg, e); throw new BPELFault(errorMsg, e); } } /** * Fill the ODE's message object with the WSDL message parts processed at the message receiver. * * @param odeRequest Request message pass in to BPEL engine * @param inComingMessage incoming request from message receiver */ private void fillODEMessage(final Message odeRequest, final WSDLAwareMessage inComingMessage) { Map<String, OMElement> bodyParts = inComingMessage.getBodyParts(); Map<String, OMElement> headerParts = inComingMessage.getHeaderParts(); for (Map.Entry<String, OMElement> bodyPart : bodyParts.entrySet()) { if (inComingMessage.isRPC()) { /* In RPC Style messages parent element's name is equal to the part name.*/ odeRequest.setPart(bodyPart.getKey(), OMUtils.toDOM(bodyPart.getValue())); } else { /* in document style there isn't any relationship between part name and element * names. therefore we wrap document style message parts with a element which * has part name as it's local name. */ Document doc = DOMUtils.newDocument(); Element destPart = doc.createElementNS(null, bodyPart.getKey()); destPart.appendChild(doc.importNode(OMUtils.toDOM(bodyPart.getValue()), true)); odeRequest.setPart(bodyPart.getKey(), destPart); } } for (Map.Entry<String, OMElement> headerPart : headerParts.entrySet()) { odeRequest.setHeaderPart(headerPart.getKey(), OMUtils.toDOM(headerPart.getValue())); } } private void onResponse(final BPELMessageContext bpelMessageContext, final MyRoleMessageExchange mex, final MessageContext msgContext) throws AxisFault { switch (mex.getStatus()) { case FAULT: if (log.isDebugEnabled()) { log.debug("Fault response message: " + mex.getFault()); } SOAPFault fault = SOAPUtils.createSoapFault(bpelMessageContext, mex); msgContext.getEnvelope().getBody().addFault(fault); if (log.isDebugEnabled()) { log.debug("Returning fault: " + msgContext.getEnvelope().toString()); } break; case ASYNC: case RESPONSE: SOAPUtils.createSOAPResponse(bpelMessageContext, mex); if (log.isDebugEnabled()) { log.debug("Response message " + msgContext.getEnvelope()); } //writeHeader(msgContext, mex); break; case FAILURE: throw new BPELFault("Message exchange failure"); default: throw new BPELFault("Received ODE message exchange in unexpected state: " + mex.getStatus()); } } /** * do not store the value so it can be dynamically updated. * * @return Message Exchange Timeout */ private long getTimeout() { String timeout = processConfiguration.getEndpointProperties(serviceReference) .get(Properties.PROP_MEX_TIMEOUT); if (timeout != null) { try { return Long.parseLong(timeout); } catch (NumberFormatException e) { log.warn("Mal-formatted Property: [" + Properties.PROP_MEX_TIMEOUT + "=" + timeout + "] Default value (" + Properties.DEFAULT_MEX_TIMEOUT + ") will be used"); } } // Default value is 120000 milliseconds and if specified in bps.xml configuration file, that value will be // returned. return BPELServerImpl.getInstance().getBpelServerConfiguration().getMexTimeOut(); } /** * Get the EPR of this service from the WSDL. * * @param wsdlDef WSDL Definition * @param serviceName service name * @param portName port name * @return XML representation of the EPR */ public static Element genEPRfromWSDL( final Definition wsdlDef, final QName serviceName, final String portName) { Service serviceDef = wsdlDef.getService(serviceName); if (serviceDef != null) { Port portDef = serviceDef.getPort(portName); if (portDef != null) { Document doc = DOMUtils.newDocument(); Element service = doc.createElementNS(Namespaces.WSDL_11, "service"); service.setAttribute("name", serviceDef.getQName().getLocalPart()); service.setAttribute("targetNamespace", serviceDef.getQName().getNamespaceURI()); Element port = doc.createElementNS(Namespaces.WSDL_11, "port"); service.appendChild(port); port.setAttribute("name", portDef.getName()); port.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:bindns", portDef.getBinding().getQName().getNamespaceURI()); port.setAttribute("bindns:binding", portDef.getName()); for (Object extElmt : portDef.getExtensibilityElements()) { if (extElmt instanceof SOAPAddress) { Element soapAddr = doc.createElementNS(Namespaces.SOAP_NS, "address"); port.appendChild(soapAddr); soapAddr.setAttribute("location", ((SOAPAddress) extElmt).getLocationURI()); } else if (extElmt instanceof HTTPAddress) { Element httpAddr = doc.createElementNS(Namespaces.HTTP_NS, "address"); port.appendChild(httpAddr); httpAddr.setAttribute("location", ((HTTPAddress) extElmt).getLocationURI()); } else if (extElmt instanceof SOAP12Address) { Element soap12Addr = doc.createElementNS(Namespaces.SOAP12_NS, "address"); port.appendChild(soap12Addr); soap12Addr.setAttribute("location", ((SOAP12Address) extElmt).getLocationURI()); } else { port.appendChild( doc.importNode(((UnknownExtensibilityElement) extElmt).getElement(), true)); } } return service; } } return null; } /** * Create-and-copy a service-ref element. * * @param elmt Service Reference element * @return wrapped element */ public static MutableEndpoint createServiceRef(final Element elmt) { Document doc = DOMUtils.newDocument(); QName elQName = new QName(elmt.getNamespaceURI(), elmt.getLocalName()); // If we get a service-ref, just copy it, otherwise make a service-ref // wrapper if (!EndpointReference.SERVICE_REF_QNAME.equals(elQName)) { Element serviceref = doc.createElementNS( EndpointReference.SERVICE_REF_QNAME.getNamespaceURI(), EndpointReference.SERVICE_REF_QNAME.getLocalPart()); serviceref.appendChild(doc.importNode(elmt, true)); doc.appendChild(serviceref); } else { doc.appendChild(doc.importNode(elmt, true)); } return EndpointFactory.createEndpoint(doc.getDocumentElement()); } }