001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: package com.sun.xml.ws.client.dispatch;
038:
039: import com.sun.istack.NotNull;
040: import com.sun.istack.Nullable;
041: import com.sun.xml.ws.api.BindingID;
042: import com.sun.xml.ws.api.SOAPVersion;
043: import com.sun.xml.ws.api.WSBinding;
044: import com.sun.xml.ws.api.addressing.WSEndpointReference;
045: import com.sun.xml.ws.api.message.Attachment;
046: import com.sun.xml.ws.api.message.AttachmentSet;
047: import com.sun.xml.ws.api.message.Message;
048: import com.sun.xml.ws.api.message.Packet;
049: import com.sun.xml.ws.api.pipe.Fiber;
050: import com.sun.xml.ws.api.pipe.Tube;
051: import com.sun.xml.ws.binding.BindingImpl;
052: import com.sun.xml.ws.client.AsyncInvoker;
053: import com.sun.xml.ws.client.AsyncResponseImpl;
054: import com.sun.xml.ws.client.RequestContext;
055: import com.sun.xml.ws.client.ResponseContext;
056: import com.sun.xml.ws.client.ResponseContextReceiver;
057: import com.sun.xml.ws.client.Stub;
058: import com.sun.xml.ws.client.WSServiceDelegate;
059: import com.sun.xml.ws.encoding.soap.DeserializationException;
060: import com.sun.xml.ws.fault.SOAPFaultBuilder;
061: import com.sun.xml.ws.message.AttachmentSetImpl;
062: import com.sun.xml.ws.message.DataHandlerAttachment;
063: import com.sun.xml.ws.resources.DispatchMessages;
064:
065: import javax.activation.DataHandler;
066: import javax.xml.bind.JAXBException;
067: import javax.xml.namespace.QName;
068: import javax.xml.transform.Source;
069: import javax.xml.ws.AsyncHandler;
070: import javax.xml.ws.BindingProvider;
071: import javax.xml.ws.Dispatch;
072: import javax.xml.ws.Response;
073: import javax.xml.ws.Service;
074: import javax.xml.ws.Service.Mode;
075: import javax.xml.ws.WebServiceException;
076: import javax.xml.ws.handler.MessageContext;
077: import javax.xml.ws.http.HTTPBinding;
078: import javax.xml.ws.soap.SOAPBinding;
079: import javax.xml.ws.soap.SOAPFaultException;
080: import java.net.MalformedURLException;
081: import java.net.URI;
082: import java.net.URISyntaxException;
083: import java.net.URL;
084: import java.util.ArrayList;
085: import java.util.HashMap;
086: import java.util.List;
087: import java.util.Map;
088: import java.util.concurrent.Callable;
089: import java.util.concurrent.ExecutorService;
090: import java.util.concurrent.Future;
091: import java.util.concurrent.TimeUnit;
092:
093: /**
094: * The <code>DispatchImpl</code> abstract class provides support
095: * for the dynamic invocation of a service endpoint operation using XML
096: * constructs, JAXB objects or <code>SOAPMessage</code>. The <code>javax.xml.ws.Service</code>
097: * interface acts as a factory for the creation of <code>DispatchImpl</code>
098: * instances.
099: *
100: * @author WS Development Team
101: * @version 1.0
102: */
103: public abstract class DispatchImpl<T> extends Stub implements
104: Dispatch<T> {
105:
106: final Service.Mode mode;
107: final QName portname;
108: final SOAPVersion soapVersion;
109: static final long AWAIT_TERMINATION_TIME = 800L;
110:
111: /**
112: *
113: * @param port dispatch instance is asssociated with this wsdl port qName
114: * @param mode Service.mode associated with this Dispatch instance - Service.mode.MESSAGE or Service.mode.PAYLOAD
115: * @param owner Service that created the Dispatch
116: * @param pipe Master pipe for the pipeline
117: * @param binding Binding of this Dispatch instance, current one of SOAP/HTTP or XML/HTTP
118: */
119: protected DispatchImpl(QName port, Service.Mode mode,
120: WSServiceDelegate owner, Tube pipe, BindingImpl binding,
121: @Nullable
122: WSEndpointReference epr) {
123: super (owner, pipe, binding,
124: (owner.getWsdlService() != null) ? owner
125: .getWsdlService().get(port) : null, owner
126: .getEndpointAddress(port), epr);
127: this .portname = port;
128: this .mode = mode;
129: this .soapVersion = binding.getSOAPVersion();
130: }
131:
132: /**
133: * Abstract method that is implemented by each concrete Dispatch class
134: * @param msg message passed in from the client program on the invocation
135: * @return The Message created returned as the Interface in actuallity a
136: * concrete Message Type
137: */
138: abstract Packet createPacket(T msg);
139:
140: /**
141: * Obtains the value to return from the response message.
142: */
143: abstract T toReturnValue(Packet response);
144:
145: public final Response<T> invokeAsync(T param) {
146: AsyncInvoker invoker = new DispatchAsyncInvoker(param);
147: AsyncResponseImpl<T> ft = new AsyncResponseImpl<T>(invoker,
148: null);
149: invoker.setReceiver(ft);
150: // TODO: Do we set this executor on Engine and run the AsyncInvoker in this thread ?
151: owner.getExecutor().execute(ft);
152: return ft;
153: }
154:
155: public final Future<?> invokeAsync(T param,
156: AsyncHandler<T> asyncHandler) {
157: AsyncInvoker invoker = new DispatchAsyncInvoker(param);
158: AsyncResponseImpl<T> ft = new AsyncResponseImpl<T>(invoker,
159: asyncHandler);
160: invoker.setReceiver(ft);
161:
162: // temp needed so that unit tests run and complete otherwise they may
163: //not. Need a way to put this in the test harness or other way to do this
164: //todo: as above
165: ExecutorService exec = (ExecutorService) owner.getExecutor();
166: try {
167: exec.awaitTermination(AWAIT_TERMINATION_TIME,
168: TimeUnit.MICROSECONDS);
169: } catch (InterruptedException e) {
170: throw new WebServiceException(e);
171: }
172: exec.execute(ft);
173: return ft;
174: }
175:
176: /**
177: * Synchronously invokes a service.
178: *
179: * See {@link #process(Packet, RequestContext, ResponseContextReceiver)} on
180: * why it takes a {@link RequestContext} and {@link ResponseContextReceiver} as a parameter.
181: */
182: public final T doInvoke(T in, RequestContext rc,
183: ResponseContextReceiver receiver) {
184: Packet response;
185: try {
186: checkNullAllowed(in, rc, binding, mode);
187:
188: Packet message = createPacket(in);
189: resolveEndpointAddress(message, rc);
190: setProperties(message, true);
191: response = process(message, rc, receiver);
192: Message msg = response.getMessage();
193:
194: if (msg != null && msg.isFault()) {
195: SOAPFaultBuilder faultBuilder = SOAPFaultBuilder
196: .create(msg);
197: // passing null means there is no checked excpetion we're looking for all
198: // it will get back to us is a protocol exception
199: throw (SOAPFaultException) faultBuilder
200: .createException(null);
201: }
202: } catch (JAXBException e) {
203: //TODO: i18nify
204: throw new DeserializationException(DispatchMessages
205: .INVALID_RESPONSE_DESERIALIZATION(), e);
206: } catch (WebServiceException e) {
207: //it could be a WebServiceException or a ProtocolException
208: throw e;
209: } catch (Throwable e) {
210: // it could be a RuntimeException resulting due to some internal bug or
211: // its some other exception resulting from user error, wrap it in
212: // WebServiceException
213: throw new WebServiceException(e);
214: }
215:
216: return toReturnValue(response);
217: }
218:
219: public final T invoke(T in) {
220: return doInvoke(in, requestContext, this );
221: }
222:
223: public final void invokeOneWay(T in) {
224: try {
225: checkNullAllowed(in, requestContext, binding, mode);
226:
227: Packet request = createPacket(in);
228: setProperties(request, false);
229: Packet response = process(request, requestContext, this );
230: } catch (WebServiceException e) {
231: //it could be a WebServiceException or a ProtocolException
232: throw e;
233: } catch (Throwable e) {
234: // it could be a RuntimeException resulting due to some internal bug or
235: // its some other exception resulting from user error, wrap it in
236: // WebServiceException
237: throw new WebServiceException(e);
238: }
239: }
240:
241: void setProperties(Packet packet, boolean expectReply) {
242: packet.expectReply = expectReply;
243: }
244:
245: static boolean isXMLHttp(@NotNull
246: WSBinding binding) {
247: return binding.getBindingId().equals(BindingID.XML_HTTP);
248: }
249:
250: static boolean isPAYLOADMode(@NotNull
251: Service.Mode mode) {
252: return mode == Service.Mode.PAYLOAD;
253: }
254:
255: static void checkNullAllowed(@Nullable
256: Object in, RequestContext rc, WSBinding binding, Service.Mode mode) {
257:
258: if (in != null)
259: return;
260:
261: //With HTTP Binding a null invocation parameter can not be used
262: //with HTTP Request Method == POST
263: if (isXMLHttp(binding)) {
264: if (methodNotOk(rc))
265: throw new WebServiceException(DispatchMessages
266: .INVALID_NULLARG_XMLHTTP_REQUEST_METHOD(
267: HTTP_REQUEST_METHOD_POST,
268: HTTP_REQUEST_METHOD_GET));
269: } else { //soapBinding
270: if (mode == Service.Mode.MESSAGE)
271: throw new WebServiceException(DispatchMessages
272: .INVALID_NULLARG_SOAP_MSGMODE(mode.name(),
273: Service.Mode.PAYLOAD.toString()));
274: }
275: }
276:
277: static boolean methodNotOk(@NotNull
278: RequestContext rc) {
279: String requestMethod = (String) rc
280: .get(MessageContext.HTTP_REQUEST_METHOD);
281: String request = (requestMethod == null) ? HTTP_REQUEST_METHOD_POST
282: : requestMethod;
283: // if method == post or put with a null invocation parameter in xml/http binding this is not ok
284: return HTTP_REQUEST_METHOD_POST.equalsIgnoreCase(request)
285: || HTTP_REQUEST_METHOD_PUT.equalsIgnoreCase(request);
286: }
287:
288: public static void checkValidSOAPMessageDispatch(WSBinding binding,
289: Service.Mode mode) {
290: // Dispatch<SOAPMessage> is only valid for soap binding and in Service.Mode.MESSAGE
291: if (DispatchImpl.isXMLHttp(binding))
292: throw new WebServiceException(DispatchMessages
293: .INVALID_SOAPMESSAGE_DISPATCH_BINDING(
294: HTTPBinding.HTTP_BINDING,
295: SOAPBinding.SOAP11HTTP_BINDING + " or "
296: + SOAPBinding.SOAP12HTTP_BINDING));
297: if (DispatchImpl.isPAYLOADMode(mode))
298: throw new WebServiceException(DispatchMessages
299: .INVALID_SOAPMESSAGE_DISPATCH_MSGMODE(mode.name(),
300: Service.Mode.MESSAGE.toString()));
301: }
302:
303: public static void checkValidDataSourceDispatch(WSBinding binding,
304: Service.Mode mode) {
305: // Dispatch<DataSource> is only valid with xml/http binding and in Service.Mode.MESSAGE
306: if (!DispatchImpl.isXMLHttp(binding))
307: throw new WebServiceException(DispatchMessages
308: .INVALID_DATASOURCE_DISPATCH_BINDING("SOAP/HTTP",
309: HTTPBinding.HTTP_BINDING));
310: if (DispatchImpl.isPAYLOADMode(mode))
311: throw new WebServiceException(DispatchMessages
312: .INVALID_DATASOURCE_DISPATCH_MSGMODE(mode.name(),
313: Service.Mode.MESSAGE.toString()));
314: }
315:
316: protected final @NotNull
317: QName getPortName() {
318: return portname;
319: }
320:
321: void resolveEndpointAddress(@NotNull
322: Packet message, @NotNull
323: RequestContext requestContext) {
324: //resolve endpoint look for query parameters, pathInfo
325: String endpoint = (String) requestContext
326: .get(BindingProvider.ENDPOINT_ADDRESS_PROPERTY);
327: if (endpoint == null)
328: endpoint = message.endpointAddress.toString();
329:
330: String pathInfo = null;
331: String queryString = null;
332: if (requestContext.get(MessageContext.PATH_INFO) != null)
333: pathInfo = (String) requestContext
334: .get(MessageContext.PATH_INFO);
335:
336: if (requestContext.get(MessageContext.QUERY_STRING) != null)
337: queryString = (String) requestContext
338: .get(MessageContext.QUERY_STRING);
339:
340: String resolvedEndpoint = null;
341: if (pathInfo != null || queryString != null) {
342: pathInfo = checkPath(pathInfo);
343: queryString = checkQuery(queryString);
344: if (endpoint != null) {
345: try {
346: final URI endpointURI = new URI(endpoint);
347: resolvedEndpoint = resolveURI(endpointURI,
348: pathInfo, queryString);
349: } catch (URISyntaxException e) {
350: throw new WebServiceException(DispatchMessages
351: .INVALID_URI(endpoint));
352: }
353: }
354: requestContext.put(
355: BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
356: resolvedEndpoint);
357: //message.endpointAddress = EndpointAddress.create(resolvedEndpoint);
358: }
359: }
360:
361: protected @NotNull
362: String resolveURI(@NotNull
363: URI endpointURI, @Nullable
364: String pathInfo, @Nullable
365: String queryString) {
366: String query = null;
367: String fragment = null;
368: if (queryString != null) {
369: final URI result;
370: try {
371: URI tp = new URI(null, null, endpointURI.getPath(),
372: queryString, null);
373: result = endpointURI.resolve(tp);
374: } catch (URISyntaxException e) {
375: throw new WebServiceException(DispatchMessages
376: .INVALID_QUERY_STRING(queryString));
377: }
378: query = result.getQuery();
379: fragment = result.getFragment();
380: }
381:
382: final String path = (pathInfo != null) ? pathInfo : endpointURI
383: .getPath();
384: try {
385: //final URI temp = new URI(null, null, path, query, fragment);
386: //return endpointURI.resolve(temp).toURL().toExternalForm();
387: // Using the following HACK instead of the above to avoid double encoding of
388: // the query. Application's QUERY_STRING is encoded using URLEncoder.encode().
389: // If we use that query in URI's constructor, it is encoded again.
390: // URLEncoder's encoding is not the same as URI's encoding of the query.
391: // See {@link URL}
392: StringBuilder spec = new StringBuilder();
393: if (path != null) {
394: spec.append(path);
395: }
396: if (query != null) {
397: spec.append("?");
398: spec.append(query);
399: }
400: if (fragment != null) {
401: spec.append("#");
402: spec.append(fragment);
403: }
404: return new URL(endpointURI.toURL(), spec.toString())
405: .toExternalForm();
406: } catch (MalformedURLException e) {
407: throw new WebServiceException(DispatchMessages
408: .INVALID_URI_RESOLUTION(path));
409: }
410: }
411:
412: private static String checkPath(@Nullable
413: String path) {
414: //does it begin with /
415: return (path == null || path.startsWith("/")) ? path : "/"
416: + path;
417: }
418:
419: private static String checkQuery(@Nullable
420: String query) {
421: if (query == null)
422: return null;
423:
424: if (query.indexOf('?') == 0)
425: throw new WebServiceException(DispatchMessages
426: .INVALID_QUERY_LEADING_CHAR(query));
427: return query;
428: }
429:
430: protected AttachmentSet setOutboundAttachments() {
431: HashMap<String, DataHandler> attachments = (HashMap<String, DataHandler>) getRequestContext()
432: .get(MessageContext.OUTBOUND_MESSAGE_ATTACHMENTS);
433:
434: if (attachments != null) {
435: List<Attachment> alist = new ArrayList();
436: for (Map.Entry<String, DataHandler> att : attachments
437: .entrySet()) {
438: DataHandlerAttachment dha = new DataHandlerAttachment(
439: att.getKey(), att.getValue());
440: alist.add(dha);
441: }
442: return new AttachmentSetImpl(alist);
443: }
444: return new AttachmentSetImpl();
445: }
446:
447: /* private void getInboundAttachments(Message msg) {
448: AttachmentSet attachments = msg.getAttachments();
449: if (!attachments.isEmpty()) {
450: Map<String, DataHandler> in = new HashMap<String, DataHandler>();
451: for (Attachment attachment : attachments)
452: in.put(attachment.getContentId(), attachment.asDataHandler());
453: getResponseContext().put(MessageContext.INBOUND_MESSAGE_ATTACHMENTS, in);
454: }
455:
456: }
457: */
458:
459: /**
460: * Calls {@link DispatchImpl#doInvoke(Object,RequestContext,ResponseContextReceiver)}.
461: */
462: private class Invoker implements Callable {
463: private final T param;
464: // snapshot the context now. this is necessary to avoid concurrency issue,
465: // and is required by the spec
466: private final RequestContext rc = requestContext.copy();
467:
468: /**
469: * Because of the object instantiation order,
470: * we can't take this as a constructor parameter.
471: */
472: private ResponseContextReceiver receiver;
473:
474: Invoker(T param) {
475: this .param = param;
476: }
477:
478: public T call() throws Exception {
479: return doInvoke(param, rc, receiver);
480: }
481:
482: void setReceiver(ResponseContextReceiver receiver) {
483: this .receiver = receiver;
484: }
485: }
486:
487: /**
488: *
489: */
490: private class DispatchAsyncInvoker extends AsyncInvoker {
491: private final T param;
492: // snapshot the context now. this is necessary to avoid concurrency issue,
493: // and is required by the spec
494: private final RequestContext rc = requestContext.copy();
495:
496: DispatchAsyncInvoker(T param) {
497: this .param = param;
498: }
499:
500: public void do_run() {
501: checkNullAllowed(param, rc, binding, mode);
502: Packet message = createPacket(param);
503: resolveEndpointAddress(message, rc);
504: setProperties(message, true);
505: Fiber.CompletionCallback callback = new Fiber.CompletionCallback() {
506: public void onCompletion(@NotNull
507: Packet response) {
508: Message msg = response.getMessage();
509: try {
510: if (msg != null && msg.isFault()) {
511: SOAPFaultBuilder faultBuilder = SOAPFaultBuilder
512: .create(msg);
513: // passing null means there is no checked excpetion we're looking for all
514: // it will get back to us is a protocol exception
515: throw (SOAPFaultException) faultBuilder
516: .createException(null);
517: }
518: responseImpl
519: .setResponseContext(new ResponseContext(
520: response));
521: responseImpl.set(toReturnValue(response), null);
522: } catch (JAXBException e) {
523: //TODO: i18nify
524: responseImpl
525: .set(
526: null,
527: new DeserializationException(
528: DispatchMessages
529: .INVALID_RESPONSE_DESERIALIZATION(),
530: e));
531: } catch (WebServiceException e) {
532: //it could be a WebServiceException or a ProtocolException
533: responseImpl.set(null, e);
534: } catch (Throwable e) {
535: // It could be any RuntimeException resulting due to some internal bug.
536: // or its some other exception resulting from user error, wrap it in
537: // WebServiceException
538: responseImpl.set(null, new WebServiceException(
539: e));
540: }
541: }
542:
543: public void onCompletion(@NotNull
544: Throwable error) {
545: if (error instanceof WebServiceException) {
546: responseImpl.set(null, error);
547:
548: } else {
549: //its RuntimeException or some other exception resulting from user error, wrap it in
550: // WebServiceException
551: responseImpl.set(null, new WebServiceException(
552: error));
553: }
554: }
555: };
556: processAsync(message, rc, callback);
557: }
558: }
559:
560: public void setOutboundHeaders(Object... headers) {
561: throw new UnsupportedOperationException();
562: }
563:
564: static final String HTTP_REQUEST_METHOD_GET = "GET";
565: static final String HTTP_REQUEST_METHOD_POST = "POST";
566: static final String HTTP_REQUEST_METHOD_PUT = "PUT";
567:
568: public static Dispatch<Source> createSourceDispatch(QName port,
569: Mode mode, WSServiceDelegate owner, Tube pipe,
570: BindingImpl binding, WSEndpointReference epr) {
571: if (isXMLHttp(binding))
572: return new RESTSourceDispatch(port, mode, owner, pipe,
573: binding, epr);
574: else
575: return new SOAPSourceDispatch(port, mode, owner, pipe,
576: binding, epr);
577: }
578: }
|