blob: 183049d0465a161b24d2dda66a46f05e7422b512 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.axis2.jaxws.client.dispatch;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.jaxws.BindingProvider;
import org.apache.axis2.jaxws.ExceptionFactory;
import org.apache.axis2.jaxws.client.async.AsyncResponse;
import org.apache.axis2.jaxws.core.InvocationContext;
import org.apache.axis2.jaxws.core.InvocationContextFactory;
import org.apache.axis2.jaxws.core.MessageContext;
import org.apache.axis2.jaxws.core.controller.InvocationController;
import org.apache.axis2.jaxws.core.controller.InvocationControllerFactory;
import org.apache.axis2.jaxws.description.EndpointDescription;
import org.apache.axis2.jaxws.description.EndpointInterfaceDescription;
import org.apache.axis2.jaxws.description.OperationDescription;
import org.apache.axis2.jaxws.i18n.Messages;
import org.apache.axis2.jaxws.marshaller.impl.alt.MethodMarshallerUtils;
import org.apache.axis2.jaxws.message.Message;
import org.apache.axis2.jaxws.registry.FactoryRegistry;
import org.apache.axis2.jaxws.spi.Binding;
import org.apache.axis2.jaxws.spi.Constants;
import org.apache.axis2.jaxws.spi.ServiceDelegate;
import org.apache.axis2.jaxws.spi.migrator.ApplicationContextMigratorUtil;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Node;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.dom.DOMSource;
import javax.xml.ws.AsyncHandler;
import javax.xml.ws.ProtocolException;
import javax.xml.ws.Response;
import javax.xml.ws.Service.Mode;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.WebServiceFeature;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.ws.soap.SOAPBinding;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
public abstract class BaseDispatch<T> extends BindingProvider
implements javax.xml.ws.Dispatch {
private static Log log = LogFactory.getLog(BaseDispatch.class);
protected InvocationController ic;
protected ServiceClient serviceClient;
protected Mode mode;
protected BaseDispatch(ServiceDelegate svcDelgate,
EndpointDescription epDesc,
EndpointReference epr,
String addressingNamespace,
WebServiceFeature... features) {
super(svcDelgate, epDesc, epr, addressingNamespace, features);
InvocationControllerFactory icf = (InvocationControllerFactory) FactoryRegistry.getFactory(InvocationControllerFactory.class);
ic = icf.getInvocationController();
if (ic == null) {
throw new WebServiceException(Messages.getMessage("missingInvocationController"));
}
}
/**
* Take the input object and turn it into an OMElement so that it can be sent.
*
* @param value
* @return
*/
protected abstract Message createMessageFromValue(Object value);
/**
* Given a message, return the business object based on the requestor's required format (PAYLOAD
* vs. MESSAGE) and datatype.
*
* @param message
* @return
*/
protected abstract Object getValueFromMessage(Message message);
/**
* Creates an instance of the AsyncListener that is to be used for waiting for async responses.
*
* @return a configured AsyncListener instance
*/
protected abstract AsyncResponse createAsyncResponseListener();
/**
* Note to developer: When making a change or fix to this method, please consider
* all 5 Proxy/Dispatch "invoke" methods now available in JAX-WS. For Dispatch,
* these are:
* 1) Synchronous invoke()
* 2) invokeOneWay()
* 3) invokeAsynch (Future)
* 4) invokeAsynch (Callback)
*
* For Proxy:
* 5) invokeSEIMethod()
*
*/
public Object invoke(Object obj) throws WebServiceException {
// Catch all exceptions and rethrow an appropriate WebService Exception
try {
if (log.isDebugEnabled()) {
log.debug("Entered synchronous invocation: BaseDispatch.invoke()");
}
// Create the InvocationContext instance for this request/response flow.
InvocationContext invocationContext =
InvocationContextFactory.createInvocationContext(null);
invocationContext.setServiceClient(serviceClient);
// Create the MessageContext to hold the actual request message and its
// associated properties
MessageContext requestMsgCtx = new MessageContext();
requestMsgCtx.getAxisMessageContext().setProperty(BINDING_PROVIDER, this);
requestMsgCtx.setEndpointDescription(getEndpointDescription());
invocationContext.setRequestMessageContext(requestMsgCtx);
/*
* TODO: review: make sure the handlers are set on the InvocationContext
* This implementation of the JAXWS runtime does not use Endpoint, which
* would normally be the place to initialize and store the handler list.
* In lieu of that, we will have to intialize and store them on the
* InvocationContext. also see the InvocationContextFactory. On the client
* side, the binding is not yet set when we call into that factory, so the
* handler list doesn't get set on the InvocationContext object there. Thus
* we gotta do it here.
*/
// be sure to use whatever handlerresolver is registered on the Service
Binding binding = (Binding) getBinding();
invocationContext.setHandlers(binding.getHandlerChain());
initMessageContext(obj, requestMsgCtx);
// call common init method for all invoke* paths
preInvokeInit(invocationContext);
// Migrate the properties from the client request context bag to
// the request MessageContext.
ApplicationContextMigratorUtil.performMigrationToMessageContext(
Constants.APPLICATION_CONTEXT_MIGRATOR_LIST_ID,
getRequestContext(), requestMsgCtx);
// Perform the WebServiceFeature configuration requested by the user.
binding.configure(requestMsgCtx, this);
// Initializing the message context above will put the outbound message onto the messageContext
// Determine the operation if possible from the outbound message. If it can not be determined
// it will be set to null. In this case, an anonymous operation will be used. Note that determining
// the operation will mean deserializing the message. That means that any WebServiceFeatures must have
// been configured first so that any relevant configurations (such as MTOM) have been initialized prior to
// the message being deserialized. This is particularly true for Dispatch<JAXB Element>.
requestMsgCtx.setOperationDescription(getOperationDescriptionForDispatch(requestMsgCtx));
// Send the request using the InvocationController
ic.invoke(invocationContext);
MessageContext responseMsgCtx = invocationContext.getResponseMessageContext();
responseMsgCtx.setEndpointDescription(requestMsgCtx.getEndpointDescription());
// Migrate the properties from the response MessageContext back
// to the client response context bag.
ApplicationContextMigratorUtil.performMigrationFromMessageContext(
Constants.APPLICATION_CONTEXT_MIGRATOR_LIST_ID,
getResponseContext(), responseMsgCtx);
if (hasFaultResponse(responseMsgCtx)) {
WebServiceException wse = BaseDispatch.getFaultResponse(responseMsgCtx);
throw wse;
}
// Get the return object
Object returnObj = null;
try {
Message responseMsg = responseMsgCtx.getMessage();
returnObj = getValueFromMessage(responseMsg);
}
finally {
// Free the incoming input stream
try {
responseMsgCtx.freeInputStream();
}
catch (Throwable t) {
throw ExceptionFactory.makeWebServiceException(t);
}
}
//Check to see if we need to maintain session state
checkMaintainSessionState(requestMsgCtx, invocationContext);
if (log.isDebugEnabled()) {
log.debug("Synchronous invocation completed: BaseDispatch.invoke()");
}
return returnObj;
} catch (WebServiceException e) {
if (log.isDebugEnabled()) {
log.debug("BaseDispatch.invoke(): Synchronous invocation failed, "
+ "caught a WebServiceException: ", e);
}
throw e;
} catch (Exception e) {
// All exceptions are caught and rethrown as a WebServiceException
if (log.isDebugEnabled()) {
log.debug("BaseDispatch.invoke(): Synchronous invocation failed, caught an Exception, " +
"wrapping into a WebServiceException. Exception caught: ", e);
}
throw ExceptionFactory.makeWebServiceException(e);
}
}
/**
* Given a JAXWS Message Context which contains an outbound service-requester Message for a Dispatch client,
* determine the OperationDescription for the operation contained in that Dispatch message.
*
* Note that operation resolution can be disabled by a property setting.
* @see org.apache.axis2.jaxws.Constants.DISPATCH_CLIENT_OUTBOUND_RESOLUTION
*
* @param requestMessageCtx JAXWS Message Context containing the outbound Dispatch message
* @return the OperationDescription corresponding to the operation contained in the Dispatch message, or null
* if it can not be determined or if dispatch operation resolution is disabled via a property.
*/
private OperationDescription getOperationDescriptionForDispatch(MessageContext requestMessageCtx) {
OperationDescription operationDesc = null;
if (dispatchOperationResolutionEnabled()) {
EndpointInterfaceDescription endpointInterfaceDesc = getEndpointDescription().getEndpointInterfaceDescription();
// The SEI interface could be null (for example if there was no SEI and all the ports were dynamically added).
// If there is an SEI, then try to determine the operation for the outbound dispatch message.
if (endpointInterfaceDesc != null) {
QName bodyElementQName = getBodyElementQNameFromDispatchMessage(requestMessageCtx);
operationDesc = determineOperationDescFromBodyElementQName(endpointInterfaceDesc, bodyElementQName);
}
}
return operationDesc;
}
/**
* Returns the OperationDescription corresponding to the bodyElementQName passed in. What that body element corresponds to
* depends on the type of the message:
* - For Doc/Lit/Wrapped, the body element is the operation name
* - For Doc/Lit/Bare, the body element is the element name contained in the wsdl:message wsdl:part
* - For RPC, the body element is effectively the operation name.
*
* @param endpointInterfaceDesc The interface (i.e. SEI) on which to search for the operation
* @param bodyElementQName the QName of the first body element for which to find the operation
*
* @return The OperationDescription corresponding to the body element QName or null if one can not be found.
*/
private OperationDescription determineOperationDescFromBodyElementQName(EndpointInterfaceDescription endpointInterfaceDesc,
QName bodyElementQName) {
OperationDescription operationDesc = null;
// If there's no bodyElementQName for us to work with, there's nothing more we can do.
if (bodyElementQName != null) {
// This logic mimics the code in SOAPMessageBodyBasedOperationDispatcher.findOperation. We will look for
// the AxisOperation corresponding to the body element name. Note that we are searching for the AxisOperation instead
// of searching through the OperationDescriptions so that we can use the getOperationByMessageElementQName
// for the Doc/Lit/Bare case. Once we have the AxisOperation, we'll use that to find the Operation Description.
AxisService axisService = endpointInterfaceDesc.getEndpointDescription().getAxisService();
AxisOperation axisOperation = null;
// Doc/Lit/Wrapped and RPC, the operation name is the first body element qname
axisOperation = axisService.getOperation(new QName(bodyElementQName.getLocalPart()));
if (axisOperation == null) {
// Doc/Lit/Bare, the first body element qname is the element name contained in the wsdl:message part
axisOperation = axisService.getOperationByMessageElementQName(bodyElementQName);
}
if (axisOperation == null) {
// Not sure why we wouldn't have found the operation above using just the localPart rather than the full QName used here,
// but this is what SOAPMessageBodyBasedOperationDispatcher.findOperation does.
axisOperation = axisService.getOperation(bodyElementQName);
}
// If we found an axis operation, then find the operation description that corresponds to it
if (axisOperation != null) {
OperationDescription allOpDescs[] = endpointInterfaceDesc.getDispatchableOperations();
for (OperationDescription checkOpDesc : allOpDescs ) {
AxisOperation checkAxisOperation = checkOpDesc.getAxisOperation();
if (checkAxisOperation == axisOperation) {
operationDesc = checkOpDesc;
break;
}
}
}
}
return operationDesc;
}
/**
* Answer if operation resolution on outbound messages for dispatch clients should be done. The default value
* is TRUE, enabling operation resolution. Resolution can be disabled via a property on the AxisConfiguration
* or on the RequestContext.
*
* Operation resolution is also disabled if a non-null value is specified on the request context for the Action
*
* @see org.apache.axis2.jaxws.Constants.DISPATCH_CLIENT_OUTBOUND_RESOLUTION
* @see javax.xml.ws.BindingProvider.SOAPACTION_USE_PROPERTY
* @see javax.xml.ws.BindingProvider.SOAPACTION_URI_PROPERTY
*
* @return true if operation resolution should be performed on outbound
*/
private boolean dispatchOperationResolutionEnabled() {
boolean resolutionEnabled = true;
// See if any properties disabled operation resolution
// Check for System property setting
String flagValue = getProperty(org.apache.axis2.jaxws.Constants.DISPATCH_CLIENT_OUTBOUND_RESOLUTION);
// If no System property was set, see if one was set on this request context.
if (flagValue == null) {
flagValue = (String) getRequestContext().get(org.apache.axis2.jaxws.Constants.DISPATCH_CLIENT_OUTBOUND_RESOLUTION);
}
// If any property was set, check the value.
if (flagValue != null) {
if ("false".equalsIgnoreCase(flagValue)) {
resolutionEnabled = false;
} else if ("true".equalsIgnoreCase(flagValue)) {
resolutionEnabled = true;
}
}
// If a property didn't disable resolution, then see if a URI value was specified.
// If so, we'll use that later and there's no need to do operation resolution.
if (resolutionEnabled) {
Boolean useSoapAction = (Boolean) getRequestContext().get(SOAPACTION_USE_PROPERTY);
if (useSoapAction != null && useSoapAction.booleanValue()) {
String soapAction = (String) getRequestContext().get(SOAPACTION_URI_PROPERTY);
if (soapAction != null) {
resolutionEnabled = false;
}
}
}
return resolutionEnabled;
}
/**
* Retrieve the specified property from the AxisConfiguration.
*
* @param key The property to retrieve from the AxisConfiguration
* @return the value associated with the property or null if the property did not exist on the configuration.
*/
private String getProperty(String key) {
String propertyValue = null;
AxisConfiguration axisConfig = serviceDelegate.getServiceDescription().getAxisConfigContext().getAxisConfiguration();
Parameter parameter = axisConfig.getParameter(key);
if (parameter != null) {
propertyValue = (String) parameter.getValue();
}
return propertyValue;
}
/**
* Given a JAXWS Message Context which contains an outbound service-requester Message for a Dispatch client,
* determine the QName of the first body element contained in that message.
*
* @param requestMessageCtx requestMessageCtx JAXWS Message Context containing the outbound Dispatch message
* @return the QName of the first body element contained in the outbound Dispatch message, or null if it
* can not be determined.
*/
QName getBodyElementQNameFromDispatchMessage(MessageContext requestMessageCtx) {
QName bodyElementQName = null;
Message dispatchMessage = requestMessageCtx.getMessage();
SOAPMessage soapMessage = dispatchMessage.getAsSOAPMessage();
try {
SOAPBody soapBody = soapMessage.getSOAPBody();
Node firstElement = soapBody.getFirstChild();
// A Doc/Lit/Bare message may not have a firsElement. The soap:Body element may be empty if there
// are no arguments to the operation.
if (firstElement != null) {
String ns = firstElement.getNamespaceURI();
String lp= firstElement.getLocalName();
// A Doc/Lit/Bare message may not have a localPart on the element. That can happen if the first element
// is the argument value and there is no wrapper element surrounding it.
if (lp != null) {
bodyElementQName = new QName(ns, lp);
}
}
} catch (SOAPException e) {
if (log.isDebugEnabled()) {
log.debug("Unabled to get the first body element from the outbound dispatch message", e);
}
}
return bodyElementQName;
}
protected void initMessageContext(Object obj, MessageContext requestMsgCtx) {
Message requestMsg = createRequestMessage(obj);
setupMessageProperties(requestMsg);
requestMsgCtx.setMessage(requestMsg);
// handle HTTP_REQUEST_METHOD property
String method = (String)requestContext.get(javax.xml.ws.handler.MessageContext.HTTP_REQUEST_METHOD);
if (method != null) {
requestMsgCtx.setProperty(org.apache.axis2.Constants.Configuration.HTTP_METHOD, method);
}
}
/**
* Note to developer: When making a change or fix to this method, please consider
* all 5 Proxy/Dispatch "invoke" methods now available in JAX-WS. For Dispatch,
* these are:
* 1) Synchronous invoke()
* 2) invokeOneWay()
* 3) invokeAsynch (Future)
* 4) invokeAsynch (Callback)
*
* For Proxy:
* 5) invokeSEIMethod()
*
*/
public void invokeOneWay(Object obj) throws WebServiceException {
// All exceptions are caught and rethrown as a WebServiceException
MessageContext requestMsgCtx = null;
try {
if (log.isDebugEnabled()) {
log.debug("Entered one-way invocation: BaseDispatch.invokeOneWay()");
}
// Create the InvocationContext instance for this request/response flow.
InvocationContext invocationContext =
InvocationContextFactory.createInvocationContext(null);
invocationContext.setServiceClient(serviceClient);
// Create the MessageContext to hold the actual request message and its
// associated properties
requestMsgCtx = new MessageContext();
requestMsgCtx.getAxisMessageContext().setProperty(BINDING_PROVIDER, this);
requestMsgCtx.setEndpointDescription(getEndpointDescription());
invocationContext.setRequestMessageContext(requestMsgCtx);
/*
* TODO: review: make sure the handlers are set on the InvocationContext
* This implementation of the JAXWS runtime does not use Endpoint, which
* would normally be the place to initialize and store the handler list.
* In lieu of that, we will have to intialize and store them on the
* InvocationContext. also see the InvocationContextFactory. On the client
* side, the binding is not yet set when we call into that factory, so the
* handler list doesn't get set on the InvocationContext object there. Thus
* we gotta do it here.
*/
// be sure to use whatever handlerresolver is registered on the Service
Binding binding = (Binding) getBinding();
invocationContext.setHandlers(binding.getHandlerChain());
initMessageContext(obj, requestMsgCtx);
/*
* if SESSION_MAINTAIN_PROPERTY is true, and the client app has explicitly set a HEADER_COOKIE on the request context, assume the client
* app is expecting the HEADER_COOKIE to be the session id. If we were establishing a new session, no cookie would be sent, and the
* server would reply with a "Set-Cookie" header, which is copied as a "Cookie"-keyed property to the service context during response.
* In this case, if we succeed in using an existing server session, no "Set-Cookie" header will be returned, and therefore no
* "Cookie"-keyed property would be set on the service context. So, let's copy our request context HEADER_COOKIE key to the service
* context now to prevent the "no cookie" exception in BindingProvider.setupSessionContext. It is possible the server does not support
* sessions, in which case no error occurs, but the client app would assume it is participating in a session.
*/
if ((requestContext.containsKey(BindingProvider.SESSION_MAINTAIN_PROPERTY)) && ((Boolean)requestContext.get(BindingProvider.SESSION_MAINTAIN_PROPERTY))) {
if ((requestContext.containsKey(HTTPConstants.HEADER_COOKIE)) && (requestContext.get(HTTPConstants.HEADER_COOKIE) != null)) {
if (invocationContext.getServiceClient().getServiceContext().getProperty(HTTPConstants.HEADER_COOKIE) == null) {
invocationContext.getServiceClient().getServiceContext().setProperty(HTTPConstants.HEADER_COOKIE, requestContext.get(HTTPConstants.HEADER_COOKIE));
if (log.isDebugEnabled()) {
log.debug("Client-app defined Cookie property (assume to be session cookie) on request context copied to service context." +
" Caution: server may or may not support sessions, but client app will not be informed when not supported.");
}
}
}
}
// call common init method for all invoke* paths
preInvokeInit(invocationContext);
// Migrate the properties from the client request context bag to
// the request MessageContext.
ApplicationContextMigratorUtil.performMigrationToMessageContext(
Constants.APPLICATION_CONTEXT_MIGRATOR_LIST_ID,
getRequestContext(), requestMsgCtx);
// Perform the WebServiceFeature configuration requested by the user.
binding.configure(requestMsgCtx, this);
// Initializing the message context above will put the outbound message onto the messageContext
// Determine the operation if possible from the outbound message. If it can not be determined
// it will be set to null. In this case, an anonymous operation will be used. Note that determining
// the operation will mean deserializing the message. That means that any WebServiceFeatures must have
// been configured first so that any relevant configurations (such as MTOM) have been initialized prior to
// the message being deserialized. This is particularly true for Dispatch<JAXB Element>.
requestMsgCtx.setOperationDescription(getOperationDescriptionForDispatch(requestMsgCtx));
// Send the request using the InvocationController
ic.invokeOneWay(invocationContext);
//Check to see if we need to maintain session state
checkMaintainSessionState(requestMsgCtx, invocationContext);
if (log.isDebugEnabled()) {
log.debug("One-way invocation completed: BaseDispatch.invokeOneWay()");
}
return;
} catch (WebServiceException e) {
if (log.isDebugEnabled()) {
log.debug("BaseDispatch.invokeOneWay(): One-way invocation failed, " +
"caught a WebServiceException: ", e);
}
throw e;
} catch (Exception e) {
// All exceptions are caught and rethrown as a WebServiceException
if (log.isDebugEnabled()) {
log.debug("BaseDispatch.invokeOneWay(): One-way invocation failed, " +
"caught an Exception, wrapping into a WebServicesException. " +
" Exception caught: ", e);
}
throw ExceptionFactory.makeWebServiceException(e);
} finally {
// In all other cases we rely on freeInputStream to perform the clean up. Since we don't expect
// a response in the invokeOneWay case, we need to perform call TransportSender#cleanup explicitly
try {
if (requestMsgCtx != null && requestMsgCtx.getAxisMessageContext() != null) {
org.apache.axis2.context.MessageContext axisMsgCtx = requestMsgCtx.getAxisMessageContext();
if (axisMsgCtx.getTransportOut() != null && axisMsgCtx.getTransportOut().getSender() != null) {
axisMsgCtx.getTransportOut().getSender().cleanup(axisMsgCtx);
}
}
} catch (Exception ignore) {
}
}
}
/**
* Note to developer: When making a change or fix to this method, please consider
* all 5 Proxy/Dispatch "invoke" methods now available in JAX-WS. For Dispatch,
* these are:
* 1) Synchronous invoke()
* 2) invokeOneWay()
* 3) invokeAsynch (Future)
* 4) invokeAsynch (Callback)
*
* For Proxy:
* 5) invokeSEIMethod()
*
*/
public Future<?> invokeAsync(Object obj, AsyncHandler asynchandler) throws WebServiceException {
// All exceptions are caught and rethrown as a WebServiceException
try {
if (log.isDebugEnabled()) {
log.debug("Entered asynchronous (callback) invocation: BaseDispatch.invokeAsync()");
}
// Create the InvocationContext instance for this request/response flow.
InvocationContext invocationContext =
InvocationContextFactory.createInvocationContext(null);
invocationContext.setServiceClient(serviceClient);
// Create the MessageContext to hold the actual request message and its
// associated properties
MessageContext requestMsgCtx = new MessageContext();
requestMsgCtx.getAxisMessageContext().setProperty(BINDING_PROVIDER, this);
requestMsgCtx.setEndpointDescription(getEndpointDescription());
invocationContext.setRequestMessageContext(requestMsgCtx);
/*
* TODO: review: make sure the handlers are set on the InvocationContext
* This implementation of the JAXWS runtime does not use Endpoint, which
* would normally be the place to initialize and store the handler list.
* In lieu of that, we will have to intialize and store them on the
* InvocationContext. also see the InvocationContextFactory. On the client
* side, the binding is not yet set when we call into that factory, so the
* handler list doesn't get set on the InvocationContext object there. Thus
* we gotta do it here.
*/
// be sure to use whatever handlerresolver is registered on the Service
Binding binding = (Binding) getBinding();
invocationContext.setHandlers(binding.getHandlerChain());
initMessageContext(obj, requestMsgCtx);
/*
* if SESSION_MAINTAIN_PROPERTY is true, and the client app has explicitly set a HEADER_COOKIE on the request context, assume the client
* app is expecting the HEADER_COOKIE to be the session id. If we were establishing a new session, no cookie would be sent, and the
* server would reply with a "Set-Cookie" header, which is copied as a "Cookie"-keyed property to the service context during response.
* In this case, if we succeed in using an existing server session, no "Set-Cookie" header will be returned, and therefore no
* "Cookie"-keyed property would be set on the service context. So, let's copy our request context HEADER_COOKIE key to the service
* context now to prevent the "no cookie" exception in BindingProvider.setupSessionContext. It is possible the server does not support
* sessions, in which case no error occurs, but the client app would assume it is participating in a session.
*/
if ((requestContext.containsKey(BindingProvider.SESSION_MAINTAIN_PROPERTY)) && ((Boolean)requestContext.get(BindingProvider.SESSION_MAINTAIN_PROPERTY))) {
if ((requestContext.containsKey(HTTPConstants.HEADER_COOKIE)) && (requestContext.get(HTTPConstants.HEADER_COOKIE) != null)) {
if (invocationContext.getServiceClient().getServiceContext().getProperty(HTTPConstants.HEADER_COOKIE) == null) {
invocationContext.getServiceClient().getServiceContext().setProperty(HTTPConstants.HEADER_COOKIE, requestContext.get(HTTPConstants.HEADER_COOKIE));
if (log.isDebugEnabled()) {
log.debug("Client-app defined Cookie property (assume to be session cookie) on request context copied to service context." +
" Caution: server may or may not support sessions, but client app will not be informed when not supported.");
}
}
}
}
// call common init method for all invoke* paths
preInvokeInit(invocationContext);
// Migrate the properties from the client request context bag to
// the request MessageContext.
ApplicationContextMigratorUtil.performMigrationToMessageContext(
Constants.APPLICATION_CONTEXT_MIGRATOR_LIST_ID,
getRequestContext(), requestMsgCtx);
// Perform the WebServiceFeature configuration requested by the user.
binding.configure(requestMsgCtx, this);
// Initializing the message context above will put the outbound message onto the messageContext
// Determine the operation if possible from the outbound message. If it can not be determined
// it will be set to null. In this case, an anonymous operation will be used. Note that determining
// the operation will mean deserializing the message. That means that any WebServiceFeatures must have
// been configured first so that any relevant configurations (such as MTOM) have been initialized prior to
// the message being deserialized. This is particularly true for Dispatch<JAXB Element>.
requestMsgCtx.setOperationDescription(getOperationDescriptionForDispatch(requestMsgCtx));
// Setup the Executor that will be used to drive async responses back to
// the client.
// FIXME: We shouldn't be getting this from the ServiceDelegate, rather each
// Dispatch object should have it's own.
Executor e = serviceDelegate.getExecutor();
invocationContext.setExecutor(e);
// Create the AsyncListener that is to be used by the InvocationController.
AsyncResponse listener = createAsyncResponseListener();
invocationContext.setAsyncResponseListener(listener);
// Send the request using the InvocationController
Future<?> asyncResponse = ic.invokeAsync(invocationContext, asynchandler);
//Check to see if we need to maintain session state
checkMaintainSessionState(requestMsgCtx, invocationContext);
if (log.isDebugEnabled()) {
log.debug("Asynchronous (callback) invocation sent: BaseDispatch.invokeAsync()");
}
return asyncResponse;
} catch (WebServiceException e) {
if (log.isDebugEnabled()) {
log.debug("BaseDispatch.invokeAsync() [Callback]: Asynchronous invocation failed, " +
"caught a WebServiceException: ", e);
}
throw e;
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("BaseDispatch.invokeAsync() [Callback]: Asynchronous invocation failed, " +
"caught an Exception, wrapping into a WebServiceException. Exception caught: ", e);
}
// All exceptions are caught and rethrown as a WebServiceException
throw ExceptionFactory.makeWebServiceException(e);
}
}
/**
* Note to developer: When making a change or fix to this method, please consider
* all 5 Proxy/Dispatch "invoke" methods now available in JAX-WS. For Dispatch,
* these are:
* 1) Synchronous invoke()
* 2) invokeOneWay()
* 3) invokeAsynch (Future)
* 4) invokeAsynch (Callback)
*
* For Proxy:
* 5) invokeSEIMethod()
*
*/
public Response invokeAsync(Object obj) throws WebServiceException {
// All exceptions are caught and rethrown as a WebServiceException
try {
if (log.isDebugEnabled()) {
log.debug("Entered asynchronous (polling) invocation: BaseDispatch.invokeAsync()");
}
// Create the InvocationContext instance for this request/response flow.
InvocationContext invocationContext =
InvocationContextFactory.createInvocationContext(null);
invocationContext.setServiceClient(serviceClient);
// Create the MessageContext to hold the actual request message and its
// associated properties
MessageContext requestMsgCtx = new MessageContext();
requestMsgCtx.getAxisMessageContext().setProperty(BINDING_PROVIDER, this);
requestMsgCtx.setEndpointDescription(getEndpointDescription());
invocationContext.setRequestMessageContext(requestMsgCtx);
/*
* TODO: review: make sure the handlers are set on the InvocationContext
* This implementation of the JAXWS runtime does not use Endpoint, which
* would normally be the place to initialize and store the handler list.
* In lieu of that, we will have to intialize and store them on the
* InvocationContext. also see the InvocationContextFactory. On the client
* side, the binding is not yet set when we call into that factory, so the
* handler list doesn't get set on the InvocationContext object there. Thus
* we gotta do it here.
*/
// be sure to use whatever handlerresolver is registered on the Service
Binding binding = (Binding) getBinding();
invocationContext.setHandlers(binding.getHandlerChain());
initMessageContext(obj, requestMsgCtx);
/*
* if SESSION_MAINTAIN_PROPERTY is true, and the client app has explicitly set a HEADER_COOKIE on the request context, assume the client
* app is expecting the HEADER_COOKIE to be the session id. If we were establishing a new session, no cookie would be sent, and the
* server would reply with a "Set-Cookie" header, which is copied as a "Cookie"-keyed property to the service context during response.
* In this case, if we succeed in using an existing server session, no "Set-Cookie" header will be returned, and therefore no
* "Cookie"-keyed property would be set on the service context. So, let's copy our request context HEADER_COOKIE key to the service
* context now to prevent the "no cookie" exception in BindingProvider.setupSessionContext. It is possible the server does not support
* sessions, in which case no error occurs, but the client app would assume it is participating in a session.
*/
if ((requestContext.containsKey(BindingProvider.SESSION_MAINTAIN_PROPERTY)) && ((Boolean)requestContext.get(BindingProvider.SESSION_MAINTAIN_PROPERTY))) {
if ((requestContext.containsKey(HTTPConstants.HEADER_COOKIE)) && (requestContext.get(HTTPConstants.HEADER_COOKIE) != null)) {
if (invocationContext.getServiceClient().getServiceContext().getProperty(HTTPConstants.HEADER_COOKIE) == null) {
invocationContext.getServiceClient().getServiceContext().setProperty(HTTPConstants.HEADER_COOKIE, requestContext.get(HTTPConstants.HEADER_COOKIE));
if (log.isDebugEnabled()) {
log.debug("Client-app defined Cookie property (assume to be session cookie) on request context copied to service context." +
" Caution: server may or may not support sessions, but client app will not be informed when not supported.");
}
}
}
}
// call common init method for all invoke* paths
preInvokeInit(invocationContext);
// Migrate the properties from the client request context bag to
// the request MessageContext.
ApplicationContextMigratorUtil.performMigrationToMessageContext(
Constants.APPLICATION_CONTEXT_MIGRATOR_LIST_ID,
getRequestContext(), requestMsgCtx);
// Perform the WebServiceFeature configuration requested by the user.
binding.configure(requestMsgCtx, this);
// Initializing the message context above will put the outbound message onto the messageContext
// Determine the operation if possible from the outbound message. If it can not be determined
// it will be set to null. In this case, an anonymous operation will be used. Note that determining
// the operation will mean deserializing the message. That means that any WebServiceFeatures must have
// been configured first so that any relevant configurations (such as MTOM) have been initialized prior to
// the message being deserialized. This is particularly true for Dispatch<JAXB Element>.
requestMsgCtx.setOperationDescription(getOperationDescriptionForDispatch(requestMsgCtx));
// Setup the Executor that will be used to drive async responses back to
// the client.
// FIXME: We shouldn't be getting this from the ServiceDelegate, rather each
// Dispatch object should have it's own.
Executor e = serviceDelegate.getExecutor();
invocationContext.setExecutor(e);
// Create the AsyncListener that is to be used by the InvocationController.
AsyncResponse listener = createAsyncResponseListener();
invocationContext.setAsyncResponseListener(listener);
// Send the request using the InvocationController
Response asyncResponse = ic.invokeAsync(invocationContext);
//Check to see if we need to maintain session state
checkMaintainSessionState(requestMsgCtx, invocationContext);
if (log.isDebugEnabled()) {
log.debug("Asynchronous (polling) invocation sent: BaseDispatch.invokeAsync()");
}
return asyncResponse;
} catch (WebServiceException e) {
if (log.isDebugEnabled()) {
log.debug("BaseDispatch.invokeAsync() [Polling]: Asynchronous invocation failed, " +
"caught a WebServiceException: ", e);
}
throw e;
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("BaseDispatch.invokeAsync() [Polling]: Asynchronous invocation failed, " +
"caught an Exception, wrapping into a WebServiceException. Exception caught: ",e);
}
// All exceptions are caught and rethrown as a WebServiceException
throw ExceptionFactory.makeWebServiceException(e);
}
}
public void setServiceClient(ServiceClient sc) {
serviceClient = sc;
}
public Mode getMode() {
return mode;
}
public void setMode(Mode m) {
mode = m;
}
/**
* Returns the fault that is contained within the MessageContext for an invocation. If no fault
* exists, null will be returned.
*
* @param msgCtx
* @return
*/
public static WebServiceException getFaultResponse(MessageContext msgCtx) {
try {
Message msg = msgCtx.getMessage();
if (msg != null && msg.isFault()) {
//XMLFault fault = msg.getXMLFault();
// 4.3.2 conformance bullet 1 requires a ProtocolException here
ProtocolException pe =
MethodMarshallerUtils.createSystemException(msg.getXMLFault(), msg);
if (msgCtx.getLocalException() != null) {
// If a local exception occured, set it as the initial cause of the
// exception that will be returned
ExceptionFactory.setInitialCause(pe, msgCtx.getLocalException());
}
return pe;
} else if (msgCtx.getLocalException() != null) {
// use the factory, it'll throw the right thing:
return ExceptionFactory.makeWebServiceException(msgCtx.getLocalException());
}
} finally {
// Free the incoming input stream
try {
msgCtx.freeInputStream();
} catch (IOException ioe) {
return ExceptionFactory.makeWebServiceException(ioe);
}
}
return null;
}
/**
* Returns a boolean indicating whether or not the MessageContext contained a fault.
*
* @param msgCtx
* @return
*/
public boolean hasFaultResponse(MessageContext msgCtx) {
if(!msgCtx.getAxisMessageContext().getOptions().isExceptionToBeThrownOnSOAPFault()){
if(log.isDebugEnabled()){
log.debug("msgCtx.Options.isExceptionToBeThrownOnSOAPFault set to false; Exception will not be thrown on fault");
}
return false;
}
if (msgCtx.getMessage() != null && msgCtx.getMessage().isFault())
return true;
else if (msgCtx.getLocalException() != null)
return true;
else
return false;
}
/*
* Configure any properties that will be needed on the Message
*/
private void setupMessageProperties(Message msg) {
// If the user has enabled MTOM on the SOAPBinding, we need
// to make sure that gets pushed to the Message object.
Binding binding = (Binding) getBinding();
if (binding != null && binding instanceof SOAPBinding) {
SOAPBinding soapBinding = (SOAPBinding)binding;
if (soapBinding.isMTOMEnabled())
msg.setMTOMEnabled(true);
}
}
/*
* Checks to see if the parameter for the invocation is valid
* given the scenario that the client is operating in. There are
* some cases when nulls are allowed and others where it is
* an error.
*/
private boolean isValidInvocationParam(Object object) {
String bindingId = endpointDesc.getClientBindingID();
// If no bindingId was found, use the default.
if (bindingId == null) {
bindingId = SOAPBinding.SOAP11HTTP_BINDING;
}
// If it's not an HTTP_BINDING, then we can allow for null params,
// but only in PAYLOAD mode per JAX-WS Section 4.3.2.
if (!bindingId.equals(HTTPBinding.HTTP_BINDING)) {
if (mode.equals(Mode.MESSAGE) && object == null) {
throw ExceptionFactory.makeWebServiceException(Messages.getMessage("dispatchNullParamMessageMode"));
}
} else {
// In all cases (PAYLOAD and MESSAGE) we must throw a WebServiceException
// if the parameter is null and request method is POST or PUT.
if (object == null && isPOSTorPUTRequest()) {
throw ExceptionFactory.makeWebServiceException(Messages.getMessage("dispatchNullParamHttpBinding"));
}
}
if (object instanceof DOMSource) {
DOMSource ds = (DOMSource)object;
if (ds.getNode() == null && ds.getSystemId() == null) {
throw ExceptionFactory.makeWebServiceException(Messages.getMessage("dispatchBadDOMSource"));
}
}
// If we've gotten this far, then all is good.
return true;
}
private boolean isPOSTorPUTRequest() {
String method = (String)this.requestContext.get(javax.xml.ws.handler.MessageContext.HTTP_REQUEST_METHOD);
// if HTTP_REQUEST_METHOD is not specified, assume it is a POST method
return (method == null ||
HTTPConstants.HEADER_POST.equalsIgnoreCase(method) ||
HTTPConstants.HEADER_PUT.equalsIgnoreCase(method));
}
private Message createRequestMessage(Object obj) throws WebServiceException {
// Check to see if the object is a valid invocation parameter.
// Then create the message from the object.
// If an exception occurs, it is local to the client and therefore is a
// WebServiceException (and not ProtocolExceptions).
// This code complies with JAX-WS 2.0 sections 4.3.2, 4.3.3 and 4.3.4.
if (!isValidInvocationParam(obj)) {
throw ExceptionFactory.makeWebServiceException(Messages.getMessage("dispatchInvalidParam"));
}
Message requestMsg = null;
try {
requestMsg = createMessageFromValue(obj);
} catch (Throwable t) {
// The webservice exception wraps the thrown exception.
throw ExceptionFactory.makeWebServiceException(t);
}
return requestMsg;
}
private void preInvokeInit(InvocationContext requestIC) {
/*
* if SESSION_MAINTAIN_PROPERTY is true, and the client app has explicitly set a HEADER_COOKIE on the request context, assume the client
* app is expecting the HEADER_COOKIE to be the session id. If we were establishing a new session, no cookie would be sent, and the
* server would reply with a "Set-Cookie" header, which is copied as a "Cookie"-keyed property to the service context during response.
* In this case, if we succeed in using an existing server session, no "Set-Cookie" header will be returned, and therefore no
* "Cookie"-keyed property would be set on the service context. So, let's copy our request context HEADER_COOKIE key to the service
* context now to prevent the "no cookie" exception in BindingProvider.setupSessionContext. It is possible the server does not support
* sessions, in which case no error occurs, but the client app would assume it is participating in a session.
*/
if ((requestContext.containsKey(BindingProvider.SESSION_MAINTAIN_PROPERTY)) && ((Boolean)requestContext.get(BindingProvider.SESSION_MAINTAIN_PROPERTY))) {
if ((requestContext.containsKey(HTTPConstants.HEADER_COOKIE)) && (requestContext.get(HTTPConstants.HEADER_COOKIE) != null)) {
if (requestIC.getServiceClient().getServiceContext().getProperty(HTTPConstants.HEADER_COOKIE) == null) {
requestIC.getServiceClient().getServiceContext().setProperty(HTTPConstants.HEADER_COOKIE, requestContext.get(HTTPConstants.HEADER_COOKIE));
if (log.isDebugEnabled()) {
log.debug("Client-app defined Cookie property (assume to be session cookie) on request context copied to service context." +
" Caution: server may or may not support sessions, but client app will not be informed when not supported.");
}
}
}
}
}
}