Package org.jboss.soa.esb.actions.soap.proxy

Source Code of org.jboss.soa.esb.actions.soap.proxy.SOAPProxy$ElementFinder$ElementFound

/*
* 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-2009
*/
package org.jboss.soa.esb.actions.soap.proxy;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.wsdl.Binding;
import javax.wsdl.BindingOperation;
import javax.wsdl.Definition;
import javax.wsdl.Input;
import javax.wsdl.Operation;
import javax.wsdl.Part;
import javax.wsdl.Port;
import javax.wsdl.PortType;
import javax.wsdl.Service;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.wsdl.extensions.soap.SOAPOperation;
import javax.wsdl.extensions.soap12.SOAP12Address;
import javax.wsdl.extensions.soap12.SOAP12Operation;
import javax.xml.namespace.QName;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.jboss.internal.soa.esb.publish.ContractProvider;
import org.jboss.internal.soa.esb.publish.ContractProviderLifecycleResource;
import org.jboss.internal.soa.esb.publish.Publish;
import org.jboss.soa.esb.Configurable;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.actions.AbstractActionPipelineProcessor;
import org.jboss.soa.esb.actions.ActionLifecycleException;
import org.jboss.soa.esb.actions.ActionProcessingException;
import org.jboss.soa.esb.actions.soap.WebServiceUtils;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.http.HttpRequest;
import org.jboss.soa.esb.lifecycle.LifecycleResourceException;
import org.jboss.soa.esb.listeners.ListenerTagNames;
import org.jboss.soa.esb.listeners.message.MessageDeliverException;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.MessagePayloadProxy;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

