001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.axis2.jaxws.client.dispatch;
020:
021: import java.net.HttpURLConnection;
022: import java.util.concurrent.Executor;
023: import java.util.concurrent.Future;
024:
025: import javax.xml.soap.SOAPBody;
026: import javax.xml.soap.SOAPConstants;
027: import javax.xml.soap.SOAPFactory;
028: import javax.xml.soap.SOAPFault;
029: import javax.xml.transform.dom.DOMSource;
030: import javax.xml.ws.AsyncHandler;
031: import javax.xml.ws.Binding;
032: import javax.xml.ws.ProtocolException;
033: import javax.xml.ws.Response;
034: import javax.xml.ws.WebServiceException;
035: import javax.xml.ws.Service.Mode;
036: import javax.xml.ws.http.HTTPBinding;
037: import javax.xml.ws.http.HTTPException;
038: import javax.xml.ws.soap.SOAPBinding;
039: import javax.xml.ws.soap.SOAPFaultException;
040:
041: import org.apache.axis2.client.ServiceClient;
042: import org.apache.axis2.jaxws.BindingProvider;
043: import org.apache.axis2.jaxws.ExceptionFactory;
044: import org.apache.axis2.jaxws.client.async.AsyncResponse;
045: import org.apache.axis2.jaxws.core.InvocationContext;
046: import org.apache.axis2.jaxws.core.InvocationContextFactory;
047: import org.apache.axis2.jaxws.core.MessageContext;
048: import org.apache.axis2.jaxws.core.controller.AxisInvocationController;
049: import org.apache.axis2.jaxws.core.controller.InvocationController;
050: import org.apache.axis2.jaxws.description.EndpointDescription;
051: import org.apache.axis2.jaxws.i18n.Messages;
052: import org.apache.axis2.jaxws.marshaller.impl.alt.MethodMarshallerUtils;
053: import org.apache.axis2.jaxws.message.Message;
054: import org.apache.axis2.jaxws.message.Protocol;
055: import org.apache.axis2.jaxws.message.util.XMLFaultUtils;
056: import org.apache.axis2.jaxws.spi.Constants;
057: import org.apache.axis2.jaxws.spi.ServiceDelegate;
058: import org.apache.axis2.jaxws.spi.migrator.ApplicationContextMigratorUtil;
059: import org.apache.axis2.jaxws.utility.SAAJFactory;
060: import org.apache.commons.logging.Log;
061: import org.apache.commons.logging.LogFactory;
062:
063: public abstract class BaseDispatch<T> extends BindingProvider implements
064: javax.xml.ws.Dispatch {
065:
066: private Log log = LogFactory.getLog(BaseDispatch.class);
067:
068: protected InvocationController ic;
069:
070: protected ServiceClient serviceClient;
071:
072: protected Mode mode;
073:
074: protected BaseDispatch(ServiceDelegate svcDelgate,
075: EndpointDescription epDesc) {
076: super (svcDelgate, epDesc);
077:
078: ic = new AxisInvocationController();
079: }
080:
081: /**
082: * Take the input object and turn it into an OMElement so that it can be sent.
083: *
084: * @param value
085: * @return
086: */
087: protected abstract Message createMessageFromValue(Object value);
088:
089: /**
090: * Given a message, return the business object based on the requestor's required format (PAYLOAD
091: * vs. MESSAGE) and datatype.
092: *
093: * @param message
094: * @return
095: */
096: protected abstract Object getValueFromMessage(Message message);
097:
098: /**
099: * Creates an instance of the AsyncListener that is to be used for waiting for async responses.
100: *
101: * @return a configured AsyncListener instance
102: */
103: protected abstract AsyncResponse createAsyncResponseListener();
104:
105: public Object invoke(Object obj) throws WebServiceException {
106:
107: // Catch all exceptions and rethrow an appropriate WebService Exception
108: try {
109: if (log.isDebugEnabled()) {
110: log
111: .debug("Entered synchronous invocation: BaseDispatch.invoke()");
112: }
113:
114: // Create the InvocationContext instance for this request/response flow.
115: InvocationContext invocationContext = InvocationContextFactory
116: .createInvocationContext(null);
117: invocationContext.setServiceClient(serviceClient);
118:
119: // Create the MessageContext to hold the actual request message and its
120: // associated properties
121: MessageContext requestMsgCtx = new MessageContext();
122: requestMsgCtx
123: .setEndpointDescription(getEndpointDescription());
124: invocationContext.setRequestMessageContext(requestMsgCtx);
125:
126: /*
127: * TODO: review: make sure the handlers are set on the InvocationContext
128: * This implementation of the JAXWS runtime does not use Endpoint, which
129: * would normally be the place to initialize and store the handler list.
130: * In lieu of that, we will have to intialize and store them on the
131: * InvocationContext. also see the InvocationContextFactory. On the client
132: * side, the binding is not yet set when we call into that factory, so the
133: * handler list doesn't get set on the InvocationContext object there. Thus
134: * we gotta do it here.
135: */
136:
137: // be sure to use whatever handlerresolver is registered on the Service
138: invocationContext.setHandlers(getBinding()
139: .getHandlerChain());
140:
141: Message requestMsg = null;
142: try {
143: if (isValidInvocationParam(obj)) {
144: requestMsg = createMessageFromValue(obj);
145: } else {
146: throw ExceptionFactory
147: .makeWebServiceException(Messages
148: .getMessage("dispatchInvalidParam"));
149: }
150: } catch (Exception e) {
151: throw getProtocolException(e);
152: }
153:
154: setupMessageProperties(requestMsg);
155: requestMsgCtx.setMessage(requestMsg);
156:
157: // Migrate the properties from the client request context bag to
158: // the request MessageContext.
159: ApplicationContextMigratorUtil
160: .performMigrationToMessageContext(
161: Constants.APPLICATION_CONTEXT_MIGRATOR_LIST_ID,
162: getRequestContext(), requestMsgCtx);
163:
164: // Send the request using the InvocationController
165: ic.invoke(invocationContext);
166:
167: MessageContext responseMsgCtx = invocationContext
168: .getResponseMessageContext();
169: responseMsgCtx.setEndpointDescription(requestMsgCtx
170: .getEndpointDescription());
171:
172: // Migrate the properties from the response MessageContext back
173: // to the client response context bag.
174: ApplicationContextMigratorUtil
175: .performMigrationFromMessageContext(
176: Constants.APPLICATION_CONTEXT_MIGRATOR_LIST_ID,
177: getResponseContext(), responseMsgCtx);
178:
179: if (hasFaultResponse(responseMsgCtx)) {
180: WebServiceException wse = BaseDispatch
181: .getFaultResponse(responseMsgCtx);
182: throw wse;
183: }
184:
185: Message responseMsg = responseMsgCtx.getMessage();
186: Object returnObj = getValueFromMessage(responseMsg);
187:
188: //Check to see if we need to maintain session state
189: checkMaintainSessionState(requestMsgCtx, invocationContext);
190:
191: if (log.isDebugEnabled()) {
192: log
193: .debug("Synchronous invocation completed: BaseDispatch.invoke()");
194: }
195:
196: return returnObj;
197: } catch (WebServiceException e) {
198: throw e;
199: } catch (Exception e) {
200: // All exceptions are caught and rethrown as a WebServiceException
201: throw ExceptionFactory.makeWebServiceException(e);
202: }
203: }
204:
205: public void invokeOneWay(Object obj) throws WebServiceException {
206:
207: // All exceptions are caught and rethrown as a WebServiceException
208: try {
209: if (log.isDebugEnabled()) {
210: log
211: .debug("Entered one-way invocation: BaseDispatch.invokeOneWay()");
212: }
213:
214: // Create the InvocationContext instance for this request/response flow.
215: InvocationContext invocationContext = InvocationContextFactory
216: .createInvocationContext(null);
217: invocationContext.setServiceClient(serviceClient);
218:
219: // Create the MessageContext to hold the actual request message and its
220: // associated properties
221: MessageContext requestMsgCtx = new MessageContext();
222: requestMsgCtx
223: .setEndpointDescription(getEndpointDescription());
224: invocationContext.setRequestMessageContext(requestMsgCtx);
225:
226: Message requestMsg = null;
227: try {
228: if (isValidInvocationParam(obj)) {
229: requestMsg = createMessageFromValue(obj);
230: } else {
231: throw ExceptionFactory
232: .makeWebServiceException(Messages
233: .getMessage("dispatchInvalidParam"));
234: }
235: } catch (Exception e) {
236: throw getProtocolException(e);
237: }
238:
239: setupMessageProperties(requestMsg);
240: requestMsgCtx.setMessage(requestMsg);
241:
242: // Migrate the properties from the client request context bag to
243: // the request MessageContext.
244: ApplicationContextMigratorUtil
245: .performMigrationToMessageContext(
246: Constants.APPLICATION_CONTEXT_MIGRATOR_LIST_ID,
247: getRequestContext(), requestMsgCtx);
248:
249: // Send the request using the InvocationController
250: ic.invokeOneWay(invocationContext);
251:
252: //Check to see if we need to maintain session state
253: checkMaintainSessionState(requestMsgCtx, invocationContext);
254:
255: if (log.isDebugEnabled()) {
256: log
257: .debug("One-way invocation completed: BaseDispatch.invokeOneWay()");
258: }
259:
260: return;
261: } catch (WebServiceException e) {
262: throw e;
263: } catch (Exception e) {
264: // All exceptions are caught and rethrown as a WebServiceException
265: throw ExceptionFactory.makeWebServiceException(e);
266: }
267: }
268:
269: public Future<?> invokeAsync(Object obj, AsyncHandler asynchandler)
270: throws WebServiceException {
271:
272: // All exceptions are caught and rethrown as a WebServiceException
273: try {
274: if (log.isDebugEnabled()) {
275: log
276: .debug("Entered asynchronous (callback) invocation: BaseDispatch.invokeAsync()");
277: }
278:
279: // Create the InvocationContext instance for this request/response flow.
280: InvocationContext invocationContext = InvocationContextFactory
281: .createInvocationContext(null);
282: invocationContext.setServiceClient(serviceClient);
283:
284: // Create the MessageContext to hold the actual request message and its
285: // associated properties
286: MessageContext requestMsgCtx = new MessageContext();
287: requestMsgCtx
288: .setEndpointDescription(getEndpointDescription());
289: invocationContext.setRequestMessageContext(requestMsgCtx);
290:
291: Message requestMsg = null;
292: if (isValidInvocationParam(obj)) {
293: requestMsg = createMessageFromValue(obj);
294: } else {
295: throw ExceptionFactory.makeWebServiceException(Messages
296: .getMessage("dispatchInvalidParam"));
297: }
298:
299: setupMessageProperties(requestMsg);
300: requestMsgCtx.setMessage(requestMsg);
301:
302: // Migrate the properties from the client request context bag to
303: // the request MessageContext.
304: ApplicationContextMigratorUtil
305: .performMigrationToMessageContext(
306: Constants.APPLICATION_CONTEXT_MIGRATOR_LIST_ID,
307: getRequestContext(), requestMsgCtx);
308:
309: // Setup the Executor that will be used to drive async responses back to
310: // the client.
311: // FIXME: We shouldn't be getting this from the ServiceDelegate, rather each
312: // Dispatch object should have it's own.
313: Executor e = serviceDelegate.getExecutor();
314: invocationContext.setExecutor(e);
315:
316: // Create the AsyncListener that is to be used by the InvocationController.
317: AsyncResponse listener = createAsyncResponseListener();
318: invocationContext.setAsyncResponseListener(listener);
319:
320: // Send the request using the InvocationController
321: Future<?> asyncResponse = ic.invokeAsync(invocationContext,
322: asynchandler);
323:
324: //Check to see if we need to maintain session state
325: checkMaintainSessionState(requestMsgCtx, invocationContext);
326:
327: if (log.isDebugEnabled()) {
328: log
329: .debug("Asynchronous (callback) invocation sent: BaseDispatch.invokeAsync()");
330: }
331:
332: return asyncResponse;
333: } catch (WebServiceException e) {
334: throw e;
335: } catch (Exception e) {
336: // All exceptions are caught and rethrown as a WebServiceException
337: throw ExceptionFactory.makeWebServiceException(e);
338: }
339: }
340:
341: public Response invokeAsync(Object obj) throws WebServiceException {
342:
343: // All exceptions are caught and rethrown as a WebServiceException
344: try {
345: if (log.isDebugEnabled()) {
346: log
347: .debug("Entered asynchronous (polling) invocation: BaseDispatch.invokeAsync()");
348: }
349:
350: // Create the InvocationContext instance for this request/response flow.
351: InvocationContext invocationContext = InvocationContextFactory
352: .createInvocationContext(null);
353: invocationContext.setServiceClient(serviceClient);
354:
355: // Create the MessageContext to hold the actual request message and its
356: // associated properties
357: MessageContext requestMsgCtx = new MessageContext();
358: requestMsgCtx
359: .setEndpointDescription(getEndpointDescription());
360: invocationContext.setRequestMessageContext(requestMsgCtx);
361:
362: Message requestMsg = null;
363: if (isValidInvocationParam(obj)) {
364: requestMsg = createMessageFromValue(obj);
365: } else {
366: throw ExceptionFactory.makeWebServiceException(Messages
367: .getMessage("dispatchInvalidParam"));
368: }
369:
370: setupMessageProperties(requestMsg);
371: requestMsgCtx.setMessage(requestMsg);
372:
373: // Migrate the properties from the client request context bag to
374: // the request MessageContext.
375: ApplicationContextMigratorUtil
376: .performMigrationToMessageContext(
377: Constants.APPLICATION_CONTEXT_MIGRATOR_LIST_ID,
378: getRequestContext(), requestMsgCtx);
379:
380: // Setup the Executor that will be used to drive async responses back to
381: // the client.
382: // FIXME: We shouldn't be getting this from the ServiceDelegate, rather each
383: // Dispatch object should have it's own.
384: Executor e = serviceDelegate.getExecutor();
385: invocationContext.setExecutor(e);
386:
387: // Create the AsyncListener that is to be used by the InvocationController.
388: AsyncResponse listener = createAsyncResponseListener();
389: invocationContext.setAsyncResponseListener(listener);
390:
391: // Send the request using the InvocationController
392: Response asyncResponse = ic.invokeAsync(invocationContext);
393:
394: //Check to see if we need to maintain session state
395: checkMaintainSessionState(requestMsgCtx, invocationContext);
396:
397: if (log.isDebugEnabled()) {
398: log
399: .debug("Asynchronous (polling) invocation sent: BaseDispatch.invokeAsync()");
400: }
401:
402: return asyncResponse;
403: } catch (WebServiceException e) {
404: throw e;
405: } catch (Exception e) {
406: // All exceptions are caught and rethrown as a WebServiceException
407: throw ExceptionFactory.makeWebServiceException(e);
408: }
409: }
410:
411: public void setServiceClient(ServiceClient sc) {
412: serviceClient = sc;
413: }
414:
415: public Mode getMode() {
416: return mode;
417: }
418:
419: public void setMode(Mode m) {
420: mode = m;
421: }
422:
423: /**
424: * Returns the fault that is contained within the MessageContext for an invocation. If no fault
425: * exists, null will be returned.
426: *
427: * @param msgCtx
428: * @return
429: */
430: public static WebServiceException getFaultResponse(
431: MessageContext msgCtx) {
432: Message msg = msgCtx.getMessage();
433: if (msg != null && msg.isFault()) {
434: //XMLFault fault = msg.getXMLFault();
435: // 4.3.2 conformance bullet 1 requires a ProtocolException here
436: ProtocolException pe = MethodMarshallerUtils
437: .createSystemException(msg.getXMLFault(), msg);
438: return pe;
439: } else if (msgCtx.getLocalException() != null) {
440: // use the factory, it'll throw the right thing:
441: return ExceptionFactory.makeWebServiceException(msgCtx
442: .getLocalException());
443: }
444:
445: return null;
446: }
447:
448: private ProtocolException getProtocolException(Exception e) {
449: if (getBinding() instanceof SOAPBinding) {
450: // Throw a SOAPFaultException
451: if (log.isDebugEnabled()) {
452: log.debug("Constructing SOAPFaultException for " + e);
453: }
454: try {
455: SOAPFault soapFault = SOAPFactory.newInstance()
456: .createFault();
457: soapFault.setFaultString(e.getMessage());
458: return new SOAPFaultException(soapFault);
459: } catch (Exception ex) {
460: if (log.isDebugEnabled()) {
461: log
462: .debug(
463: "Exception occurred during fault processing:",
464: ex);
465: }
466: return ExceptionFactory.makeProtocolException(e
467: .getMessage(), null);
468: }
469: } else if (getBinding() instanceof HTTPBinding) {
470: if (log.isDebugEnabled()) {
471: log.debug("Constructing ProtocolException for " + e);
472: }
473: HTTPException ex = new HTTPException(
474: HttpURLConnection.HTTP_INTERNAL_ERROR);
475: ex.initCause(new Throwable(e.getMessage()));
476: return ex;
477: } else {
478: if (log.isDebugEnabled()) {
479: log.debug("Constructing ProtocolException for " + e);
480: }
481: return ExceptionFactory.makeProtocolException(e
482: .getMessage(), null);
483: }
484: }
485:
486: /**
487: * Returns a boolean indicating whether or not the MessageContext contained a fault.
488: *
489: * @param msgCtx
490: * @return
491: */
492: public boolean hasFaultResponse(MessageContext msgCtx) {
493: if (msgCtx.getMessage() != null
494: && msgCtx.getMessage().isFault())
495: return true;
496: else if (msgCtx.getLocalException() != null)
497: return true;
498: else
499: return false;
500: }
501:
502: /*
503: * Configure any properties that will be needed on the Message
504: */
505: private void setupMessageProperties(Message msg) {
506: // If the user has enabled MTOM on the SOAPBinding, we need
507: // to make sure that gets pushed to the Message object.
508: Binding binding = getBinding();
509: if (binding != null && binding instanceof SOAPBinding) {
510: SOAPBinding soapBinding = (SOAPBinding) binding;
511: if (soapBinding.isMTOMEnabled())
512: msg.setMTOMEnabled(true);
513: }
514:
515: // Check if the user enabled MTOM using the SOAP binding
516: // properties for MTOM
517: String bindingID = endpointDesc.getClientBindingID();
518: if ((bindingID
519: .equalsIgnoreCase(SOAPBinding.SOAP11HTTP_MTOM_BINDING) || bindingID
520: .equalsIgnoreCase(SOAPBinding.SOAP12HTTP_MTOM_BINDING))
521: && !msg.isMTOMEnabled()) {
522: msg.setMTOMEnabled(true);
523: }
524: }
525:
526: /*
527: * Checks to see if the parameter for the invocation is valid
528: * given the scenario that the client is operating in. There are
529: * some cases when nulls are allowed and others where it is
530: * an error.
531: */
532: private boolean isValidInvocationParam(Object object) {
533: String bindingId = endpointDesc.getClientBindingID();
534:
535: // If no bindingId was found, use the default.
536: if (bindingId == null) {
537: bindingId = SOAPBinding.SOAP11HTTP_BINDING;
538: }
539:
540: // If it's not an HTTP_BINDING, then we can allow for null params,
541: // but only in PAYLOAD mode per JAX-WS Section 4.3.2.
542: if (!bindingId.equals(HTTPBinding.HTTP_BINDING)) {
543: if (mode.equals(Mode.MESSAGE) && object == null) {
544: throw ExceptionFactory.makeWebServiceException(Messages
545: .getMessage("dispatchNullParamMessageMode"));
546: }
547: } else {
548: // In all cases (PAYLOAD and MESSAGE) we must throw a WebServiceException
549: // if the parameter is null.
550: if (object == null) {
551: throw ExceptionFactory.makeWebServiceException(Messages
552: .getMessage("dispatchNullParamHttpBinding"));
553: }
554: }
555:
556: if (object instanceof DOMSource) {
557: DOMSource ds = (DOMSource) object;
558: if (ds.getNode() == null && ds.getSystemId() == null) {
559: throw ExceptionFactory.makeWebServiceException(Messages
560: .getMessage("dispatchBadDOMSource"));
561: }
562: }
563:
564: // If we've gotten this far, then all is good.
565: return true;
566: }
567: }
|