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.sei;
038:
039: import com.sun.xml.bind.api.AccessorException;
040: import com.sun.xml.bind.api.Bridge;
041: import com.sun.xml.bind.api.CompositeStructure;
042: import com.sun.xml.bind.api.RawAccessor;
043: import com.sun.xml.ws.api.SOAPVersion;
044: import com.sun.xml.ws.api.message.Attachment;
045: import com.sun.xml.ws.api.message.AttachmentSet;
046: import com.sun.xml.ws.api.message.Message;
047: import com.sun.xml.ws.api.model.ParameterBinding;
048: import com.sun.xml.ws.api.streaming.XMLStreamReaderFactory;
049: import com.sun.xml.ws.message.AttachmentUnmarshallerImpl;
050: import com.sun.xml.ws.model.ParameterImpl;
051: import com.sun.xml.ws.model.WrapperParameter;
052: import com.sun.xml.ws.resources.ServerMessages;
053: import com.sun.xml.ws.streaming.XMLStreamReaderUtil;
054:
055: import javax.activation.DataHandler;
056: import javax.imageio.ImageIO;
057: import javax.xml.bind.JAXBException;
058: import javax.xml.namespace.QName;
059: import javax.xml.soap.SOAPException;
060: import javax.xml.soap.SOAPFault;
061: import javax.xml.stream.XMLStreamException;
062: import javax.xml.stream.XMLStreamReader;
063: import javax.xml.stream.XMLStreamConstants;
064: import javax.xml.transform.Source;
065: import javax.xml.ws.Holder;
066: import javax.xml.ws.WebServiceException;
067: import javax.xml.ws.soap.SOAPFaultException;
068: import java.awt.Image;
069: import java.io.IOException;
070: import java.io.InputStream;
071: import java.io.UnsupportedEncodingException;
072: import java.lang.reflect.Type;
073: import java.util.ArrayList;
074: import java.util.Collection;
075: import java.util.HashMap;
076: import java.util.Iterator;
077: import java.util.List;
078: import java.util.Map;
079:
080: /**
081: * Reads a response {@link Message}, disassembles it, and moves obtained Java values
082: * to the expected places.
083: *
084: * @author Kohsuke Kawaguchi
085: * @author Jitendra Kotamraju
086: */
087: abstract class ResponseBuilder {
088: /**
089: * Reads a response {@link Message}, disassembles it, and moves obtained Java values
090: * to the expected places.
091: *
092: * @param reply
093: * The reply {@link Message} to be de-composed.
094: * @param args
095: * The Java arguments given to the SEI method invocation.
096: * Some parts of the reply message may be set to {@link Holder}s in the arguments.
097: * @return
098: * If a part of the reply message is returned as a return value from
099: * the SEI method, this method returns that value. Otherwise null.
100: * @throws JAXBException
101: * if there's an error during unmarshalling the reply message.
102: * @throws XMLStreamException
103: * if there's an error during unmarshalling the reply message.
104: */
105: abstract Object readResponse(Message reply, Object[] args)
106: throws JAXBException, XMLStreamException;
107:
108: static final class None extends ResponseBuilder {
109: private None() {
110: }
111:
112: public Object readResponse(Message msg, Object[] args) {
113: msg.consume();
114: return null;
115: }
116: }
117:
118: /**
119: * The singleton instance that produces null return value.
120: * Used for operations that doesn't have any output.
121: */
122: public static ResponseBuilder NONE = new None();
123:
124: /**
125: * Returns the 'uninitialized' value for the given type.
126: *
127: * <p>
128: * For primitive types, it's '0', and for reference types, it's null.
129: */
130: public static Object getVMUninitializedValue(Type type) {
131: // if this map returns null, that means the 'type' is a reference type,
132: // in which case 'null' is the correct null value, so this code is correct.
133: return primitiveUninitializedValues.get(type);
134: }
135:
136: private static final Map<Class, Object> primitiveUninitializedValues = new HashMap<Class, Object>();
137:
138: static {
139: Map<Class, Object> m = primitiveUninitializedValues;
140: m.put(int.class, (int) 0);
141: m.put(char.class, (char) 0);
142: m.put(byte.class, (byte) 0);
143: m.put(short.class, (short) 0);
144: m.put(long.class, (long) 0);
145: m.put(float.class, (float) 0);
146: m.put(double.class, (double) 0);
147: }
148:
149: /**
150: * {@link ResponseBuilder} that sets the VM uninitialized value to the type.
151: */
152: static final class NullSetter extends ResponseBuilder {
153: private final ValueSetter setter;
154: private final Object nullValue;
155:
156: public NullSetter(ValueSetter setter, Object nullValue) {
157: assert setter != null;
158: this .nullValue = nullValue;
159: this .setter = setter;
160: }
161:
162: public Object readResponse(Message msg, Object[] args) {
163: return setter.put(nullValue, args);
164: }
165: }
166:
167: /**
168: * {@link ResponseBuilder} that is a composition of multiple
169: * {@link ResponseBuilder}s.
170: *
171: * <p>
172: * Sometimes we need to look at multiple parts of the reply message
173: * (say, two header params, one body param, and three attachments, etc.)
174: * and that's when this object is used to combine multiple {@link ResponseBuilder}s
175: * (that each responsible for handling one part).
176: *
177: * <p>
178: * The model guarantees that only at most one {@link ResponseBuilder} will
179: * return a value as a return value (and everything else has to go to
180: * {@link Holder}s.)
181: */
182: static final class Composite extends ResponseBuilder {
183: private final ResponseBuilder[] builders;
184:
185: public Composite(ResponseBuilder... builders) {
186: this .builders = builders;
187: }
188:
189: public Composite(Collection<? extends ResponseBuilder> builders) {
190: this (builders.toArray(new ResponseBuilder[builders.size()]));
191: }
192:
193: public Object readResponse(Message msg, Object[] args)
194: throws JAXBException, XMLStreamException {
195: Object retVal = null;
196: for (ResponseBuilder builder : builders) {
197: Object r = builder.readResponse(msg, args);
198: // there's only at most one ResponseBuilder that returns a value.
199: if (r != null) {
200: assert retVal == null;
201: retVal = r;
202: }
203: }
204: return retVal;
205: }
206: }
207:
208: /**
209: * Reads an Attachment into a Java parameter.
210: */
211: static abstract class AttachmentBuilder extends ResponseBuilder {
212: protected final ValueSetter setter;
213: protected final ParameterImpl param;
214: private final String pname;
215: private final String pname1;
216:
217: AttachmentBuilder(ParameterImpl param, ValueSetter setter) {
218: this .setter = setter;
219: this .param = param;
220: this .pname = param.getPartName();
221: this .pname1 = "<" + pname;
222: }
223:
224: /**
225: * Creates an AttachmentBuilder based on the parameter type
226: *
227: * @param param
228: * runtime Parameter that abstracts the annotated java parameter
229: * @param setter
230: * specifies how the obtained value is set into the argument. Takes
231: * care of Holder arguments.
232: */
233: public static ResponseBuilder createAttachmentBuilder(
234: ParameterImpl param, ValueSetter setter) {
235: Class type = (Class) param.getTypeReference().type;
236: if (DataHandler.class.isAssignableFrom(type)) {
237: return new DataHandlerBuilder(param, setter);
238: } else if (byte[].class == type) {
239: return new ByteArrayBuilder(param, setter);
240: } else if (Source.class.isAssignableFrom(type)) {
241: return new SourceBuilder(param, setter);
242: } else if (Image.class.isAssignableFrom(type)) {
243: return new ImageBuilder(param, setter);
244: } else if (InputStream.class == type) {
245: return new InputStreamBuilder(param, setter);
246: } else if (isXMLMimeType(param.getBinding().getMimeType())) {
247: return new JAXBBuilder(param, setter);
248: } else {
249: throw new UnsupportedOperationException(
250: "Unexpected Attachment type =" + type);
251: }
252: }
253:
254: public Object readResponse(Message msg, Object[] args)
255: throws JAXBException, XMLStreamException {
256: // TODO not to loop
257: for (Attachment att : msg.getAttachments()) {
258: String part = getWSDLPartName(att);
259: if (part == null) {
260: continue;
261: }
262: if (part.equals(pname) || part.equals(pname1)) {
263: return mapAttachment(att, args);
264: }
265: }
266: return null;
267: }
268:
269: abstract Object mapAttachment(Attachment att, Object[] args)
270: throws JAXBException;
271: }
272:
273: private static final class DataHandlerBuilder extends
274: AttachmentBuilder {
275: DataHandlerBuilder(ParameterImpl param, ValueSetter setter) {
276: super (param, setter);
277: }
278:
279: Object mapAttachment(Attachment att, Object[] args) {
280: return setter.put(att.asDataHandler(), args);
281: }
282: }
283:
284: private static final class ByteArrayBuilder extends
285: AttachmentBuilder {
286: ByteArrayBuilder(ParameterImpl param, ValueSetter setter) {
287: super (param, setter);
288: }
289:
290: Object mapAttachment(Attachment att, Object[] args) {
291: return setter.put(att.asByteArray(), args);
292: }
293: }
294:
295: private static final class SourceBuilder extends AttachmentBuilder {
296: SourceBuilder(ParameterImpl param, ValueSetter setter) {
297: super (param, setter);
298: }
299:
300: Object mapAttachment(Attachment att, Object[] args) {
301: return setter.put(att.asSource(), args);
302: }
303: }
304:
305: private static final class ImageBuilder extends AttachmentBuilder {
306: ImageBuilder(ParameterImpl param, ValueSetter setter) {
307: super (param, setter);
308: }
309:
310: Object mapAttachment(Attachment att, Object[] args) {
311: Image image;
312: InputStream is = null;
313: try {
314: is = att.asInputStream();
315: image = ImageIO.read(is);
316: } catch (IOException ioe) {
317: throw new WebServiceException(ioe);
318: } finally {
319: if (is != null) {
320: try {
321: is.close();
322: } catch (IOException ioe) {
323: throw new WebServiceException(ioe);
324: }
325: }
326: }
327: return setter.put(image, args);
328: }
329: }
330:
331: private static final class InputStreamBuilder extends
332: AttachmentBuilder {
333: InputStreamBuilder(ParameterImpl param, ValueSetter setter) {
334: super (param, setter);
335: }
336:
337: Object mapAttachment(Attachment att, Object[] args) {
338: return setter.put(att.asInputStream(), args);
339: }
340: }
341:
342: private static final class JAXBBuilder extends AttachmentBuilder {
343: JAXBBuilder(ParameterImpl param, ValueSetter setter) {
344: super (param, setter);
345: }
346:
347: Object mapAttachment(Attachment att, Object[] args)
348: throws JAXBException {
349: Object obj = param.getBridge().unmarshal(
350: att.asInputStream());
351: return setter.put(obj, args);
352: }
353: }
354:
355: /**
356: * Gets the WSDL part name of this attachment.
357: *
358: * <p>
359: * According to WSI AP 1.0
360: * <PRE>
361: * 3.8 Value-space of Content-Id Header
362: * Definition: content-id part encoding
363: * The "content-id part encoding" consists of the concatenation of:
364: * The value of the name attribute of the wsdl:part element referenced by the mime:content, in which characters disallowed in content-id headers (non-ASCII characters as represented by code points above 0x7F) are escaped as follows:
365: * o Each disallowed character is converted to UTF-8 as one or more bytes.
366: * o Any bytes corresponding to a disallowed character are escaped with the URI escaping mechanism (that is, converted to %HH, where HH is the hexadecimal notation of the byte value).
367: * o The original character is replaced by the resulting character sequence.
368: * The character '=' (0x3D).
369: * A globally unique value such as a UUID.
370: * The character '@' (0x40).
371: * A valid domain name under the authority of the entity constructing the message.
372: * </PRE>
373: *
374: * So a wsdl:part fooPart will be encoded as:
375: * <fooPart=somereallybignumberlikeauuid@example.com>
376: *
377: * @return null
378: * if the parsing fails.
379: */
380: public static final String getWSDLPartName(
381: com.sun.xml.ws.api.message.Attachment att) {
382: String cId = att.getContentId();
383:
384: int index = cId.lastIndexOf('@', cId.length());
385: if (index == -1) {
386: return null;
387: }
388: String localPart = cId.substring(0, index);
389: index = localPart.lastIndexOf('=', localPart.length());
390: if (index == -1) {
391: return null;
392: }
393: try {
394: return java.net.URLDecoder.decode(localPart.substring(0,
395: index), "UTF-8");
396: } catch (UnsupportedEncodingException e) {
397: throw new WebServiceException(e);
398: }
399: }
400:
401: /**
402: * Reads a header into a JAXB object.
403: */
404: static final class Header extends ResponseBuilder {
405: private final Bridge<?> bridge;
406: private final ValueSetter setter;
407: private final QName headerName;
408: private final SOAPVersion soapVersion;
409:
410: /**
411: * @param soapVersion
412: * SOAP1.1 or 1.2
413: * @param name
414: * The name of the header element.
415: * @param bridge
416: * specifies how to unmarshal a header into a JAXB object.
417: * @param setter
418: * specifies how the obtained value is returned to the client.
419: */
420: public Header(SOAPVersion soapVersion, QName name,
421: Bridge<?> bridge, ValueSetter setter) {
422: this .soapVersion = soapVersion;
423: this .headerName = name;
424: this .bridge = bridge;
425: this .setter = setter;
426: }
427:
428: public Header(SOAPVersion soapVersion, ParameterImpl param,
429: ValueSetter setter) {
430: this (soapVersion, param.getTypeReference().tagName, param
431: .getBridge(), setter);
432: assert param.getOutBinding() == ParameterBinding.HEADER;
433: }
434:
435: private SOAPFaultException createDuplicateHeaderException() {
436: try {
437: SOAPFault fault = soapVersion.saajSoapFactory
438: .createFault(
439: ServerMessages
440: .DUPLICATE_PORT_KNOWN_HEADER(headerName),
441: soapVersion.faultCodeServer);
442: return new SOAPFaultException(fault);
443: } catch (SOAPException e) {
444: throw new WebServiceException(e);
445: }
446: }
447:
448: public Object readResponse(Message msg, Object[] args)
449: throws JAXBException {
450: com.sun.xml.ws.api.message.Header header = null;
451: Iterator<com.sun.xml.ws.api.message.Header> it = msg
452: .getHeaders().getHeaders(headerName, true);
453: if (it.hasNext()) {
454: header = it.next();
455: if (it.hasNext()) {
456: throw createDuplicateHeaderException();
457: }
458: }
459:
460: if (header != null)
461: return setter.put(header.readAsJAXB(bridge), args);
462: else
463: // header not found.
464: return null;
465: }
466: }
467:
468: /**
469: * Reads the whole payload into a single JAXB bean.
470: */
471: static final class Body extends ResponseBuilder {
472: private final Bridge<?> bridge;
473: private final ValueSetter setter;
474:
475: /**
476: * @param bridge
477: * specifies how to unmarshal the payload into a JAXB object.
478: * @param setter
479: * specifies how the obtained value is returned to the client.
480: */
481: public Body(Bridge<?> bridge, ValueSetter setter) {
482: this .bridge = bridge;
483: this .setter = setter;
484: }
485:
486: public Object readResponse(Message msg, Object[] args)
487: throws JAXBException {
488: return setter.put(msg.readPayloadAsJAXB(bridge), args);
489: }
490: }
491:
492: /**
493: * Treats a payload as multiple parts wrapped into one element,
494: * and processes all such wrapped parts.
495: */
496: static final class DocLit extends ResponseBuilder {
497: /**
498: * {@link PartBuilder} keyed by the element name (inside the wrapper element.)
499: */
500: private final PartBuilder[] parts;
501:
502: private final Bridge wrapper;
503:
504: private final QName wrapperName;
505:
506: public DocLit(WrapperParameter wp,
507: ValueSetterFactory setterFactory) {
508: wrapperName = wp.getName();
509: wrapper = wp.getBridge();
510: Class wrapperType = (Class) wrapper.getTypeReference().type;
511:
512: List<PartBuilder> parts = new ArrayList<PartBuilder>();
513:
514: List<ParameterImpl> children = wp.getWrapperChildren();
515: for (ParameterImpl p : children) {
516: if (p.isIN())
517: continue;
518: QName name = p.getName();
519: try {
520: parts.add(new PartBuilder(wp.getOwner()
521: .getJAXBContext()
522: .getElementPropertyAccessor(wrapperType,
523: name.getNamespaceURI(),
524: p.getName().getLocalPart()),
525: setterFactory.get(p)));
526: // wrapper parameter itself always bind to body, and
527: // so do all its children
528: assert p.getBinding() == ParameterBinding.BODY;
529: } catch (JAXBException e) {
530: throw new WebServiceException( // TODO: i18n
531: wrapperType
532: + " do not have a property of the name "
533: + name, e);
534: }
535: }
536:
537: this .parts = parts.toArray(new PartBuilder[parts.size()]);
538: }
539:
540: public Object readResponse(Message msg, Object[] args)
541: throws JAXBException, XMLStreamException {
542: Object retVal = null;
543:
544: if (parts.length > 0) {
545: XMLStreamReader reader = msg.readPayload();
546: XMLStreamReaderUtil.verifyTag(reader, wrapperName);
547: Object wrapperBean = wrapper
548: .unmarshal(
549: reader,
550: (msg.getAttachments() != null) ? new AttachmentUnmarshallerImpl(
551: msg.getAttachments())
552: : null);
553:
554: try {
555: for (PartBuilder part : parts) {
556: Object o = part.readResponse(args, wrapperBean);
557: // there's only at most one ResponseBuilder that returns a value.
558: // TODO: reorder parts so that the return value comes at the end.
559: if (o != null) {
560: assert retVal == null;
561: retVal = o;
562: }
563: }
564: } catch (AccessorException e) {
565: // this can happen when the set method throw a checked exception or something like that
566: throw new WebServiceException(e); // TODO:i18n
567: }
568:
569: // we are done with the body
570: reader.close();
571: XMLStreamReaderFactory.recycle(reader);
572: } else {
573: msg.consume();
574: }
575:
576: return retVal;
577: }
578:
579: /**
580: * Unmarshals each wrapped part into a JAXB object and moves it
581: * to the expected place.
582: */
583: static final class PartBuilder {
584: private final RawAccessor accessor;
585: private final ValueSetter setter;
586:
587: /**
588: * @param accessor
589: * specifies which portion of the wrapper bean to obtain the value from.
590: * @param setter
591: * specifies how the obtained value is returned to the client.
592: */
593: public PartBuilder(RawAccessor accessor, ValueSetter setter) {
594: this .accessor = accessor;
595: this .setter = setter;
596: assert accessor != null && setter != null;
597: }
598:
599: final Object readResponse(Object[] args, Object wrapperBean)
600: throws AccessorException {
601: Object obj = accessor.get(wrapperBean);
602: return setter.put(obj, args);
603: }
604:
605: }
606: }
607:
608: /**
609: * Treats a payload as multiple parts wrapped into one element,
610: * and processes all such wrapped parts.
611: */
612: static final class RpcLit extends ResponseBuilder {
613: /**
614: * {@link PartBuilder} keyed by the element name (inside the wrapper element.)
615: */
616: private final Map<QName, PartBuilder> parts = new HashMap<QName, PartBuilder>();
617:
618: private QName wrapperName;
619:
620: public RpcLit(WrapperParameter wp,
621: ValueSetterFactory setterFactory) {
622: assert wp.getTypeReference().type == CompositeStructure.class;
623:
624: wrapperName = wp.getName();
625: List<ParameterImpl> children = wp.getWrapperChildren();
626: for (ParameterImpl p : children) {
627: parts.put(p.getName(), new PartBuilder(p.getBridge(),
628: setterFactory.get(p)));
629: // wrapper parameter itself always bind to body, and
630: // so do all its children
631: assert p.getBinding() == ParameterBinding.BODY;
632: }
633: }
634:
635: public Object readResponse(Message msg, Object[] args)
636: throws JAXBException, XMLStreamException {
637: Object retVal = null;
638:
639: XMLStreamReader reader = msg.readPayload();
640: if (!reader.getName().equals(wrapperName))
641: throw new WebServiceException( // TODO: i18n
642: "Unexpected response element "
643: + reader.getName() + " expected: "
644: + wrapperName);
645: reader.nextTag();
646:
647: while (reader.getEventType() == XMLStreamReader.START_ELEMENT) {
648: // TODO: QName has a performance issue
649: PartBuilder part = parts.get(reader.getName());
650: if (part == null) {
651: // no corresponding part found. ignore
652: XMLStreamReaderUtil.skipElement(reader);
653: reader.nextTag();
654: } else {
655: Object o = part.readResponse(args, reader, msg
656: .getAttachments());
657: // there's only at most one ResponseBuilder that returns a value.
658: if (o != null) {
659: assert retVal == null;
660: retVal = o;
661: }
662: }
663: // skip any whitespace
664: if (reader.getEventType() != XMLStreamConstants.START_ELEMENT
665: && reader.getEventType() != XMLStreamConstants.END_ELEMENT) {
666: XMLStreamReaderUtil.nextElementContent(reader);
667: }
668: }
669:
670: // we are done with the body
671: reader.close();
672: XMLStreamReaderFactory.recycle(reader);
673:
674: return retVal;
675: }
676:
677: /**
678: * Unmarshals each wrapped part into a JAXB object and moves it
679: * to the expected place.
680: */
681: static final class PartBuilder {
682: private final Bridge bridge;
683: private final ValueSetter setter;
684:
685: /**
686: * @param bridge
687: * specifies how the part is unmarshalled.
688: * @param setter
689: * specifies how the obtained value is returned to the client.
690: */
691: public PartBuilder(Bridge bridge, ValueSetter setter) {
692: this .bridge = bridge;
693: this .setter = setter;
694: }
695:
696: final Object readResponse(Object[] args, XMLStreamReader r,
697: AttachmentSet att) throws JAXBException {
698: Object obj = bridge.unmarshal(r,
699: (att != null) ? new AttachmentUnmarshallerImpl(
700: att) : null);
701: return setter.put(obj, args);
702: }
703:
704: }
705: }
706:
707: private static boolean isXMLMimeType(String mimeType) {
708: return mimeType.equals("text/xml")
709: || mimeType.equals("application/xml");
710: }
711: }
|