/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and others contributors as indicated
* by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006,
* @author JBoss Inc.
*/
package org.jboss.soa.esb.actions.bpel;
import java.security.AccessController;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.internal.soa.esb.actions.bpel.ESBInvocationAdapter;
import org.jboss.soa.esb.actions.AbstractActionLifecycle;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.MessagePayloadProxy;
import org.jboss.soa.esb.message.format.MessageFactory;
import javax.naming.*;
import javax.security.auth.Subject;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.jboss.soa.bpel.runtime.engine.*;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
public class BPELInvoke extends AbstractActionLifecycle {
private static final String TOP_LEVEL_ELEMENT_NAME = "message";
protected static final String REQUEST_PART_NAME = "requestPartName";
protected static final String REQUEST_SAML_PART_NAME = "requestSAMLPartName";
protected static final String RESPONSE_PART_NAME = "responsePartName";
protected static final String SERVICE = "service";
protected static final String PORT = "port";
protected static final String OPERATION = "operation";
protected static final String ABORT_ON_FAULT = "abortOnFault";
protected static final String BODY_FAULT_CODE = "org.jboss.soa.esb.message.fault.detail.code";
protected static final String BODY_BPEL_FAULT_CODE = "org.jboss.soa.bpel.message.fault.detail.code";
protected ConfigTree _config;
private static BPELEngine _bpelEngine;
public BPELInvoke(ConfigTree config) {
_config = config;
}
/**
* This method returns a reference to the BPEL Engine.
*
* @return The BPEL engine
* @throws Exception Failed to obtain a reference to the
* BPEL engine
*/
protected static BPELEngine getBPELEngine() throws Exception {
// If BPEL engine not set, then locate from JNDI
if (_bpelEngine == null) {
InitialContext context=new InitialContext();
_bpelEngine = (BPELEngine)context.lookup(BPELEngine.Service);
}
return(_bpelEngine);
}
/**
* This method sets the reference to the BPEL engine.
*
* @param engine The BPEL engine
*/
protected void setBPELEngine(BPELEngine engine) {
_bpelEngine = engine;
}
/**
* This method processes the supplied message, and optionally
* returns a response message.
*
* @param message The request message
* @return The optional response message
* @throws Exception Failed to process the message
*/
public Message process(Message message) throws Exception {
Message ret=null;
// Check the operation and service details are available
if (_config.getAttribute(OPERATION) == null) {
throw new RuntimeException("Property '"+OPERATION+"' has not been specified");
}
if (_config.getAttribute(SERVICE) == null) {
throw new RuntimeException("Property '"+SERVICE+"' has not been specified");
}
BPELEngine engine=getBPELEngine();
if (engine == null) {
throw new RuntimeException("Failed to locate BPEL engine");
}
// Create MessagePayloadProxy
MessagePayloadProxy proxy=new MessagePayloadProxy(_config);
logger.debug("Request: "+message);
// Get default body value
Object value=proxy.getPayload(message);
boolean f_toText=(value instanceof String);
org.w3c.dom.Element mesgElem=createMessageElement(value);
// Invoke the service
String serviceName=_config.getAttribute(SERVICE);
javax.xml.namespace.QName qname=javax.xml.namespace.QName.valueOf(serviceName);
logger.debug("Invoking service: "+qname);
// Create invocationContext
ESBInvocationAdapter invocationContext =
new ESBInvocationAdapter(_config.getAttribute(OPERATION), qname, _config.getAttribute(PORT));
invocationContext.setRequestXML(mesgElem);
establishHeaderParts(invocationContext);
// invoke ODE
try {
engine.invoke(invocationContext);
} catch(Throwable t) {
// RIFTSAW-177 - prevent ODE specific exceptions being returned to ESB client where
// a ClassNotFoundException would be thrown
throw new Exception("BPEL invoke failed: "+t);
}
ret = handleResponse(invocationContext.getInvocationResult(),
invocationContext.getFaultName(), serviceName, proxy, f_toText);
return(ret);
}
protected Element createMessageElement(Object value) throws Exception {
String requestPartName=_config.getAttribute(REQUEST_PART_NAME);
org.w3c.dom.Element mesgElem=null;
Node node=null;
if (value instanceof String) {
// Convert to element
node = getNode((String)value);
} else if (value instanceof Node) {
node = (Node)value;
}
if (node == null) {
throw new RuntimeException("Failed to obtain DOM representation of message value");
}
logger.debug("Node type is: "+node.getNodeType()+" requestPartName="+requestPartName);
// If value is an element, and no request part name has been provided,
// then pass the DOM element through as is.
if (node.getNodeType() == Node.ELEMENT_NODE &&
requestPartName == null) {
mesgElem = (org.w3c.dom.Element)node;
} else if (requestPartName == null) {
// Not possible to pass anything other than an Element
// if the request partname is not defined
throw new RuntimeException("Non-element value can only be used in request if part name specified");
} else {
// Need to construct DOM element for message
mesgElem = createMessage();
// Transfer value into this message element
node = (Node)node.cloneNode(true);
Element partElem=mesgElem.getOwnerDocument().createElement(requestPartName);
mesgElem.appendChild(partElem);
node = (Node)mesgElem.getOwnerDocument().adoptNode(node);
partElem.appendChild(node);
}
return(mesgElem);
}
protected void establishHeaderParts(ESBInvocationAdapter invocationContext) {
String requestSAMLAssertionPartName=_config.getAttribute(REQUEST_SAML_PART_NAME);
if (requestSAMLAssertionPartName != null) {
// Check if PicketLink SAML creditial is available
Subject subject = Subject.getSubject(AccessController.getContext());
if (subject != null) {
org.picketlink.identity.federation.core.wstrust.SamlCredential samlCred=null;
for (Object cred : subject.getPublicCredentials()) {
if (cred instanceof org.picketlink.identity.federation.core.wstrust.SamlCredential) {
samlCred = (org.picketlink.identity.federation.core.wstrust.SamlCredential)cred;
break;
}
}
if (samlCred != null) {
try {
java.util.Map<String,Element> headerParts=
new java.util.HashMap<String, Element>();
// Wrap assertion element in a wsse:Security element
org.w3c.dom.Element assertion=samlCred.getAssertionAsElement();
org.w3c.dom.Document doc=assertion.getOwnerDocument();
org.w3c.dom.Element security=doc.createElementNS(
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
"Security");
security.appendChild(assertion.cloneNode(true));
//doc.appendChild(security);
headerParts.put(requestSAMLAssertionPartName, security);
invocationContext.setRequestHeaderParts(headerParts);
} catch(Throwable t) {
logger.error("Failed to add header parts", t);
}
}
}
}
}
protected Message handleResponse(org.w3c.dom.Element resp, javax.xml.namespace.QName faultName,
String serviceName, MessagePayloadProxy proxy, boolean f_toText)
throws Exception {
Message ret=null;
if (resp != null) {
ret = MessageFactory.getInstance().getMessage();
try {
String defpart=_config.getAttribute(RESPONSE_PART_NAME);
org.w3c.dom.Node respNode=null;
if (defpart != null) {
org.w3c.dom.NodeList nl=resp.getChildNodes();
// Find content in named part
for (int i=0; respNode == null && i < nl.getLength(); i++) {
if (nl.item(i).getNodeType() == Node.ELEMENT_NODE &&
nl.item(i).getNodeName().equals(defpart)) {
// Find element
org.w3c.dom.NodeList subnl=
((org.w3c.dom.Element)nl.item(i)).getChildNodes();
if (subnl.getLength() == 1) {
respNode = subnl.item(0);
} else {
// Scan for element
for (int j=0; respNode != null && j < subnl.getLength(); j++) {
if (subnl.item(j).getNodeType() == Node.ELEMENT_NODE) {
respNode = subnl.item(j);
}
}
}
}
}
} else {
respNode = resp;
}
Object respValue=respNode;
// Check if node needs to be converted to text
if ((f_toText || faultName != null) && respNode != null) {
respValue = getText(respNode);
}
if (respValue == null) {
logger.error("Unable to convert message part '"+(defpart==null?"<undefined>":defpart)+
"' into a response document");
} else {
//ret.getBody().add(respValue);
// Set the payload in the returned message
proxy.setPayload(ret, respValue);
// Check if fault name should be set
if (faultName != null) {
logger.debug("Fault '"+faultName+"' detected, throwing exception");
org.jboss.soa.esb.actions.ActionProcessingDetailFaultException faultException=
new org.jboss.soa.esb.actions.ActionProcessingDetailFaultException(faultName,
"Fault '"+faultName+"' occurred when calling service '"+serviceName+"'",
respValue.toString());
// In case we wanted to return the fault message as the message body
//faultException.getFaultMessage().getBody().add(respValue.toString());
// Record string representation of faultName, as currently if the client
// does not include the stax-api.jar in its list of endorsed libs (as the
// JBoss AS server does, then it will result in a class version issue when
// it tries to retrieve the ESB fault code (which is transferred as a QName).
// See RIFTSAW-110 for more details.
faultException.getFaultMessage().getBody().add(BODY_BPEL_FAULT_CODE, faultName.toString());
throw faultException;
}
}
} catch(org.jboss.soa.esb.actions.ActionProcessingDetailFaultException fault) {
// Determine whether to abort action pipeline with exception or return
// fault message for continued processing
if (_config.getAttribute(ABORT_ON_FAULT, "true").equalsIgnoreCase("true")) {
logger.debug("Rethrowing BPEL fault: "+fault);
throw fault;
} else {
ret = fault.getFaultMessage();
logger.debug("Returning fault as message: "+ret);
}
} catch(Exception e) {
logger.error("Failed to parse response '"+resp+"'", e);
}
}
logger.debug("Response: "+ret);
return(ret);
}
/**
* This class converts a DOM representation node to
* text.
*
* @param node The DOM node
* @return The text
* @throws Exception Failed to convert
*/
protected static String getText(Node node) throws Exception {
String ret=null;
try {
// Transform the DOM represent to text
java.io.ByteArrayOutputStream xmlstr=
new java.io.ByteArrayOutputStream();
DOMSource source=new DOMSource();
source.setNode(node);
StreamResult result=new StreamResult(xmlstr);
Transformer trans=
TransformerFactory.newInstance().newTransformer();
trans.transform(source, result);
xmlstr.close();
ret = new String(xmlstr.toByteArray());
if ((node instanceof org.w3c.dom.Document) == false) {
// Strip off any <?xml> header
int index=ret.indexOf("<?xml");
if (index != -1) {
index = ret.indexOf("<", 1);
if (index != -1) {
ret = ret.substring(index);
} else {
index = ret.indexOf("?>");
if (index != -1) {
index += 2;
// Remove any trailing whitespaces
// after XML header
while (index < ret.length() &&
Character.isWhitespace(ret.charAt(index))) {
index++;
}
ret = ret.substring(index);
}
}
}
}
} catch(Exception e) {
throw new Exception("Failed to transform " +
"DOM representation into text", e);
}
return(ret);
}
/**
* Create an empty message element.
*
* @return The new message
*/
protected org.w3c.dom.Element createMessage() {
org.w3c.dom.Element ret=null;
try {
DocumentBuilder builder=DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc=builder.newDocument();
ret = doc.createElement(TOP_LEVEL_ELEMENT_NAME);
} catch(Exception e) {
logger.error("Failed to create message", e);
}
return(ret);
}
/**
* This method converts the supplied text representation
* of an XML document into a DOM Node.
*
* @param text The text
* @return The node
* @throws Exception Failed to convert the text
*/
protected static Node getNode(String text) throws Exception {
Node ret=null;
try {
// Transform the text representation to DOM
DocumentBuilderFactory fact=DocumentBuilderFactory.newInstance();
fact.setNamespaceAware(true);
DocumentBuilder builder=fact.newDocumentBuilder();
// Check if XML document, and if not return as text node
if (text.trim().length() == 0 || text.charAt(0) != '<') {
org.w3c.dom.Document doc=builder.newDocument();
// Assume is text node
ret = doc.createTextNode(text);
} else {
java.io.InputStream xmlstr=
new java.io.ByteArrayInputStream(text.getBytes());
org.w3c.dom.Document doc=builder.parse(xmlstr);
xmlstr.close();
ret = doc.getDocumentElement();
}
} catch(Exception e) {
throw new Exception("Failed to transform text " +
"into DOM representation", e);
}
return(ret);
}
private final Log logger = LogFactory.getLog(getClass());
}