/* * 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."); } } } } } }