/** * Copyright (C) 2010 Orbeon, Inc. * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; either version * 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html */ package org.orbeon.oxf.processor; import org.apache.axis.client.Call; import org.apache.axis.client.Service; import org.apache.axis.message.*; import org.apache.axis.soap.SOAPConstants; import org.orbeon.dom.*; import org.orbeon.dom.Text; import org.orbeon.oxf.common.OXFException; import org.orbeon.oxf.common.ValidationException; import org.orbeon.oxf.pipeline.api.PipelineContext; import org.orbeon.oxf.util.XPath; import org.orbeon.oxf.xml.XMLReceiver; import org.orbeon.oxf.servicedirectory.ServiceDirectory; import org.orbeon.oxf.util.*; import org.orbeon.oxf.webapp.ProcessorService; import org.orbeon.oxf.xml.*; import org.orbeon.oxf.xml.dom4j.*; import org.orbeon.dom.saxon.DocumentWrapper; import org.orbeon.saxon.om.DocumentInfo; import org.w3c.dom.Node; import org.xml.sax.*; import javax.naming.Context; import javax.naming.InitialContext; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.*; public class DelegationProcessor extends ProcessorImpl { public static final String DELEGATION_NAMESPACE_URI = "http://orbeon.org/oxf/xml/delegation"; private static final String DEFAULT_SELECT_WEB_SERVICE_RPC = "/*:Envelope/*:Body/*[1]/text() | /*:Envelope/*:Body/*[1]/*"; private static final String DEFAULT_SELECT_WEB_SERVICE_DOCUMENT = "/*:Envelope/*:Body/text() | /*:Envelope/*:Body/*"; private static final String DEFAULT_SELECT_BUS = "/*:Envelope/*:Body/*"; public final String INPUT_INTERFACE = "interface"; public final String INPUT_CALL = "call"; public DelegationProcessor() { addInputInfo(new ProcessorInputOutputInfo(INPUT_INTERFACE, DELEGATION_NAMESPACE_URI)); addInputInfo(new ProcessorInputOutputInfo(INPUT_CALL)); addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_DATA)); } public ProcessorOutput createOutput(String name) { ProcessorOutput output = new ProcessorOutputImpl(DelegationProcessor.this, name) { public void readImpl(final PipelineContext context, final XMLReceiver xmlReceiver) { final List<ServiceDefinition> services = readServices(readInputAsOrbeonDom(context, INPUT_INTERFACE)); readInputAsSAX(context, INPUT_CALL, new ForwardingXMLReceiver(xmlReceiver) { Locator locator; String operationName; Integer operationTimeout; ServiceDefinition service; OperationDefinition operation; SAXStore parameters; private NamespaceContext namespaceContext = new NamespaceContext(); public void startElement(String uri, String localname, String qName, Attributes attributes) throws SAXException { namespaceContext.startElement(); if (uri.equals(DELEGATION_NAMESPACE_URI) && localname.equals("execute")) { // Find service service = null; String serviceId = attributes.getValue("service"); for ( Iterator<ServiceDefinition> i = services.iterator(); i.hasNext();) { ServiceDefinition candidateService = i.next(); if (candidateService.id.equals(serviceId)) { service = candidateService; break; } } if (service == null) throw new OXFException("Cannot find service with id \"" + serviceId + "\""); operation = null; operationName = attributes.getValue("operation"); // Find operation for Web service if (service.type == ServiceDefinition.WEB_SERVICE_TYPE && operationName != null) { for (Iterator<OperationDefinition> i = service.operations.iterator(); i.hasNext();) { OperationDefinition candidateOperation = i.next(); if (candidateOperation.name.equals(operationName)) { operation = candidateOperation; break; } } if (operation == null) throw new ValidationException("No operation '" + operationName + "' declared", new LocationData(locator)); } // Get timeout if any { final String timeoutAttribute = attributes.getValue("timeout"); if (timeoutAttribute != null) { try { operationTimeout = new Integer(timeoutAttribute); } catch (NumberFormatException e) { throw new ValidationException("Invalid timeout specified: " + timeoutAttribute, new LocationData(locator)); } if (operationTimeout.intValue() < 0) throw new ValidationException("Invalid timeout specified: " + operationTimeout, new LocationData(locator)); } } // Start building the parameters document { parameters = new SAXStore(); parameters.startDocument(); { // Handle namespaces in scope as of delegation:execute element for (Enumeration e = namespaceContext.getPrefixes(); e.hasMoreElements();) { final String namespacePrefix = (String) e.nextElement(); if (!namespacePrefix.startsWith("xml") && !namespacePrefix.equals("")) parameters.startPrefixMapping(namespacePrefix, namespaceContext.getURI(namespacePrefix)); } final String defaultNS = namespaceContext.getURI(""); if (defaultNS != null && defaultNS.length() > 0) super.startPrefixMapping("", defaultNS); } parameters.startElement("", "parameters", "parameters", SAXUtils.EMPTY_ATTRIBUTES); } } else { // Store values if we are inside a <delegation:execute> if (parameters == null) { super.startElement(uri, localname, qName, attributes); } else { parameters.startElement(uri, localname, qName, attributes); } } } public void endElement(String uri, String localname, String qName) { try { if (uri.equals(DELEGATION_NAMESPACE_URI)) { if (localname.equals("execute")) { // Complete parameters document { parameters.endElement("", "parameters", "parameters"); { // Handle namespaces in scope as of delegation:execute element for (Enumeration e = namespaceContext.getPrefixes(); e.hasMoreElements();) { final String namespacePrefix = (String) e.nextElement(); if (!namespacePrefix.startsWith("xml") && !namespacePrefix.equals("")) parameters.endPrefixMapping(namespacePrefix); } final String defaultNS = namespaceContext.getURI(""); if (defaultNS != null && defaultNS.length() > 0) super.endPrefixMapping(""); } parameters.endDocument(); } if (service.type == ServiceDefinition.WEB_SERVICE_TYPE || service.type == ServiceDefinition.BUS_SERVICE_TYPE) { // Call Web service final Service axisService = new Service(); final Call call = (Call) axisService.createCall(); if (operationTimeout != null) call.setTimeout(operationTimeout); // Get document containing the parameters final org.w3c.dom.Element parametersElement = getParametersDomDocument().getDocumentElement(); // Populate envelope final SOAPEnvelope requestEnvelope = service.soapVersion != null && service.soapVersion.equals("1.2") ? new SOAPEnvelope(SOAPConstants.SOAP12_CONSTANTS) : new SOAPEnvelope(); if (service.type == ServiceDefinition.BUS_SERVICE_TYPE || "document".equals(service.style)) { // Add elements to directly to body for (int i = 0; i < parametersElement.getChildNodes().getLength(); i++) { final Node child = parametersElement.getChildNodes().item(i); if (child instanceof org.w3c.dom.Element) requestEnvelope.addBodyElement(new SOAPBodyElement((org.w3c.dom.Element) child)); } } else { // Create body element with operation name, and add elements as children final SOAPBodyElement requestBody = new SOAPBodyElement(new PrefixedQName(operation.nsuri, operation.name, "m")); for (int i = 0; i < parametersElement.getChildNodes().getLength(); i++) { final Node child = parametersElement.getChildNodes().item(i); if (child instanceof org.w3c.dom.Element) { requestBody.addChild(new MessageElement((org.w3c.dom.Element) child)); } else if (child instanceof org.w3c.dom.Text) { requestBody.addTextNode(child.toString()); } else { throw new OXFException("Unsupported node type: " + child.getClass().getName()); } } requestEnvelope.addBodyElement(requestBody); } // Call service SOAPEnvelope resultEnvelope = null; if (service.type == ServiceDefinition.WEB_SERVICE_TYPE) { // Call Web service call.setTargetEndpointAddress(new URL(service.endpoint)); if (operation != null && operation.soapAction != null) { call.setUseSOAPAction(true); call.setSOAPActionURI(operation.soapAction); } call.setReturnClass(javax.xml.soap.SOAPMessage.class); resultEnvelope = call.invoke(requestEnvelope); } else { // Call bus service javax.jms.QueueConnection requestQueueConnection = null; javax.jms.QueueSession requestQueueSession = null; javax.jms.QueueSender queueSender = null; try { requestQueueConnection = JMSUtils.getQueueConnection(); requestQueueSession = requestQueueConnection.createQueueSession(false, javax.jms.Session.AUTO_ACKNOWLEDGE); queueSender = requestQueueSession.createSender ((javax.jms.Queue) new InitialContext().lookup(JMSUtils.JNDI_SERVICE_PREFIX + service.name)); javax.jms.ObjectMessage responseMessage = requestQueueSession.createObjectMessage(); responseMessage.setObject(requestEnvelope); // Send message if (ServiceDirectory.instance().getServiceByName(service.name).hasOutputs()) { // Response expected javax.jms.QueueConnection responseQueueConnection = null; javax.jms.QueueSession responseQueueSession = null; javax.jms.QueueReceiver queueReceiver = null; try { responseQueueConnection = JMSUtils.getQueueConnection(); responseQueueSession = responseQueueConnection.createQueueSession(false, javax.jms.Session.AUTO_ACKNOWLEDGE); javax.jms.Queue temporaryQueue = responseQueueSession.createTemporaryQueue(); queueReceiver = responseQueueSession.createReceiver(temporaryQueue); responseMessage.setJMSReplyTo(temporaryQueue); responseQueueConnection.start(); queueSender.send(responseMessage); javax.jms.Message message = queueReceiver.receive(); resultEnvelope = (SOAPEnvelope) ((javax.jms.ObjectMessage) message).getObject(); } finally{ if (queueReceiver != null) queueReceiver.close(); if (responseQueueSession != null) responseQueueSession.close(); if (responseQueueConnection != null) responseQueueConnection.close(); } } else { // No response expected queueSender.send(responseMessage); } } finally { if (queueSender != null) queueSender.close(); if (requestQueueSession != null) requestQueueSession.close(); if (requestQueueConnection != null) requestQueueConnection.close(); } } // Handle result if (resultEnvelope != null) { // Throw exception if a fault is returned and the user does not want the fault to be returned if (resultEnvelope.getBody().getFault() != null && !service.returnFault) { throw new OXFException("SOAP Fault. Request:\n" + TransformerUtils.domToString(requestEnvelope.getAsDocument()) + "\n\nResponse:\n" + TransformerUtils.domToString(resultEnvelope.getAsDocument())); } // Send body from result envelope final LocationSAXWriter locationSAXWriter = new LocationSAXWriter(); locationSAXWriter.setContentHandler(xmlReceiver); final Document resultEnvelopeDOM4j = new DelegationProcessorDOMReader().read(resultEnvelope.getAsDocument()); final String xpathString = operation != null && operation.select != null ? operation.select : service.type == ServiceDefinition.WEB_SERVICE_TYPE ? ("document".equals(service.style) ? DEFAULT_SELECT_WEB_SERVICE_DOCUMENT : DEFAULT_SELECT_WEB_SERVICE_RPC) : DEFAULT_SELECT_BUS; final DocumentInfo documentInfo = new DocumentWrapper(resultEnvelopeDOM4j, null, XPath.GlobalConfiguration()); final PooledXPathExpression expr = XPathCache.getXPathExpression( documentInfo.getConfiguration(), documentInfo, xpathString, operation != null && operation.select != null ? operation.selectNamespaceContext : null, getLocationData()); for (Iterator i = expr.evaluateToJavaReturnToPool().iterator(); i.hasNext();) { // Create document with node from SOAP envelope final Object result = i.next(); if (result instanceof Element) { locationSAXWriter.write((Element) result); } else if (result instanceof Document) { locationSAXWriter.write(((Document) result).getRootElement()); } else if (result instanceof Text) { locationSAXWriter.write((Text) result); } else { throw new OXFException("Unsupported result from select expression: '" + result.getClass() + "'"); } } } } else if (service.type == ServiceDefinition.STATELESS_EJB_TYPE || service.type == ServiceDefinition.JAVABEAN_TYPE) { // Get document containing the parameters final Document parametersDocument = getParametersDocument(); // Get parameter values and types final List<Class> parameterTypes = new ArrayList<Class>(); final List<Serializable> parameterValues = new ArrayList<Serializable>(); // Go through elements for (Iterator i = XPathUtils.selectNodeIterator(parametersDocument, "/*/*"); i.hasNext();) { final org.orbeon.dom.Element parameterElement = (org.orbeon.dom.Element) i.next(); final String parameterValue = parameterElement.getText(); // TODO: should pass true? final QName type = Dom4jUtils.extractAttributeValueQName(parameterElement, XMLConstants.XSI_TYPE_QNAME, false); if (type == null || XMLConstants.XS_STRING_QNAME.equals(type)) { parameterTypes.add(String.class); parameterValues.add(parameterValue); } else if (XMLConstants.XS_DOUBLE_QNAME.equals(type)) { parameterTypes.add(Double.TYPE); parameterValues.add(new Double(parameterValue)); } else if (XMLConstants.XS_BOOLEAN_QNAME.equals(type)) { parameterTypes.add(Boolean.TYPE); parameterValues.add(new Boolean(parameterValue)); } else if (XMLConstants.XS_INTEGER_QNAME.equals(type)) { parameterTypes.add(Integer.TYPE); parameterValues.add(new Integer(parameterValue)); } } if (service.type == ServiceDefinition.STATELESS_EJB_TYPE) { // Call EJB method final Context jndiContext = (Context) context.getAttribute(ProcessorService.JNDIContext()); if (jndiContext == null) throw new ValidationException("JNDI context not found in pipeline context.", new LocationData(locator)); final Object home = jndiContext.lookup(service.jndiName); if (home == null) throw new ValidationException("Home interface not found in JNDI context: " + service.jndiName, new LocationData(locator)); final Method create = home.getClass().getDeclaredMethod("create", new Class[]{}); final Object instance = create.invoke(home); final String result = callMethod(instance.getClass(), operationName, parameterTypes, instance, parameterValues); super.characters(result.toCharArray(), 0, result.length()); } else if (service.type == ServiceDefinition.JAVABEAN_TYPE) { // Call JavaBean method final Class clazz = Class.forName(service.clazz); final Object instance = clazz.newInstance(); final String result = callMethod(clazz, operationName, parameterTypes, instance, parameterValues); super.characters(result.toCharArray(), 0, result.length()); } } } } else { // Store values if we are inside a <delegation:execute> if (parameters == null) { super.endElement(uri, localname, qName); } else { parameters.endElement(uri, localname, qName); } } } catch (Exception e) { throw new OXFException(e); } namespaceContext.endElement(); } public void characters(char[] chars, int start, int length) throws SAXException { // Store values if we are inside a <delegation:execute> if (parameters == null) { super.characters(chars, start, length); } else { parameters.characters(chars, start, length); } } public void startPrefixMapping(String prefix, String uri) throws SAXException { namespaceContext.startPrefixMapping(prefix, uri); if (parameters == null) { super.startPrefixMapping(prefix, uri); } else { parameters.startPrefixMapping(prefix, uri); } } public void endPrefixMapping(String s) throws SAXException { if (parameters == null) { super.endPrefixMapping(s); } else { parameters.endPrefixMapping(s); } } public void setDocumentLocator(Locator locator) { this.locator = locator; } private Document getParametersDocument() throws SAXException { // Create Document final Document result = TransformerUtils.saxStoreToDom4jDocument(parameters); parameters = null; return result; } private org.w3c.dom.Document getParametersDomDocument() throws SAXException { // Create DOM document final org.w3c.dom.Document result = TransformerUtils.saxStoreToDomDocument(parameters); parameters = null; return result; } }); }; }; addOutput(name, output); return output; } /** * Calls a method on an object with the reflexion API. */ private String callMethod(Class clazz, String methodName, List<Class> parameterTypes, Object instance, List<Serializable> parameterValues) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { String result; Class[] parameterClasses = new Class[parameterTypes.size()]; parameterTypes.toArray(parameterClasses); Method method = clazz.getDeclaredMethod(methodName, parameterClasses); result = method.invoke(instance, parameterValues.toArray()).toString(); return result; } /** * Returns a list of AbstractService objects. */ private List<ServiceDefinition> readServices(Document interfaceDocument) { List<ServiceDefinition> services = new java.util.ArrayList<ServiceDefinition>(); for (java.util.Iterator i = interfaceDocument.getRootElement().elements("service").iterator(); i.hasNext();) { Element serviceElement = (Element) i.next(); // Create Service Definition ServiceDefinition service = new ServiceDefinition(); services.add(service); String serviceType = serviceElement.attributeValue("type"); service.type = "webservice".equals(serviceType) ? ServiceDefinition.WEB_SERVICE_TYPE : "stateless-ejb".equals(serviceType) ? ServiceDefinition.STATELESS_EJB_TYPE : "javabean".equals(serviceType) ? ServiceDefinition.JAVABEAN_TYPE : "bus".equals(serviceType) ? ServiceDefinition.BUS_SERVICE_TYPE: -1; service.id = serviceElement.attributeValue("id"); service.endpoint = serviceElement.attributeValue("endpoint"); service.jndiName = serviceElement.attributeValue("uri"); service.name = serviceElement.attributeValue("name"); service.clazz = serviceElement.attributeValue("class"); service.style = serviceElement.attributeValue("style"); service.soapVersion = serviceElement.attributeValue("soap-version"); service.returnFault = "true".equals(serviceElement.attributeValue("return-fault")); // Create operations for (java.util.Iterator j = XPathUtils.selectNodeIterator(serviceElement, "operation"); j.hasNext();) { Element operationElement = (Element) j.next(); OperationDefinition operation = new OperationDefinition(); operation.service = service; service.operations.add(operation); operation.name = operationElement.attributeValue("name"); operation.nsuri = operationElement.attributeValue("nsuri"); operation.soapAction = operationElement.attributeValue("soap-action"); operation.encodingStyle = operationElement.attributeValue("encodingStyle"); String select = operationElement.attributeValue("select"); if (select != null) { operation.select = select; operation.selectNamespaceContext = new NamespaceMapping(Dom4jUtils.getNamespaceContextNoDefault(operationElement)); } } } return services; } private static class ServiceDefinition { public final static int WEB_SERVICE_TYPE = 1; public final static int STATELESS_EJB_TYPE = 2; public final static int JAVABEAN_TYPE = 3; public final static int BUS_SERVICE_TYPE = 4; public int type; public String id; public String endpoint; public String jndiName; public String name; public String clazz; public String style; public String soapVersion; public boolean returnFault; public List<OperationDefinition> operations = new java.util.ArrayList<OperationDefinition>(); } private static class OperationDefinition { public ServiceDefinition service; public String name; public String nsuri; public String soapAction; public String encodingStyle; public String select; public NamespaceMapping selectNamespaceContext; } }