/**
* A SOAPProxy focuses on the consumption of an external WS endpoint (e.g. hosted on .NET, another external Java-based AS, LAMP)
* and re-publication of a WS endpoint via the ESB.  The ESB sits between the ultimate consumer/client (e.g. .NET WinForm
* application) and the ultimate producer (e.g. RoR-hosted WS).  The purpose of this intermediary is to provide an abstraction
* layer that solves the following problems:
* <ul>
* <li>Provides for more loose coupling between the client &amp; service; they are both completely unaware of each other.</li>
* <li>The client no longer has a direct connection to the remote service's hostname/IP address.</li>
* <li>The client will see modified WSDL that changes the inbound/outbound parameters. At a minimum, the WSDL must be tweaked so that the client is pointed to the ESB's exposed endpoint instead of the original, now proxied endpoint.</li>
* <li>A transformation of the SOAP envelope/body can be introduced via the ESB action chain both for the inbound request and outbound response.  (see XsltAction or SmooksAction)</li>
* <li>Service versioning is possible since clients can connect to 2 or more proxy endpoints on the ESB, each with its own WSDL and/or transformations and routing requirements, and the ESB will send the appropriate message to the appropriate endpoint and provide an ultimate response.</li>
* <li>Complex context-based routing via ContentBasedRouter.</li>
* </ul>
*
* Other mechanisms of doing this are inappropriate or inadequate:
* <ul>
* <li>SOAPClient is used to invoke external web services, not mirror them.</li>
* <li>SOAPProducer only executes internally-deployed JBoss WS services.</li>
* <li>HttpRouter requires too much by-hand configuration for easy WS proxying.</li>
* <li>EBWS strips out the SOAP Envelope and only passes along the body.</li>
* </ul>
*
* With a SOAPProxy action:
* <ul>
* <li>It is both a producer and consumer of web services.</li>
* <li>All that is required is a property pointing to the external wsdl.</li>
* <li>The wsdl can be automatically transformed via the optional wsdlTransform property.</li>
* <li>It is understood that SOAP is not tied to http.  The wsdl is read, and if an http transport is defined, that will be used.  Other transports (jms) will need future consideration.</li>
* <li>If using http, any of the HttpRouter properties can also optionally be applied to as overrides.</li>
* </ul>
*
* <i>Configuration Properties</i><br/>
* <ul>
* <li><b>wsdl</b> (required): The original wsdl {@link URL url} whose WS endpoint will get re-written and exposed as new wsdl from
* the ESB.  Depending upon the &lt;definitions&gt;&lt;service&gt;&lt;port&gt;&lt;soap:address location attribute's protocol (for
* example "http"), a protocol-specific {@link SOAPProxyTransport} implementation is used.  The value can reference a location based on
* five different schemes:<br/>
* <ul>
* <li><b>http://</b><br/>
* <ul>
* <li>Usage: When you want to pull wsdl from an external web server.</li>
* <li>Example: http://host/foo/HelloWorldWS?wsdl</li>
* </ul></li>
* <li><b>https://</b><br/>
* <ul>
* <li>Usage: When you want to pull wsdl from an external web server over SSL.</li>
* <li>Example: https://host/foo/HelloWorldWS?wsdl</li>
* </ul></li>
* <li><b>file://</b><br/>
* <ul>
* <li>Usage: When your wsdl is located on disk, accessible by the ESB JVM.</li>
* <li>Example: file:///tmp/HelloWorldWS.wsdl</li>
* <li><i>Note: <b>3</b> slashes in the example above. This is so we can specify an absolute vs. relative file path.</i></li>
* </ul></li>
* <li><b>classpath://</b><br/>
* <ul>
* <li>Usage: When you want to package your wsdl inside your ESB archive.</li>
* <li>Example: classpath:///META-INF/HelloWorldWS.wsdl</li>
* <li><i>Note: <b>3</b> slashes in the example above. This is so we can specify an absolute vs. relative classloader resource path.</i></li>
* </ul></li>
* <li><b>internal://</b><br/>
* <ul>
* <li>Usage: When the wsdl is being provided by a JBossWS web service <b>inside the same JVM</b> as this ESB deployment.</li>
* <li>Example: internal://HelloWorldWS</li>
* <li><i>Note: This scheme should be used instead of http or https in the usage described above. This is because on server restart, Tomcat may not yet be accepting incoming http/s requests, and thus cannot serve the wsdl.</i></li>
* </ul></li>
* </ul></li>
* <li><b>wsdlTransform</b> (optional): A &lt;smooks-resource-list&gt; xml config file allowing for flexible wsdl transformation.</li>
* <li><b>wsdlCharset</b> (optional): The character set the original wsdl (and imported resources) is encoded in, if not UTF-8.  It will be transformed to
* UTF-8 if it is a <a href="http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html">supported encoding</a> by the underlying platform.</li>
* <li><b>*</b> (optional): Any of the HttpRouter properties can be applied, if the wsdl specifies an http transport.</li>
* <li><b>endpointUrl</b> (optional): Example of an HttpRouter property, but useful when domain name matching is important for SSL certs.</li>
* <li><b>file</b> (optional): Apache Commons HTTPClient properties file, useful when proxying to a web service via SSL</li>
* <li><b>clientCredentialsRequired</b> (optional; default is "true"): Whether the Basic Auth credentials are required to come from the end
* client, or if the credentials specified inside <b>file</b> can be used instead.</li>
* <li><b>wsdlUseHttpClientProperties</b> (optional): if true then WSDL retrieval will use the same http-client-property as main webservice, otherwise will use wsdl-http-client-property entries.
* </ul>
* <b>*</b> For other possible configuration properties, see the specific {@link SOAPProxyTransport} implementations themselves.<p/>
*
* <i>Example of a straightforward scenario:</i><br/>
* <pre>
* &lt;action name="proxy" class="org.jboss.soa.esb.actions.soap.proxy.SOAPProxy"&gt;
* &nbsp;&nbsp;&nbsp;&nbsp;&lt;property name="wsdl" value="http://host/foo/HelloWorldWS?wsdl"/&gt;
* &lt;/action&gt;
* </pre>
* <i>Example of a basic auth + ssl scenario:</i><br/>
* <pre>
* &lt;action name="proxy" class="org.jboss.soa.esb.actions.soap.proxy.SOAPProxy"&gt;
* &nbsp;&nbsp;&nbsp;&nbsp;&lt;property name="wsdl" value="https://host/foo/HelloWorldWS?wsdl"/&gt;
* &nbsp;&nbsp;&nbsp;&nbsp;&lt;property name="endpointUrl" value="https://host/foo/HelloWorldWS"/&gt;
* &nbsp;&nbsp;&nbsp;&nbsp;&lt;property name="file" value="/META-INF/httpclient-8443.properties"/&gt;
* &nbsp;&nbsp;&nbsp;&nbsp;&lt;property name="clientCredentialsRequired" value="true"/&gt;
* &lt;/action&gt;
* </pre>
*
* @author dward at jboss.org
* @author <a href="mailto:mageshbk@jboss.com">Magesh Kumar B</a>
*/
@Publish(SOAPProxyWsdlContractPublisher.class)
public class SOAPProxy extends AbstractActionPipelineProcessor
{
 
