/*=============================================================================*
* 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.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.XmlBeanUtils;
import org.apache.ws.util.i18n.Messages;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;
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.SOAPEnvelope;
import javax.xml.soap.SOAPException;
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 abstract class ResourceHandler
extends GenericHandler
{
private static final Log LOG = LogFactory.getLog( ResourceHandler.class );
/**
* DOCUMENT_ME
*/
public static final Messages MSG = MessagesImpl.getInstance();
/**
* DOCUMENT_ME
*/
private 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";
public static final String HANDLER_OPT_VALIDATE_REQUEST_XML = "validateRequestXml";
static
{
ExceptionUtils.addCauseMethodName( "getLinkedCause" ); // for JAXRPCException
}
private Map m_handlerOptions;
public void init( HandlerInfo handlerInfo )
{
Map handlerConfig = handlerInfo.getHandlerConfig();
m_handlerOptions = handlerConfig != null ? handlerConfig : new HashMap();
}
/**
* 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 = createResourceContext( soapMsgContext );
String addressingAction = resourceContext.getAddressingAction();
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." );
}
// TODO: refactor below if-else - too much code duplication
if ( childElems.length == 0 ) // empty Body
{
if ( methodNameFromAction != null )
{
// try to find a method based on the wsa:Action...
serviceMethod = getServiceMethod( service, methodNameFromAction );
LOG.debug( MSG.getMessage( Keys.INVOKE_RESOURCE_METHOD,
serviceMethod.getName() ) );
XmlObject responseXBean = invokeServiceMethod( serviceMethod, service, null, serviceClass );
if ( responseXBean != null )
{
responseBodyElems.add( responseXBean );
}
}
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
{
XmlObject 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.INVOKE_RESOURCE_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 messageContext DOCUMENT_ME
*
* @return DOCUMENT_ME
*/
public boolean handleResponse( MessageContext messageContext )
{
try
{
LOG.debug( MSG.getMessage( Keys.HANDLING_RESPONSE ) );
SOAPEnvelope responseEnvelope = getResponseEnvelope( (SOAPMessageContext) messageContext );
List responseBodyElems = (List) messageContext.getProperty( WSRF_RESPONSE_XMLOBJECT_LIST );
LOG.debug( MSG.getMessage( Keys.FOUND_RESP_ELEMS, Integer.toString( responseBodyElems.size() ) ) );
if ( responseBodyElems != null )
{
SOAPBody responseBody = responseEnvelope.getBody();
for ( int i = 0; i < responseBodyElems.size(); i++ )
{
XmlObject responseBodyElem = (XmlObject) responseBodyElems.get( i );
SOAPBodyElement[] soapBodyElements = createSOAPBodyElements( responseBodyElem );
for ( int j = 0; j < soapBodyElements.length; j++ )
{
SOAPBodyElement soapBodyElement = soapBodyElements[j];
responseBody.addChildElement( soapBodyElement );
}
}
}
if ( LOG.isDebugEnabled() )
{
LOG.debug( "Sending SOAP response: \n" + responseEnvelope );
}
}
catch ( Exception e )
{
handleException( e );
}
return false; // short-circuit any remaining Handlers in chain
}
/**
* Creates a {@link ResourceContext} for this request.
*
* @param soapMsgContext the JAX-RPC SOAP message context for this request
*
* @return a ResourceContext for this request
*/
protected abstract ResourceContext createResourceContext( SOAPMessageContext soapMsgContext );
/**
* DOCUMENT_ME
*
* @param responseBodyElem DOCUMENT_ME
*
* @return DOCUMENT_ME
*/
protected abstract SOAPBodyElement[] createSOAPBodyElements( XmlObject responseBodyElem );
/**
* 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 );
}
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() );
}
}
}
private void handleException( Exception e )
{
if ( e instanceof SOAPFaultException )
{
throw (SOAPFaultException) e;
}
else
{
if ( LOG.isDebugEnabled() )
{
LOG.debug( MSG.getMessage( Keys.INTERNAL_SERVER_ERROR ) );
e.printStackTrace();
}
throw new FaultException( Soap1_1Constants.FAULT_SERVER, MSG.getMessage( Keys.INTERNAL_SERVER_ERROR ) );
}
}
private XmlObject toDocumentXmlBean( XmlObject xBean )
throws XmlException
{
// TODO: probably should change method signatures to take types instead of documents to avoid this
return XmlObject.Factory.parse( xBean.xmlText( new XmlOptions().setSaveOuter() ) );
}
/**
* 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();
}
/**
* 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 " +
0 +
" param types" );
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;
}
private XmlObject invokeServiceMethod( Method serviceMethod, WsrfService service, XmlObject requestXBean,
Class serviceClass )
throws Exception
{
XmlObject responseXBean = null;
Object[] params = ( requestXBean != null ) ? new Object[]
{
requestXBean
} : new Object[0];
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;
}
/**
* @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 );
}
}
/**
* 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 ( 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 );
}
/**
* Method used to locate a Method object in a Service based on the method name and param
*
* @param service
* @param methodName
* @param param
*
* @return
*/
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 " +
1 +
" param type called: " +
param ==
null ?
"null" : 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 ) ) //todo check if handles null
{
serviceMethod = method;
break;
}
else
{
LOG.warn( "Found method named: " + methodName + " in service " +
service.getClass().getName() +
" with " +
1 +
" param of type: " +
method.getParameterTypes()[0].getName() +
" , however the request param was: " +
param.getClass().getName() );
}
}
else
{
LOG.warn( "Found method named: " + methodName + " in service " +
service.getClass().getName() +
" with " +
method.getParameterTypes().length +
" param types, expected 1 param type." );
}
}
}
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;
}
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 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 )
{
throw (Exception) ite.getCause();
}
return service;
}
private String getHandlerOption( String optionName, String defaultValue )
{
Object value = m_handlerOptions != null ? m_handlerOptions.get( optionName ) : null;
return value != null ? value.toString() : defaultValue;
}
}