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.message.databinding.impl;
020:
021: import org.apache.axiom.om.OMElement;
022: import org.apache.axiom.om.impl.MTOMXMLStreamWriter;
023: import org.apache.axiom.om.util.StAXUtils;
024: import org.apache.axis2.java.security.AccessController;
025: import org.apache.axis2.jaxws.ExceptionFactory;
026: import org.apache.axis2.jaxws.message.Message;
027: import org.apache.axis2.jaxws.message.attachments.JAXBAttachmentMarshaller;
028: import org.apache.axis2.jaxws.message.attachments.JAXBAttachmentUnmarshaller;
029: import org.apache.axis2.jaxws.message.databinding.JAXBBlock;
030: import org.apache.axis2.jaxws.message.databinding.JAXBBlockContext;
031: import org.apache.axis2.jaxws.message.databinding.JAXBUtils;
032: import org.apache.axis2.jaxws.message.databinding.XSDListUtils;
033: import org.apache.axis2.jaxws.message.factory.BlockFactory;
034: import org.apache.axis2.jaxws.message.impl.BlockImpl;
035: import org.apache.axis2.jaxws.utility.XMLRootElementUtil;
036: import org.apache.commons.logging.Log;
037: import org.apache.commons.logging.LogFactory;
038:
039: import javax.xml.bind.JAXBElement;
040: import javax.xml.bind.JAXBException;
041: import javax.xml.bind.JAXBIntrospector;
042: import javax.xml.bind.Marshaller;
043: import javax.xml.bind.Unmarshaller;
044: import javax.xml.datatype.DatatypeConfigurationException;
045: import javax.xml.namespace.QName;
046: import javax.xml.stream.XMLStreamException;
047: import javax.xml.stream.XMLStreamReader;
048: import javax.xml.stream.XMLStreamWriter;
049: import javax.xml.ws.WebServiceException;
050:
051: import java.io.ByteArrayInputStream;
052: import java.io.ByteArrayOutputStream;
053: import java.io.OutputStream;
054: import java.lang.reflect.InvocationTargetException;
055: import java.lang.reflect.Method;
056: import java.security.PrivilegedAction;
057: import java.text.ParseException;
058:
059: /**
060: * JAXBBlockImpl <p/> A Block containing a JAXB business object (either a JAXBElement or an object
061: * with @XmlRootElement).
062: */
063: public class JAXBBlockImpl extends BlockImpl implements JAXBBlock {
064:
065: private static final Log log = LogFactory
066: .getLog(JAXBBlockImpl.class);
067:
068: private static final boolean DEBUG_ENABLED = log.isDebugEnabled();
069:
070: /**
071: * Called by JAXBBlockFactory
072: *
073: * @param busObject..The business object must be a JAXBElement or an object with an
074: * @XMLRootElement. This is assertion is validated in the JAXBFactory.
075: * @param busContext
076: * @param qName QName must be non-null
077: * @param factory
078: */
079: JAXBBlockImpl(Object busObject, JAXBBlockContext busContext,
080: QName qName, BlockFactory factory) throws JAXBException {
081: super (busObject, busContext, qName, factory);
082: }
083:
084: /**
085: * Called by JAXBBlockFactory
086: *
087: * @param omelement
088: * @param busContext
089: * @param qName must be non-null
090: * @param factory
091: */
092: JAXBBlockImpl(OMElement omElement, JAXBBlockContext busContext,
093: QName qName, BlockFactory factory) {
094: super (omElement, busContext, qName, factory);
095: }
096:
097: @Override
098: protected Object _getBOFromReader(XMLStreamReader reader,
099: Object busContext) throws XMLStreamException,
100: WebServiceException {
101: // Get the JAXBBlockContext. All of the necessry information is recorded on it
102: JAXBBlockContext ctx = (JAXBBlockContext) busContext;
103: try {
104: // TODO Re-evaluate Unmarshall construction w/ MTOM
105: Unmarshaller u = JAXBUtils.getJAXBUnmarshaller(ctx
106: .getJAXBContext());
107:
108: if (DEBUG_ENABLED) {
109: log
110: .debug("Adding JAXBAttachmentUnmarshaller to Unmarshaller");
111: }
112:
113: Message msg = getParent();
114:
115: JAXBAttachmentUnmarshaller aum = new JAXBAttachmentUnmarshaller(
116: msg);
117: u.setAttachmentUnmarshaller(aum);
118:
119: Object jaxb = null;
120:
121: // Unmarshal into the business object.
122: if (ctx.getProcessType() == null) {
123: jaxb = unmarshalByElement(u, reader); // preferred and always used for
124: // style=document
125: } else {
126: jaxb = unmarshalByType(u, reader, ctx.getProcessType(),
127: ctx.isxmlList(), ctx.getConstructionType());
128: }
129:
130: // Successfully unmarshalled the object
131: JAXBUtils.releaseJAXBUnmarshaller(ctx.getJAXBContext(), u);
132:
133: // Don't close the reader. The reader is owned by the caller, and it
134: // may contain other xml instance data (other than this JAXB object)
135: // reader.close();
136: return jaxb;
137: } catch (JAXBException je) {
138: if (DEBUG_ENABLED) {
139: try {
140: log.debug("JAXBContext for unmarshal failure:"
141: + ctx.getJAXBContext());
142: } catch (Exception e) {
143: }
144: }
145: throw ExceptionFactory.makeWebServiceException(je);
146: }
147: }
148:
149: /**
150: * @param busObj
151: * @param busContext
152: * @return
153: * @throws XMLStreamException
154: * @throws WebServiceException
155: */
156: private byte[] _getBytesFromBO(Object busObj, Object busContext,
157: String encoding) throws XMLStreamException,
158: WebServiceException {
159: ByteArrayOutputStream baos = new ByteArrayOutputStream();
160:
161: XMLStreamWriter writer = StAXUtils.createXMLStreamWriter(baos,
162: encoding);
163:
164: // Since we are writing just the xml,
165: // The writer will be a normal writer.
166: // All mtom objects will be inlined.
167: // writer = new MTOMXMLStreamWriter(writer);
168:
169: // Write the business object to the writer
170: _outputFromBO(busObj, busContext, writer);
171:
172: // Flush the writer
173: writer.flush();
174: writer.close();
175: return baos.toByteArray();
176: }
177:
178: @Override
179: protected XMLStreamReader _getReaderFromBO(Object busObj,
180: Object busContext) throws XMLStreamException,
181: WebServiceException {
182: ByteArrayInputStream baos = new ByteArrayInputStream(
183: _getBytesFromBO(busObj, busContext, "utf-8"));
184: return StAXUtils.createXMLStreamReader(baos, "utf-8");
185: }
186:
187: @Override
188: protected void _outputFromBO(Object busObject, Object busContext,
189: XMLStreamWriter writer) throws XMLStreamException,
190: WebServiceException {
191: JAXBBlockContext ctx = (JAXBBlockContext) busContext;
192: try {
193: // Very easy, use the Context to get the Marshaller.
194: // Use the marshaller to write the object.
195: Marshaller m = JAXBUtils.getJAXBMarshaller(ctx
196: .getJAXBContext());
197:
198: if (DEBUG_ENABLED) {
199: log
200: .debug("Adding JAXBAttachmentMarshaller to Marshaller");
201: }
202:
203: Message msg = getParent();
204:
205: // Pool
206: JAXBAttachmentMarshaller am = new JAXBAttachmentMarshaller(
207: msg, writer);
208: m.setAttachmentMarshaller(am);
209:
210: // Marshal the object
211: if (ctx.getProcessType() == null) {
212: marshalByElement(busObject, m, writer, !am
213: .isXOPPackage());
214: } else {
215: marshalByType(busObject, m, writer, ctx
216: .getProcessType(), ctx.isxmlList(), ctx
217: .getConstructionType());
218: }
219:
220: // Successfully marshalled the data
221: JAXBUtils.releaseJAXBMarshaller(ctx.getJAXBContext(), m);
222: } catch (JAXBException je) {
223: if (DEBUG_ENABLED) {
224: try {
225: log.debug("JAXBContext for marshal failure:"
226: + ctx.getJAXBContext());
227: } catch (Exception e) {
228: }
229: }
230: throw ExceptionFactory.makeWebServiceException(je);
231: }
232: }
233:
234: /**
235: * Get the QName from the jaxb object
236: *
237: * @param jaxb
238: * @param jbc
239: * @throws WebServiceException
240: */
241: private static QName getQName(Object jaxb, JAXBBlockContext ctx)
242: throws JAXBException {
243: JAXBIntrospector jbi = JAXBUtils.getJAXBIntrospector(ctx
244: .getJAXBContext());
245: QName qName = jbi.getElementName(jaxb);
246: JAXBUtils.releaseJAXBIntrospector(ctx.getJAXBContext(), jbi);
247: return qName;
248: }
249:
250: /**
251: * Preferred way to marshal objects.
252: *
253: * @param b Object that can be rendered as an element and the element name is known by the
254: * Marshaller
255: * @param m Marshaller
256: * @param writer XMLStreamWriter
257: */
258: private static void marshalByElement(Object b, Marshaller m,
259: XMLStreamWriter writer, boolean optimize)
260: throws WebServiceException {
261: // Marshalling directly to the output stream is faster than marshalling through the
262: // XMLStreamWriter. Take advantage of this optimization if there is an output stream.
263: try {
264: OutputStream os = (optimize) ? getOutputStream(writer)
265: : null;
266: if (os != null) {
267: if (DEBUG_ENABLED) {
268: log
269: .debug("Invoking marshalByElement. Marshaling to an OutputStream. "
270: + "Object is " + getDebugName(b));
271: }
272: writer.flush();
273: m.marshal(b, os);
274: } else {
275: if (DEBUG_ENABLED) {
276: log
277: .debug("Invoking marshalByElement. Marshaling to an XMLStreamWriter. "
278: + "Object is " + getDebugName(b));
279: }
280: m.marshal(b, writer);
281: }
282: } catch (Exception e) {
283: throw ExceptionFactory.makeWebServiceException(e);
284: }
285: }
286:
287: /**
288: * Preferred way to unmarshal objects
289: *
290: * @param u Unmarshaller
291: * @param reader XMLStreamReader
292: * @return Object that represents an element
293: * @throws WebServiceException
294: */
295: private static Object unmarshalByElement(final Unmarshaller u,
296: final XMLStreamReader reader) throws WebServiceException {
297: try {
298: if (DEBUG_ENABLED) {
299: log.debug("Invoking unMarshalByElement");
300: }
301: return AccessController
302: .doPrivileged(new PrivilegedAction() {
303: public Object run() {
304: try {
305: return u.unmarshal(reader);
306: } catch (Exception e) {
307: throw ExceptionFactory
308: .makeWebServiceException(e);
309: }
310: }
311: });
312:
313: } catch (Exception e) {
314: throw ExceptionFactory.makeWebServiceException(e);
315: }
316: }
317:
318: /**
319: * Marshal objects by type
320: *
321: * @param b Object that can be rendered as an element, but the element name is not known to the
322: * schema (i.e. rpc)
323: * @param m Marshaller
324: * @param writer XMLStreamWriter
325: * @param type
326: */
327: private static void marshalByType(final Object b,
328: final Marshaller m, final XMLStreamWriter writer,
329: final Class type, final boolean isList,
330: final JAXBUtils.CONSTRUCTION_TYPE ctype)
331: throws WebServiceException {
332: AccessController.doPrivileged(new PrivilegedAction() {
333: public Object run() {
334: try {
335:
336: // NOTE
337: // Example:
338: // <xsd:simpleType name="LongList">
339: // <xsd:list>
340: // <xsd:simpleType>
341: // <xsd:restriction base="xsd:unsignedInt"/>
342: // </xsd:simpleType>
343: // </xsd:list>
344: // </xsd:simpleType>
345: // <element name="myLong" nillable="true" type="impl:LongList"/>
346: //
347: // LongList will be represented as an int[]
348: // On the wire myLong will be represented as a list of integers
349: // with intervening whitespace
350: // <myLong>1 2 3</myLong>
351: //
352: // Unfortunately, we are trying to marshal by type. Therefore
353: // we want to marshal an element (foo) that is unknown to schema.
354: // If we use the normal marshal code, the wire will look like
355: // this (which is incorrect):
356: // <foo><item>1</item><item>2</item><item>3</item></foo>
357: //
358: // The solution is to detect this situation and marshal the
359: // String instead. Then we get the correct wire format:
360: // <foo>1 2 3</foo>
361: Object jbo = b;
362:
363: if (isList || (type != null && type.isArray())) {
364: if (DEBUG_ENABLED) {
365: log
366: .debug("marshalling type which is a List or Array");
367: }
368: // We conver to xsdListString only if the type is not known
369: // to the context. In case a jaxbcontext is created from package
370: // the array types or list are not know to the context.
371: if (ctype == JAXBUtils.CONSTRUCTION_TYPE.BY_CONTEXT_PATH) {
372: QName qName = XMLRootElementUtil
373: .getXmlRootElementQNameFromObject(b);
374: String text = XSDListUtils
375: .toXSDListString(getTypeEnabledObject(b));
376: jbo = new JAXBElement(qName, String.class,
377: text);
378: } else if (ctype == JAXBUtils.CONSTRUCTION_TYPE.BY_CLASS_ARRAY) {
379: // do nothing common array types should be know to the jaxbcontext.
380: // so do not use xsdListString conversion.
381: }
382: }
383:
384: // When JAXBContext is created using a context path, it will not include Enum
385: // classes.
386: // These classes have @XmlEnum annotation but not @XmlType/@XmlElement, so the
387: // user will see MarshallingEx, class not known to ctxt.
388: //
389: // This is a jax-b defect, for now this fix is in place to pass CTS. This only
390: // fixes the
391: // situation where the enum is the top-level object (e.g., message-part in
392: // rpc-lit scenario)
393: //
394: // Sample of what enum looks like:
395: // @XmlEnum public enum EnumString {
396: // @XmlEnumValue("String1") STRING_1("String1"),
397: // @XmlEnumValue("String2") STRING_2("String2");
398: // ... }
399: if (type.isEnum()) {
400: if (b != null) {
401: if (DEBUG_ENABLED) {
402: log.debug("marshalByType. Marshaling "
403: + type.getName() + " as Enum");
404: }
405: JAXBElement jbe = (JAXBElement) b;
406: String value = XMLRootElementUtil
407: .getEnumValue((Enum) jbe.getValue());
408:
409: jbo = new JAXBElement(jbe.getName(),
410: String.class, value);
411: }
412: }
413:
414: if (DEBUG_ENABLED) {
415: log
416: .debug("Invoking marshalByType. "
417: + "Marshaling to an XMLStreamWriter. Object is "
418: + getDebugName(b));
419: }
420: m.marshal(jbo, writer);
421:
422: } catch (Exception e) {
423: throw ExceptionFactory.makeWebServiceException(e);
424: }
425: return null;
426: }
427: });
428: }
429:
430: /**
431: * The root element being read is defined by schema/JAXB; however its contents are known by
432: * schema/JAXB. Therefore we use unmarshal by the declared type (This method is used to
433: * unmarshal rpc elements)
434: *
435: * @param u Unmarshaller
436: * @param reader XMLStreamReader
437: * @param type Class
438: * @return Object
439: * @throws WebServiceException
440: */
441: private static Object unmarshalByType(final Unmarshaller u,
442: final XMLStreamReader reader, final Class type,
443: final boolean isList,
444: final JAXBUtils.CONSTRUCTION_TYPE ctype)
445: throws WebServiceException {
446:
447: if (DEBUG_ENABLED) {
448: log.debug("Invoking unmarshalByType.");
449: }
450:
451: return AccessController.doPrivileged(new PrivilegedAction() {
452: public Object run() {
453: try {
454: // Unfortunately RPC is type based. Thus a
455: // declared type must be used to unmarshal the xml.
456: Object jaxb;
457:
458: if (!isList) {
459: // case: We are not unmarshalling an xsd:list but an Array.
460:
461: if (type.isArray()) {
462: // If the context is created using package
463: // we will not have common arrays or type array in the context
464: // so let use a differet way to unmarshal this type
465: if (ctype == JAXBUtils.CONSTRUCTION_TYPE.BY_CONTEXT_PATH) {
466: jaxb = unmarshalAsListOrArray(reader,
467: u, type);
468: }
469: // list on client array on server, Can happen only in start from java
470: // case.
471: else if ((ctype == JAXBUtils.CONSTRUCTION_TYPE.BY_CLASS_ARRAY)) {
472: // The type could be any Object or primitive
473: // I will first unmarshall the xmldata to a String[]
474: // Then use the unmarshalled jaxbElement to create
475: // proper type Object Array.
476: jaxb = u.unmarshal(reader,
477: String[].class);
478: Object typeObj = getTypeEnabledObject(jaxb);
479: // Now convert String Array in to the required Type Array.
480: if (getTypeEnabledObject(typeObj) instanceof String[]) {
481: String[] strArray = (String[]) typeObj;
482: String strTokens = new String();
483: for (String str : strArray) {
484: strTokens = strTokens + " "
485: + str;
486: }
487: QName qName = XMLRootElementUtil
488: .getXmlRootElementQNameFromObject(jaxb);
489: Object obj = XSDListUtils
490: .fromXSDListString(
491: strTokens, type);
492: jaxb = new JAXBElement(qName, type,
493: obj);
494: }
495: } else {
496: jaxb = u.unmarshal(reader, type);
497: }
498:
499: } else if (type.isEnum()) {
500: // When JAXBContext is created using a context path, it will not
501: // include Enum classes.
502: // These classes have @XmlEnum annotation but not @XmlType/@XmlElement,
503: // so the user will see MarshallingEx, class not known to ctxt.
504: //
505: // This is a jax-b defect, for now this fix is in place to pass CTS.
506: // This only fixes the
507: // situation where the enum is the top-level object (e.g., message-part
508: // in rpc-lit scenario)
509: //
510: // Sample of what enum looks like:
511: // @XmlEnum public enum EnumString {
512: // @XmlEnumValue("String1") STRING_1("String1"),
513: // @XmlEnumValue("String2") STRING_2("String2");
514: //
515: // public static getValue(String){} <-- resolves a "value" to an emum
516: // object
517: // ... }
518: if (DEBUG_ENABLED) {
519: log
520: .debug("unmarshalByType. Unmarshalling "
521: + type.getName()
522: + " as Enum");
523: }
524:
525: JAXBElement<String> enumValue = u
526: .unmarshal(reader, String.class);
527:
528: if (enumValue != null) {
529: Method m = type.getMethod("fromValue",
530: new Class[] { String.class });
531: jaxb = m.invoke(null,
532: new Object[] { enumValue
533: .getValue() });
534: } else {
535: jaxb = null;
536: }
537: }
538: //Normal case: We are not unmarshalling a xsd:list or Array
539: else {
540: jaxb = u.unmarshal(reader, type);
541: }
542:
543: } else {
544: // If this is an xsd:list, we need to return the appropriate
545: // list or array (see NOTE above)
546: // First unmarshal as a String
547: //Second convert the String into a list or array
548: jaxb = unmarshalAsListOrArray(reader, u, type);
549: }
550: return jaxb;
551: } catch (Exception e) {
552: throw ExceptionFactory.makeWebServiceException(e);
553: }
554: }
555: });
556: }
557:
558: /**
559: * convert the String into a list or array
560: * @param <T>
561: * @param jaxb
562: * @param type
563: * @return
564: * @throws IllegalAccessException
565: * @throws ParseException
566: * @throws NoSuchMethodException
567: * @throws InstantiationException
568: * @throws DatatypeConfigurationException
569: * @throws InvocationTargetException
570: */
571: private static Object unmarshalAsListOrArray(
572: XMLStreamReader reader, Unmarshaller u, Class type)
573: throws IllegalAccessException, ParseException,
574: NoSuchMethodException, InstantiationException,
575: DatatypeConfigurationException, InvocationTargetException,
576: JAXBException {
577: //If this is an xsd:list, we need to return the appropriate
578: // list or array (see NOTE above)
579: // First unmarshal as a String
580: Object jaxb = u.unmarshal(reader, String.class);
581: //Second convert the String into a list or array
582: if (getTypeEnabledObject(jaxb) instanceof String) {
583: QName qName = XMLRootElementUtil
584: .getXmlRootElementQNameFromObject(jaxb);
585: Object obj = XSDListUtils.fromXSDListString(
586: (String) getTypeEnabledObject(jaxb), type);
587: return new JAXBElement(qName, type, obj);
588: } else {
589: return jaxb;
590: }
591:
592: }
593:
594: public boolean isElementData() {
595: return true;
596: }
597:
598: /**
599: * Return type enabled object
600: *
601: * @param obj type or element enabled object
602: * @return type enabled object
603: */
604: static Object getTypeEnabledObject(Object obj) {
605: if (obj == null) {
606: return null;
607: }
608: if (obj instanceof JAXBElement) {
609: return ((JAXBElement) obj).getValue();
610: }
611: return obj;
612: }
613:
614: private static String getDebugName(Object o) {
615: return (o == null) ? "null" : o.getClass().getCanonicalName();
616: }
617:
618: /**
619: * If the writer is backed by an OutputStream, then return the OutputStream
620: * @param writer
621: * @return OutputStream or null
622: */
623: private static OutputStream getOutputStream(XMLStreamWriter writer)
624: throws XMLStreamException {
625: if (writer.getClass() == MTOMXMLStreamWriter.class) {
626: return ((MTOMXMLStreamWriter) writer).getOutputStream();
627: }
628: return null;
629: }
630: }
|