  private static Logger logger = Logger.getLogger(SOAPProxy.class);
 
  private MessagePayloadProxy payloadProxy;
 
  private Map<String,QName> soapaction_to_binding = new HashMap<String,QName>();
  private Map<QName,QName> element_to_operation = new HashMap<QName,QName>();
  private Map<QName,QName> operation_to_binding = new HashMap<QName,QName>();
  private Map<QName,SOAPProxyTransport> binding_to_transport = new HashMap<QName,SOAPProxyTransport>();
 
  @SuppressWarnings({ "unchecked", "rawtypes" })
  public SOAPProxy(ConfigTree config) throws ConfigurationException
  {
    payloadProxy = new MessagePayloadProxy(config);
    initialiseContractPublisher(config);
    SOAPProxyWsdlLoader wsdl_loader = SOAPProxyWsdlLoader.newLoader(config);
    Definition wsdl_def;
    try
    {
      wsdl_loader.load(false);
      wsdl_def = WebServiceUtils.readWSDL(wsdl_loader.getURL());
    }
    catch (Exception ioe)
    {
      throw new ConfigurationException(ioe);
    }
    finally
    {
      wsdl_loader.cleanup();
    }
    Collection<Binding> bindings = wsdl_def.getBindings().values();
    for ( Binding wsdl_bind : bindings )
    {
      PortType wsdl_portType = wsdl_bind.getPortType();
      if (wsdl_portType != null)
      {
        Collection<Operation> operations = wsdl_portType.getOperations();
        for (Operation wsdl_portType_oper : operations)
        {
          Input wsdl_portType_oper_input = wsdl_portType_oper.getInput();
          if (wsdl_portType_oper_input != null)
          {
            javax.wsdl.Message wsdl_portType_oper_input_msg = wsdl_portType_oper_input.getMessage();
            if (wsdl_portType_oper_input_msg != null)
            {
              Collection wsdl_portType_oper_input_msg_parts = wsdl_portType_oper_input_msg.getParts().values();
              if (wsdl_portType_oper_input_msg_parts.size() != 1)
              {
                // size should only be 1 for document
                continue;
              }
              Part wsdl_portType_oper_input_msg_part = (Part)wsdl_portType_oper_input_msg_parts.iterator().next();
              QName element = wsdl_portType_oper_input_msg_part.getElementName();
              if ( element != null && !element_to_operation.containsKey(element) )
              {
                QName operation = new QName(wsdl_portType.getQName().getNamespaceURI(), wsdl_portType_oper.getName());
                // no need for a duplicate mapping
                if ( operation != null && !element.equals(operation) )
                {
                  element_to_operation.put(element, operation);
                  if ( logger.isInfoEnabled() )
                  {
                    logger.info("mapped element [" + element + "] to operation [" + operation + "]");
                  }
                }
              }
            }
          }
        }
      }
      List<BindingOperation> operations = wsdl_bind.getBindingOperations();
      for ( BindingOperation wsdl_bind_oper : operations )
      {
        QName binding = wsdl_bind.getQName();
        if (binding != null)
        {
          // http://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383528
          // http://www.ws-i.org/Profiles/BasicProfile-1.1.html#SOAPAction_HTTP_Header
          String soapaction = null;
          List<ExtensibilityElement> extList = wsdl_bind_oper.getExtensibilityElements();
          for (ExtensibilityElement extElement : extList)
          {
            if (extElement instanceof SOAPOperation)
            {
              SOAPOperation soapOp = (SOAPOperation)extElement;
              soapaction = soapOp.getSoapActionURI();
            }
            else if (extElement instanceof SOAP12Operation)
            {
              SOAP12Operation soapOp = (SOAP12Operation)extElement;
              soapaction = soapOp.getSoapActionURI();
            }
          }
          if (soapaction != null)
          {
            if ( !soapaction.startsWith("\"") && !soapaction.endsWith("\"") )
            {
              soapaction = '"' + soapaction + '"';
            }
            if ( !soapaction_to_binding.containsKey(soapaction) )
            {
              soapaction_to_binding.put(soapaction, binding);
              if ( logger.isInfoEnabled() )
              {
                logger.info("mapped soapaction [" + soapaction + "] to binding [" + binding + "]");
              }
            }
          }
          QName operation = new QName(wsdl_portType.getQName().getNamespaceURI(), wsdl_bind_oper.getOperation().getName());
          if ( operation != null && !operation_to_binding.containsKey(operation) )
          {
            operation_to_binding.put(operation, binding);
            if ( logger.isInfoEnabled() )
            {
              logger.info("mapped operation [" + operation + "] to binding [" + binding + "]");
            }
          }
        }
      }
    }
    Collection<Service> services = wsdl_def.getServices().values();
    for ( Service wsdl_svc : services )
    {
      Collection<Port> ports = wsdl_svc.getPorts().values();
      for ( Port wsdl_end : ports )
      {
        QName binding = wsdl_end.getBinding().getQName();
        SOAPProxyTransport transport = null;
        String endpointAddress = getSOAPAddress(wsdl_end);
        if ( endpointAddress.toLowerCase().startsWith("http") )
        {
          transport = new HttpSOAPProxyTransport(config, payloadProxy, endpointAddress);
        }
        // else if jms ...
        if (transport != null)
        {
          if ( !binding_to_transport.containsKey(binding) )
          {
            binding_to_transport.put(binding, transport);
            if ( logger.isInfoEnabled() )
            {
              logger.info("mapped binding [" + binding + "] to transport [" + transport.getClass().getName() + "] with endpoint address: [" + transport.getEndpointAddress() + "]");
            }
          }
        }
        else
        {
          if ( logger.isEnabledFor(Level.WARN) )
          {
            logger.warn("could not map binding [" + binding + "] to transport with endpoint address: [" + endpointAddress + "]");
          }
        }
      }
    }
  }
 
