/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This 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 software 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.internal.soa.esb.webservice;
import java.io.ByteArrayInputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.soap.Detail;
import javax.xml.soap.Node;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPMessage;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.ws.Provider;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.addressing.AttributedURI;
import javax.xml.ws.addressing.Relationship;
import org.apache.log4j.Logger;
import org.jboss.internal.soa.esb.util.XMLHelper;
import org.jboss.internal.soa.esb.webservice.addressing.AddressingConstants;
import org.jboss.internal.soa.esb.webservice.addressing.MAP;
import org.jboss.internal.soa.esb.webservice.addressing.MAPBuilder;
import org.jboss.internal.soa.esb.webservice.addressing.MAPBuilderFactory;
import org.jboss.internal.soa.esb.webservice.addressing.MAPRelatesTo;
import org.jboss.soa.esb.client.ServiceInvoker;
import org.jboss.soa.esb.common.Environment;
import org.jboss.soa.esb.common.ModulePropertyManager;
import org.jboss.soa.esb.couriers.FaultMessageException;
import org.jboss.soa.esb.lifecycle.LifecycleResourceManager;
import org.jboss.soa.esb.listeners.message.MessageDeliverException;
import org.jboss.soa.esb.message.Body;
import org.jboss.soa.esb.message.Fault;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.MessagePayloadProxy;
import org.jboss.soa.esb.message.Properties;
import org.jboss.soa.esb.message.format.MessageFactory;
import org.jboss.soa.esb.services.security.SecurityServiceException;
import org.jboss.soa.esb.services.security.auth.AuthenticationRequest;
import org.jboss.soa.esb.services.security.auth.ExtractionException;
import org.jboss.soa.esb.services.security.auth.ExtractorUtil;
import org.jboss.soa.esb.services.security.auth.SecurityInfoExtractor;
import org.jboss.soa.esb.services.security.auth.ws.SamlSoapAssertionExtractor;
import org.jboss.soa.esb.services.security.auth.ws.WSSecuritySoapExtractor;
import org.w3c.dom.Document;
import com.arjuna.common.util.propertyservice.PropertyManager;
/**
* This is the abstract base class for a SOAP messages
* @author kevin
* @author <a href="mailto:mageshbk@jboss.com">Magesh Kumar B</a>
*/
public abstract class BaseWebService implements Provider<SOAPMessage>
{
private static final QName SERVER_FAULT_QN = new QName("http://schemas.xmlsoap.org/soap/envelope/", "Server") ;
private static final boolean RETURN_STACK_TRACES ;
private static final Logger LOGGER = Logger.getLogger(BaseWebService.class);
private static final javax.xml.soap.MessageFactory SOAP_MESSAGE_FACTORY ;
private static final MAPBuilder ADDRESSING_BUILDER = MAPBuilderFactory.getInstance().getBuilderInstance() ;
private static final String ADDRESSING_NAMESPACE = AddressingConstants.Core.NS ;
private static final QName ADDRESSING_REPLY = new QName(ADDRESSING_NAMESPACE, "Reply") ;
private static final Set<SecurityInfoExtractor<SOAPMessage>> extractors = new LinkedHashSet<SecurityInfoExtractor<SOAPMessage>>();
static
{
extractors.add(new WSSecuritySoapExtractor());
extractors.add(new SamlSoapAssertionExtractor());
}
private final String deployment ;
protected final ServiceInvoker serviceInvoker ;
protected final MessagePayloadProxy requestProxy ;
protected final MessagePayloadProxy responseProxy ;
protected final String action ;
protected BaseWebService(final String deployment, final ServiceInvoker serviceInvoker, final String requestLocation, final String responseLocation, final String action)
throws MessageDeliverException
{
this.deployment = deployment ;
this.serviceInvoker = serviceInvoker ;
requestProxy = new MessagePayloadProxy(null, requestLocation) ;
responseProxy = new MessagePayloadProxy(responseLocation, null) ;
this.action = action ;
}
public SOAPMessage invoke(final SOAPMessage request)
{
if (SOAP_MESSAGE_FACTORY == null)
{
throw new WebServiceException("Failed to instantiate SOAP Message Factory") ;
}
final MAP soapIncomingProps = AddressingContext.getAddressingProperties() ;
final Message esbReq = MessageFactory.getInstance().getMessage() ;
final ClassLoader origClassLoader = Thread.currentThread().getContextClassLoader() ;
final ClassLoader deploymentClassLoader = LifecycleResourceManager.getSingleton().getClassLoaderForDeployment(deployment) ;
if (deploymentClassLoader != null)
{
Thread.currentThread().setContextClassLoader(deploymentClassLoader) ;
}
try
{
final SOAPBody soapBody = request.getSOAPBody() ;
if (soapBody == null)
{
throw new WebServiceException("Missing SOAP body from request") ;
}
// There is a bug in JBossWS extractContentAsDocument so we do this ourselves
final Iterator children = soapBody.getChildElements() ;
boolean found = false ;
while(children.hasNext())
{
final Node node = (Node)children.next() ;
if (node instanceof SOAPElement)
{
if (found)
{
throw new SOAPException("Found multiple SOAPElements in SOAPBody") ;
}
final StringWriter sw = new StringWriter() ;
final XMLEventWriter writer = XMLHelper.getXMLEventWriter(new StreamResult(sw)) ;
XMLHelper.readDomNode(node, writer, true) ;
requestProxy.setPayload(esbReq, sw.toString()) ;
found = true ;
}
}
if (!found)
{
throw new SOAPException("Could not find SOAPElement in SOAPBody") ;
}
if (soapIncomingProps != null)
{
initialiseWSAProps(esbReq, soapIncomingProps) ;
}
// Extract security info from SOAPMessage.
AuthenticationRequest authRequest = extractSecurityDetails(request, esbReq);
ExtractorUtil.addAuthRequestToMessage(authRequest, esbReq);
// We should be able to return null here but this causes JBossWS to NPE.
final Message esbRes = deliverMessage(esbReq) ;
final SOAPMessage response = SOAP_MESSAGE_FACTORY.createMessage();
if (esbRes != null)
{
final Object input = responseProxy.getPayload(esbRes) ;
if (input == null)
{
throw new SOAPException("Null response from service") ;
}
final String soapRes = input.toString();
final Document root = parseAsDom(soapRes) ;
response.getSOAPBody().addDocument(root) ;
}
if (soapIncomingProps == null)
{
AddressingContext.setAddressingProperties(null) ;
}
else
{
final MAP soapOutgoingProps = ADDRESSING_BUILDER.newMap() ;
if (action != null)
{
soapOutgoingProps.setAction(action) ;
}
AddressingContext.setAddressingProperties(soapOutgoingProps) ;
}
return response ;
}
catch (final WebServiceException wse)
{
throw wse ;
}
catch (final Exception ex)
{
try
{
SOAPMessage faultMsg = null;
if (ex instanceof FaultMessageException)
{
final FaultMessageException fme = (FaultMessageException) ex ;
final Message faultMessage = fme.getReturnedMessage() ;
if (faultMessage != null)
{
final Body body = faultMessage.getBody() ;
final QName faultCode = (QName)body.get(Fault.DETAIL_CODE_CONTENT) ;
final String faultDescription = (String)body.get(Fault.DETAIL_DESCRIPTION_CONTENT) ;
final String faultDetail = (String)body.get(Fault.DETAIL_DETAIL_CONTENT) ;
if (faultCode != null)
{
faultMsg = SOAP_MESSAGE_FACTORY.createMessage() ;
final SOAPFault fault = faultMsg.getSOAPBody().addFault(faultCode, faultDescription) ;
if (faultDetail != null)
{
try
{
final Document detailDoc = parseAsDom(faultDetail) ;
final Detail detail = fault.addDetail() ;
detail.appendChild(detailDoc.getDocumentElement()) ;
}
catch (final Exception ex2)
{
LOGGER.warn("Failed to parse fault detail", ex2) ;
}
}
}
else
{
final Throwable cause = fme.getCause() ;
faultMsg = (cause != null) ? generateFault(cause) : generateFault(ex) ;
}
}
}
if (faultMsg == null)
{
faultMsg = generateFault(ex) ;
}
return faultMsg ;
}
catch (final SOAPException soape)
{
throw new WebServiceException("Unexpected exception generating fault response", soape) ;
}
}
finally
{
Thread.currentThread().setContextClassLoader(origClassLoader) ;
}
}
private static Document parseAsDom(String soapRes) throws ParserConfigurationException, XMLStreamException
{
final XMLEventReader reader = XMLHelper.getXMLEventReader(new ByteArrayInputStream(soapRes.getBytes())) ;
return XMLHelper.createDocument(reader) ;
}
protected AuthenticationRequest extractSecurityDetails(SOAPMessage request, Message esbReq) throws SecurityServiceException
{
try
{
return ExtractorUtil.extract(request, extractors);
}
catch (final ExtractionException e)
{
throw new SecurityServiceException(e.getMessage(), e);
}
}
private SOAPMessage generateFault(final Throwable th)
throws SOAPException
{
final SOAPMessage faultMsg = SOAP_MESSAGE_FACTORY.createMessage() ;
if (RETURN_STACK_TRACES)
{
final StringWriter sw = new StringWriter() ;
final PrintWriter pw = new PrintWriter(sw) ;
th.printStackTrace(pw) ;
pw.flush() ;
pw.close() ;
faultMsg.getSOAPBody().addFault(SERVER_FAULT_QN, sw.toString());
}
else
{
faultMsg.getSOAPBody().addFault(SERVER_FAULT_QN, th.getMessage());
}
return faultMsg ;
}
private void initialiseWSAProps(final Message esbReq, final MAP props)
{
final String messageID = props.getMessageID() ;
final Properties esbReqProps = esbReq.getProperties() ;
if (messageID != null)
{
esbReqProps.setProperty(Environment.WSA_MESSAGE_ID, messageID) ;
}
final MAPRelatesTo relationship = props.getRelatesTo() ;
if (relationship != null)
{
final String[] relatesTo = new String[1] ;
final String[] relationshipType = new String[1] ;
relatesTo[0] = relationship.getRelatesTo() ;
final QName type = relationship.getType() ;
if (type != null)
{
relationshipType[0] = type.toString() ;
}
else
{
relationshipType[0] = ADDRESSING_REPLY.toString() ;
}
esbReqProps.setProperty(Environment.WSA_RELATES_TO, relatesTo) ;
esbReqProps.setProperty(Environment.WSA_RELATIONSHIP_TYPE, relationshipType) ;
}
}
protected abstract Message deliverMessage(final Message request)
throws Exception ;
static
{
final PropertyManager propertyManager = ModulePropertyManager.getPropertyManager(ModulePropertyManager.TRANSPORTS_MODULE) ;
final String returnStackTraces = propertyManager.getProperty(Environment.WS_RETURN_STACK_TRACE);
RETURN_STACK_TRACES = Boolean.parseBoolean(returnStackTraces) ;
javax.xml.soap.MessageFactory soapMessageFactory = null ;
try
{
soapMessageFactory = javax.xml.soap.MessageFactory.newInstance() ;
}
catch (final SOAPException soape)
{
LOGGER.error("Could not instantiate SOAP Message Factory", soape) ;
}
SOAP_MESSAGE_FACTORY = soapMessageFactory ;
}
}