/*=============================================================================*
* Copyright 2004 The Apache Software Foundation
*
* 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.apache.ws.resource.handler;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.Soap1_1Constants;
import org.apache.ws.addressing.handler.WSAddressingHandler;
import org.apache.ws.resource.ResourceContext;
import org.apache.ws.resource.ResourceContextException;
import org.apache.ws.resource.faults.FaultException;
import org.apache.ws.resource.i18n.Keys;
import org.apache.ws.resource.i18n.MessagesImpl;
import org.apache.ws.util.NameUtils;
import org.apache.ws.util.XmlBeanUtils;
import org.apache.ws.util.helper.Dom2SaajConverter;
import org.apache.ws.util.i18n.Messages;
import org.apache.ws.util.platform.JaxRpcPlatform;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.namespace.QName;
import javax.xml.rpc.JAXRPCException;
import javax.xml.rpc.handler.GenericHandler;
import javax.xml.rpc.handler.HandlerInfo;
import javax.xml.rpc.handler.MessageContext;
import javax.xml.rpc.handler.soap.SOAPMessageContext;
import javax.xml.rpc.soap.SOAPFaultException;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Base class for platform-specific providers; also is a JAX-RPC Handler to make it easier to port to different SOAP
* platforms.
*
* @author Ian Springer
* @author Sal Campana
*/
public class ResourceHandler
extends GenericHandler
{
private static final Log LOG = LogFactory.getLog( ResourceHandler.class );
/**
* DOCUMENT_ME
*/
public static final Messages MSG = MessagesImpl.getInstance( );
/**
* DOCUMENT_ME
*/
public static final String WSRF_RESPONSE_XMLOBJECT_LIST = "WSRF_RESPONSE_XMLOBJECT_LIST";
/**
* DOCUMENT_ME
*/
public static final String SERVICE_OPT_WSDL_TARGET_NAMESPACE = "wsdlTargetNamespace";
/**
* DOCUMENT_ME
*/
public static final String SERVICE_OPT_SERVICE_CLASS_NAME = "serviceClassName";
/** DOCUMENT_ME */
public static final String HANDLER_OPT_VALIDATE_REQUEST_XML = "validateRequestXml";
static
{
ExceptionUtils.addCauseMethodName( "getLinkedCause" ); // for JAXRPCException
}
private Map m_handlerOptions;
/**
* This handler acts as the pivot and does not process any SOAP header elements.
*
* @return the names of the SOAP header elements that this handler processes
*/
public QName[] getHeaders( )
{
return new QName[0];
}
/**
* Deserializes the incoming SOAP request to an XMLBean and dispatches it to the appropriate service. It is required
* that the request meet the following criteria: <ol> <li>SOAP Header contains the header elements required by
* WS-Addressing (either 2003/03 or 2004/08)</li> <li>SOAP Body contains no more than one body element</li> </ol>
*/
public boolean handleRequest( MessageContext msgContext )
{
try
{
LOG.debug( MSG.getMessage( Keys.RECEIVED_REQUEST ) );
SOAPMessageContext soapMsgContext = (SOAPMessageContext) msgContext;
SOAPEnvelope envelope = soapMsgContext.getMessage( ).getSOAPPart( ).getEnvelope( );
if ( LOG.isDebugEnabled( ) )
{
LOG.debug( "Received SOAP request: \n" + envelope );
}
org.apache.ws.util.soap.Body body = getSoapBody( envelope );
ResourceContext resourceContext =
JaxRpcPlatform.getJaxRpcPlatform( ).createResourceContext( soapMsgContext );
String addressingAction = resourceContext.getRequestAction( );
SoapMethodNameMap methodnameMap = new ServiceSoapMethodNameMap( resourceContext );
String methodNameFromAction =
methodnameMap.getMethodNameFromAction( addressingAction );
List responseBodyElems = new ArrayList( );
Class serviceClass = getServiceClassName( resourceContext );
WsrfService service = createService( serviceClass, resourceContext );
XmlObject[] childElems = XmlBeanUtils.getChildElements( body.getXmlObject( ) );
Method serviceMethod = null;
if ( childElems.length > 1 )
{
throw new FaultException( Soap1_1Constants.FAULT_CLIENT,
"SOAP request Body contains more than one body element - this service requires that all SOAP requests contain at most one body element." );
}
XmlObject requestXBean;
if ( childElems.length == 0 ) // empty Body
{
requestXBean = null;
if ( methodNameFromAction != null )
{
// try to find a method based on the wsa:Action...
serviceMethod = getServiceMethod( service, methodNameFromAction );
}
else // empty Body and unmapped wsa:Action
{
throw new FaultException( Soap1_1Constants.FAULT_CLIENT,
"No SOAP Body elements were defined, and the value of the WS-Addressing Action header was not recognized - unable to dispatch request." );
}
}
else // childElems.length == 1
{
requestXBean = toDocumentXmlBean( childElems[0] );
validateRequestXml( requestXBean );
if ( methodNameFromAction != null )
{
serviceMethod = getServiceMethod( service, methodNameFromAction, requestXBean ); //get method based on Action
}
else
{
serviceMethod = getServiceMethod( service, requestXBean ); //get method based solely on request elem
}
}
LOG.debug( MSG.getMessage( Keys.INVOKING_SERVICE_METHOD,
serviceMethod.getName( ) ) );
XmlObject responseXBean = invokeServiceMethod( serviceMethod, service, requestXBean, serviceClass );
if ( responseXBean != null )
{
responseBodyElems.add( responseXBean );
}
if ( !responseBodyElems.isEmpty( ) )
{
msgContext.setProperty( WSRF_RESPONSE_XMLOBJECT_LIST, responseBodyElems );
}
}
catch ( Exception e )
{
handleException( e );
}
return false; // short-circuit any remaining Handlers in chain
}
/**
* DOCUMENT_ME
*
* @param msgContext DOCUMENT_ME
*
* @return DOCUMENT_ME
*/
public boolean handleResponse( MessageContext msgContext )
{
try
{
LOG.debug( MSG.getMessage( Keys.HANDLING_RESPONSE ) );
List responseBodyElems = (List) msgContext.getProperty( WSRF_RESPONSE_XMLOBJECT_LIST );
if ( responseBodyElems != null )
{
SOAPEnvelope responseEnvelope = getResponseEnvelope( (SOAPMessageContext) msgContext );
addWsaHeaderElementsToResponse( msgContext, responseEnvelope );
LOG.debug( MSG.getMessage( Keys.FOUND_RESP_ELEMS,
Integer.toString( responseBodyElems.size( ) ) ) );
SOAPBody responseBody = responseEnvelope.getBody( );
for ( int i = 0; i < responseBodyElems.size( ); i++ )
{
XmlObject responseBodyElem = (XmlObject) responseBodyElems.get( i );
addSOAPBodyElements( responseBodyElem, responseBody );
}
if ( LOG.isDebugEnabled( ) )
{
LOG.debug( "Sending SOAP response: \n" + responseEnvelope );
}
}
else // one-way MEP
{
// a null message tells the SOAP engine to return an empty HTTP response with a 202 status code
( (SOAPMessageContext) msgContext ).setMessage( null );
}
}
catch ( Exception e )
{
handleException( e );
}
return false; // short-circuit any remaining Handlers in chain
}
/**
* DOCUMENT_ME
*
* @param handlerInfo DOCUMENT_ME
*/
public void init( HandlerInfo handlerInfo )
{
Map handlerConfig = handlerInfo.getHandlerConfig( );
m_handlerOptions = ( handlerConfig != null ) ? handlerConfig : new HashMap( );
}
/**
* DOCUMENT_ME
*
* @param resourceContext DOCUMENT_ME
*
* @return DOCUMENT_ME
*/
protected Class getServiceClassName( ResourceContext resourceContext )
throws ResourceContextException,
ClassNotFoundException
{
String serviceClassName = resourceContext.getResourceHome( ).getServiceClassName( );
LOG.debug( MSG.getMessage( Keys.RETRIEVED_SERVICE_CLASSNAME, serviceClassName ) );
if ( serviceClassName == null )
{
throw new IllegalStateException( MSG.getMessage( Keys.SERVICE_OPT_UNDEFINED_IN_HOME,
SERVICE_OPT_SERVICE_CLASS_NAME ) );
}
return Class.forName( serviceClassName );
}
/**
* @param resourceContext
* @param key
*
* @return the service option string
*/
protected final String getServiceOption( ResourceContext resourceContext,
String key )
{
return (String) resourceContext.getProperty( key );
}
/**
* DOCUMENT_ME
*
* @param soapMsgContext DOCUMENT_ME
*
* @return DOCUMENT_ME
*/
protected SOAPEnvelope getResponseEnvelope( SOAPMessageContext soapMsgContext )
{
return ( getEnvelope( getResponseMessage( soapMsgContext ) ) );
}
/**
* @param soapMsgContext the response's JAX-RPC message context
*
* @return response JAX-RPC SOAP message
*/
protected SOAPMessage getResponseMessage( SOAPMessageContext soapMsgContext )
{
SOAPMessage soapMsg = soapMsgContext.getMessage( );
if ( soapMsg == null )
{
soapMsg = createSOAPMessage( );
soapMsgContext.setMessage( soapMsg );
}
return ( soapMsg );
}
/**
* DOCUMENT_ME
*
* @param responseDocXBean DOCUMENT_ME
* @param soapBody DOCUMENT_ME
*/
protected void addSOAPBodyElements( XmlObject responseDocXBean,
SOAPBody soapBody )
{
Node node = responseDocXBean.newDomNode( );
NodeList childNodes = node.getChildNodes( );
for ( int i = 0; i < childNodes.getLength( ); i++ )
{
Node child = childNodes.item( i );
if ( child.getNodeType( ) == Node.ELEMENT_NODE )
{
try
{
SOAPBodyElement soapBodyElement =
soapBody.addBodyElement( NameUtils.createName( child.getLocalName( ),
child.getPrefix( ),
child.getNamespaceURI( ) ) );
Dom2SaajConverter dom2Saaj = new Dom2SaajConverter( );
SOAPElement childSoapElement = dom2Saaj.toSOAPElement( (Element) child );
NodeList children = childSoapElement.getChildNodes( );
for ( int j = 0; j < children.getLength( ); j++ )
{
soapBodyElement.appendChild( children.item( j ) );
}
}
catch ( SOAPException e )
{
e.printStackTrace( );
}
}
}
}
/**
* @param soapMsg a SAAJ SOAP message
*
* @return
*/
private SOAPEnvelope getEnvelope( SOAPMessage soapMsg )
{
try
{
return soapMsg.getSOAPPart( ).getEnvelope( );
}
catch ( SOAPException soape )
{
throw new JAXRPCException( "Failed to get SOAPEnvelope from request SOAPMessage.", soape );
}
}
/**
* This method is used when there are no parameters to a method
*
* @param service The service to find the method on.
* @param methodNameFromAction The method name we are looking for.
*
* @return The Method object
*/
private Method getServiceMethod( WsrfService service,
String methodNameFromAction )
{
Method serviceMethod = null;
LOG.debug( "Based on the request, looking for method named: " + methodNameFromAction + " in service "
+ service.getClass( ).getName( ) + " with no params." );
Method[] methods = service.getClass( ).getMethods( );
for ( int i = 0; i < methods.length; i++ )
{
Method method = methods[i];
if ( method.getName( ).equals( methodNameFromAction ) )
{
if ( method.getParameterTypes( ).length == 0 )
{
serviceMethod = method;
break;
}
else
{
LOG.warn( "Found method named: " + methodNameFromAction + " in service "
+ service.getClass( ).getName( ) + " with " + method.getParameterTypes( ).length
+ " param types, expected 0 param type." );
}
}
}
if ( serviceMethod == null )
{
throw new RuntimeException( MSG.getMessage( Keys.BAD_REQUEST_BODY_ELEMENT,
methodNameFromAction,
service.getClass( ).getName( ) ) );
}
LOG.debug( MSG.getMessage( Keys.FOUND_SERVICE_METHOD,
serviceMethod.getName( ) ) );
return serviceMethod;
}
/**
* Finds the method based on the requestXBean name
*
* @param service
* @param requestXBean
*
* @return
*/
private Method getServiceMethod( WsrfService service,
XmlObject requestXBean )
{
QName bodyElemName = XmlBeanUtils.getName( requestXBean );
if ( bodyElemName == null )
{
throw new FaultException( Soap1_1Constants.FAULT_CLIENT,
MSG.getMessage( Keys.BAD_REQUEST_BODY_ELEMENT_NOPARAM ) );
}
if ( LOG.isDebugEnabled( ) )
{
LOG.debug( MSG.getMessage( Keys.DERIVE_SERVICE_NAME_FROM_REQ,
toString( bodyElemName ) ) );
}
String serviceMethodName = service.getMethodNameMap( ).getMethodName( bodyElemName );
return getServiceMethod( service, serviceMethodName, requestXBean );
}
private Method getServiceMethod( WsrfService service,
String methodName,
XmlObject param )
{
Method serviceMethod = null;
LOG.debug( "Based on the request, looking for method named: " + methodName + " in service "
+ service.getClass( ).getName( ) + " with a single param of type: "
+ param.getClass( ).getName( ) );
Method[] methods = service.getClass( ).getMethods( );
for ( int i = 0; i < methods.length; i++ )
{
Method method = methods[i];
if ( method.getName( ).equals( methodName ) )
{
if ( method.getParameterTypes( ).length == 1 )
{
if ( method.getParameterTypes( )[0].isInstance( param ) )
{
serviceMethod = method;
break;
}
else
{
LOG.warn( "Found method named: " + methodName + " in service "
+ service.getClass( ).getName( ) + " with a single param of type: "
+ method.getParameterTypes( )[0].getName( )
+ " , however the request param was of type: " + param.getClass( ).getName( ) );
}
}
else
{
LOG.warn( "Found method named: " + methodName + " in service " + service.getClass( ).getName( )
+ " with " + method.getParameterTypes( ).length
+ " params; expected method to have exactly one param." );
}
}
}
if ( serviceMethod == null ) // method not found
{
QName bodyElemName = XmlBeanUtils.getName( param );
throw new FaultException( Soap1_1Constants.FAULT_CLIENT,
MSG.getMessage( Keys.BAD_REQUEST_BODY_ELEMENT,
toString( bodyElemName ) ) );
}
LOG.debug( MSG.getMessage( Keys.FOUND_SERVICE_METHOD,
serviceMethod.getName( ) ) );
return serviceMethod;
}
/**
* Returns a facade-wrapped SOAPBody
*
* @param envelope
*
* @return a facade-wrapped SOAPBody
*
* @throws XmlException
*/
private org.apache.ws.util.soap.Body getSoapBody( SOAPEnvelope envelope )
throws XmlException
{
org.apache.ws.util.soap.Envelope envelopeWrapper = null;
XmlObject envelopeDocXmlBean = XmlObject.Factory.parse( envelope.toString( ) );
if ( envelopeDocXmlBean instanceof org.w3.x2003.x05.soapEnvelope.EnvelopeDocument )
{
envelopeWrapper =
new org.apache.ws.util.soap.Envelope( (org.w3.x2003.x05.soapEnvelope.EnvelopeDocument) envelopeDocXmlBean );
}
else if ( envelopeDocXmlBean instanceof org.xmlsoap.schemas.soap.envelope.EnvelopeDocument )
{
envelopeWrapper =
new org.apache.ws.util.soap.Envelope( (org.xmlsoap.schemas.soap.envelope.EnvelopeDocument) envelopeDocXmlBean );
}
else
{
throw new IllegalArgumentException( "Unknown version of SOAPEnvelope: "
+ envelopeDocXmlBean.getClass( ).getName( ) );
}
return envelopeWrapper.getBody( );
}
private void addWsaHeaderElementsToResponse( MessageContext msgContext,
SOAPEnvelope responseEnvelope )
throws SOAPException
{
SOAPHeader header = responseEnvelope.getHeader( );
if ( header == null )
{
header = responseEnvelope.addHeader( );
}
String wsaAction = (String) msgContext.getProperty( WSAddressingHandler.CONTEXT_PROP_WSA_RESPONSE_ACTION );
if ( wsaAction == null )
{
wsaAction =
(String) msgContext.getProperty( WSAddressingHandler.CONTEXT_PROP_WSA_NAMESPACE_URI ) + "/anonymous";
}
SOAPHeaderElement actionHeaderElem =
header.addHeaderElement( NameUtils.createName( "Action", "wsa",
(String) msgContext.getProperty( WSAddressingHandler.CONTEXT_PROP_WSA_NAMESPACE_URI ) ) );
actionHeaderElem.setValue( wsaAction );
String wsaTo = (String) msgContext.getProperty( WSAddressingHandler.CONTEXT_PROP_WSA_RESPONSE_DESTINATION );
if ( wsaTo == null )
{
wsaTo =
(String) msgContext.getProperty( WSAddressingHandler.CONTEXT_PROP_WSA_NAMESPACE_URI ) + "/anonymous";
}
SOAPHeaderElement toHeaderElem =
header.addHeaderElement( NameUtils.createName( "To", "wsa",
(String) msgContext.getProperty( WSAddressingHandler.CONTEXT_PROP_WSA_NAMESPACE_URI ) ) );
toHeaderElem.setValue( wsaTo );
}
private void handleException( Exception e )
{
if ( e instanceof SOAPFaultException )
{
throw (SOAPFaultException) e;
}
else
{
LOG.error( MSG.getMessage( Keys.UNEXPECTED_ERROR ),
e );
throw new FaultException( Soap1_1Constants.FAULT_SERVER,
MSG.getMessage( Keys.INTERNAL_SERVER_ERROR ) );
}
}
private XmlObject invokeServiceMethod( Method serviceMethod,
WsrfService service,
XmlObject requestXBean,
Class serviceClass )
throws Exception
{
XmlObject responseXBean = null;
Object[] params = toObjectArray( requestXBean );
try
{
responseXBean = (XmlObject) serviceMethod.invoke( service, params );
}
catch ( InvocationTargetException ite )
{
if ( LOG.isDebugEnabled( ) )
{
LOG.debug( MSG.getMessage( Keys.ERROR_INVOKING_METHOD_ON_SERVICE,
serviceMethod.getName( ),
serviceClass.getName( ) ) );
if ( ite.getCause( ) != null )
{
ite.getCause( ).printStackTrace( );
}
}
if ( ite.getCause( ) != null )
{
throw (Exception) ite.getCause( );
}
else
{
throw ite;
}
}
if ( ( responseXBean == null ) && ( serviceMethod.getReturnType( ) != void.class ) )
{
// don't allow service method to return null
LOG.error( "Service method " + serviceMethod.getName( ) + " in class " + serviceClass.getName( )
+ " returned null - this is not allowed." );
throw new IllegalStateException( );
}
return responseXBean;
}
private XmlObject toDocumentXmlBean( XmlObject xBean )
throws XmlException
{
// TODO (performance): probably should change method signatures to take types instead of documents to avoid this
return XmlObject.Factory.parse( xBean.xmlText( new XmlOptions( ).setSaveOuter( ) ) );
}
private Object[] toObjectArray( Object obj )
{
return ( obj != null ) ? new Object[]
{
obj
} : new Object[0];
}
private static String toString( QName name )
{
StringBuffer strBuf = new StringBuffer( );
strBuf.append( name.getLocalPart( ) );
if ( name.getNamespaceURI( ) != null )
{
strBuf.append( "@" );
strBuf.append( name.getNamespaceURI( ) );
}
return strBuf.toString( );
}
private String getHandlerOption( String optionName,
String defaultValue )
{
Object value = ( m_handlerOptions != null ) ? m_handlerOptions.get( optionName ) : null;
return ( value != null ) ? value.toString( ) : defaultValue;
}
private SOAPMessage createSOAPMessage( )
{
try
{
return MessageFactory.newInstance( ).createMessage( );
}
catch ( SOAPException soape )
{
throw new JAXRPCException( MSG.getMessage( Keys.FAILED_TO_CREATE_SOAPMESSAGE ), soape );
}
}
private WsrfService createService( Class serviceClass,
ResourceContext resourceContext )
throws Exception
{
LOG.debug( MSG.getMessage( Keys.CREATING_INSTANCE_OF_SERVICE, serviceClass ) );
Constructor serviceCtor = serviceClass.getConstructor( new Class[]
{
ResourceContext.class
} );
WsrfService service = null;
try
{
service = (WsrfService) serviceCtor.newInstance( new Object[]
{
resourceContext
} );
}
catch ( InvocationTargetException ite )
{
Throwable cause = ite.getCause( );
if ( cause instanceof Exception )
{
throw (Exception) cause;
}
throw ite;
}
return service;
}
private void validateRequestXml( XmlObject requestXBean )
{
boolean validateRequestXml =
Boolean.valueOf( getHandlerOption( HANDLER_OPT_VALIDATE_REQUEST_XML, "true" ) ).booleanValue( );
if ( validateRequestXml && !( requestXBean instanceof XmlAnyTypeImpl ) )
{
XmlOptions validateOptions = new XmlOptions( );
List errorList = new ArrayList( );
validateOptions.setErrorListener( errorList );
boolean isValid = requestXBean.validate( validateOptions );
if ( !isValid )
{
QName bodyElemName = XmlBeanUtils.getName( requestXBean );
StringBuffer strBuf = new StringBuffer( "Request body element " );
strBuf.append( toString( bodyElemName ) );
strBuf.append( " is not valid as per its schema: \n\n" );
for ( int i = 0; i < errorList.size( ); i++ )
{
strBuf.append( "\t\t" );
strBuf.append( i + 1 );
strBuf.append( ") " );
strBuf.append( errorList.get( i ) );
strBuf.append( "\n" );
}
strBuf.append( "\n" );
throw new FaultException( Soap1_1Constants.FAULT_CLIENT,
strBuf.toString( ) );
}
}
}
}