  private void initialiseContractPublisher(final ConfigTree config)
    throws ConfigurationException
  {
    final ConfigTree parent = config.getParent();
    if (parent != null)
    {
      final String category = parent.getAttribute(ListenerTagNames.SERVICE_CATEGORY_NAME_TAG);
      final String name = parent.getAttribute(ListenerTagNames.SERVICE_NAME_TAG);
      if ((category != null) && (name != null))
      {
        final ContractProvider provider ;
        try
        {
          provider = ContractProviderLifecycleResource.getContractProvider(category, name);
        }
        catch (final LifecycleResourceException lre)
        {
          throw new ConfigurationException("Unexpected exception querying contract provider", lre);
        }
       
        if ((provider != null) && (provider instanceof Configurable))
        {
          final Configurable configurable = Configurable.class.cast(provider) ;
          configurable.setConfiguration(config);
        }
      }
    }
  }
 
  public void initialise() throws ActionLifecycleException
  {
    for ( SOAPProxyTransport transport : binding_to_transport.values() )
    {
      transport.initialise();
    }
  }
 
  public Message process(Message message) throws ActionProcessingException
  {
    HttpRequest request = HttpRequest.getRequest(message);
    String soapaction = (request != null) ? request.getHeaderValue("soapaction") : null;
    if (soapaction == null)
    {
      soapaction = (String)message.getProperties().getProperty("soapaction");
    }
    QName element = null;
    QName operation = null;
    QName binding = (soapaction != null) ? soapaction_to_binding.get(soapaction) : null;
    if (binding == null)
    {
      if ( logger.isEnabledFor(Level.WARN) )
      {
        logger.warn("null binding for soapaction [" + soapaction + "]; parsing envelope to find element or operation...");
      }
      element = findElement(message);
      operation = element;
      if (element != null)
      {
        if ( element_to_operation.containsKey(element) )
        {
          operation = element_to_operation.get(element);
        }
        binding = (operation != null) ? operation_to_binding.get(operation) : null;
      }
    }
    if ( binding == null  && logger.isEnabledFor(Level.ERROR) )
    {
      logger.error("null binding for element [" + element + "] or operation [" + operation + "] in addition to soapaction [" + soapaction + "]");
    }
    SOAPProxyTransport transport = (binding != null) ? binding_to_transport.get(binding) : null;
    if (transport == null)
    {
      throw new ActionProcessingException("null transport for soapaction [" + soapaction + "], element [" + element + "], operation [" + operation + "], binding [" + binding + "]");
    }
    if ( logger.isDebugEnabled() )
    {
      logger.debug("using transport [" + transport.getClass().getName() + "] with endpoint address: [" + transport.getEndpointAddress() + "] for binding [" + binding + "]");
    }
    return transport.process(message);
  }
 
  public void destroy() throws ActionLifecycleException
  {
    for ( SOAPProxyTransport transport : binding_to_transport.values() )
    {
      transport.destroy();
    }
  }

  /** Get the endpoint address from the ports extensible element
  */
  private String getSOAPAddress(Port srcPort) throws ConfigurationException
  {
    String soapAddress = "dummy";

    @SuppressWarnings("unchecked")
    List<ExtensibilityElement> elements = srcPort.getExtensibilityElements();
    for ( ExtensibilityElement extElement : elements )
    {
      QName elementType = extElement.getElementType();

      if ( extElement instanceof SOAPAddress )
      {
        SOAPAddress  addr = (SOAPAddress)extElement;
        soapAddress  = addr.getLocationURI();
        break;
      }
      else if ( extElement instanceof  SOAP12Address )
      {
        SOAP12Address addr = (SOAP12Address)extElement;
        soapAddress  = addr.getLocationURI();
        break;
      }
      else if ("address".equals(elementType.getLocalPart()))
      {
        logger.warn("Unprocessed extension element: " + elementType);
      }
    }

    if (soapAddress == null)
      throw new ConfigurationException("Cannot obtain SOAP address");

    return soapAddress;
  }

  // This is a best guess (and potentially expensive)!  See logger.warn(String) warning in process(Message) above.
  private QName findElement(Message message) throws ActionProcessingException
  {
    Object payload;
    try
    {
      payload = payloadProxy.getPayload(message);
    }
    catch (MessageDeliverException mde)
    {
      throw new ActionProcessingException(mde);
    }
    InputSource is = null;
    if (payload instanceof byte[])
    {
      byte[] byte_payload = (byte[])payload;
      if (byte_payload.length == 0)
      {
        throw new ActionProcessingException("message contains zero-length byte[] payload");
      }
      is = new InputSource( new ByteArrayInputStream(byte_payload) );
    }
    else if (payload instanceof String)
    {
      String string_payload = (String)payload;
      if (string_payload.length() == 0)
      {
        throw new ActionProcessingException("message contains zero-length String payload");
      }
      is = new InputSource( new StringReader(string_payload) );
    }
    else
    {
      throw new ActionProcessingException( "unsupported payload type: " + payload.getClass().getName() );
    }
    QName element = null;
    ContentHandler ch = new ElementFinder();
    try
    {
      XMLReader xr = XMLReaderFactory.createXMLReader();
      xr.setContentHandler(ch);
      xr.parse(is);
    }
    catch (SAXException saxe)
    {
      throw new ActionProcessingException(saxe);
    }
    catch (IOException ioe)
    {
      throw new ActionProcessingException(ioe);
    }
    catch (ElementFinder.ElementFound ef)
    {
      element = ef.element;
    }
    return element;
  }

  private static class ElementFinder extends DefaultHandler
  {
   
    private boolean insideEnvelope = false;
    private boolean insideBody = false;
   
    @Override
    public void startElement(String uri, String localName, String qName, Attributes atts)
    {
      if ( localName.equals("Envelope") )
      {
        insideEnvelope = true;
      }
      else if ( localName.equals("Body") )
      {
        insideBody = true;
      }
      else if (insideEnvelope && insideBody)
      {
        // stop parsing as soon as possible!
        throw new ElementFound( new QName(uri, localName) );
      }
    }
   
    @SuppressWarnings("serial")
    private static class ElementFound extends RuntimeException
    {
     
      private QName element;
     
      private ElementFound(QName element)
      {
        this.element = element;
      }
     
    }
   
  }

}
TOP

Related Classes of org.jboss.soa.esb.actions.soap.proxy.SOAPProxy$ElementFinder$ElementFound

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.