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: package com.sun.xml.bind.v2.runtime;
037:
038: import java.io.BufferedWriter;
039: import java.io.Closeable;
040: import java.io.FileOutputStream;
041: import java.io.Flushable;
042: import java.io.IOException;
043: import java.io.OutputStream;
044: import java.io.OutputStreamWriter;
045: import java.io.UnsupportedEncodingException;
046: import java.io.Writer;
047:
048: import javax.xml.bind.DatatypeConverter;
049: import javax.xml.bind.JAXBException;
050: import javax.xml.bind.MarshalException;
051: import javax.xml.bind.Marshaller;
052: import javax.xml.bind.PropertyException;
053: import javax.xml.bind.ValidationEvent;
054: import javax.xml.bind.ValidationEventHandler;
055: import javax.xml.bind.annotation.adapters.XmlAdapter;
056: import javax.xml.bind.attachment.AttachmentMarshaller;
057: import javax.xml.bind.helpers.AbstractMarshallerImpl;
058: import javax.xml.stream.XMLEventWriter;
059: import javax.xml.stream.XMLStreamException;
060: import javax.xml.stream.XMLStreamWriter;
061: import javax.xml.transform.Result;
062: import javax.xml.transform.dom.DOMResult;
063: import javax.xml.transform.sax.SAXResult;
064: import javax.xml.transform.stream.StreamResult;
065: import javax.xml.validation.Schema;
066: import javax.xml.validation.ValidatorHandler;
067: import javax.xml.namespace.NamespaceContext;
068:
069: import com.sun.xml.bind.DatatypeConverterImpl;
070: import com.sun.xml.bind.api.JAXBRIContext;
071: import com.sun.xml.bind.marshaller.CharacterEscapeHandler;
072: import com.sun.xml.bind.marshaller.DataWriter;
073: import com.sun.xml.bind.marshaller.DumbEscapeHandler;
074: import com.sun.xml.bind.marshaller.MinimumEscapeHandler;
075: import com.sun.xml.bind.marshaller.NamespacePrefixMapper;
076: import com.sun.xml.bind.marshaller.NioEscapeHandler;
077: import com.sun.xml.bind.marshaller.SAX2DOMEx;
078: import com.sun.xml.bind.marshaller.XMLWriter;
079: import com.sun.xml.bind.v2.runtime.output.C14nXmlOutput;
080: import com.sun.xml.bind.v2.runtime.output.Encoded;
081: import com.sun.xml.bind.v2.runtime.output.ForkXmlOutput;
082: import com.sun.xml.bind.v2.runtime.output.IndentingUTF8XmlOutput;
083: import com.sun.xml.bind.v2.runtime.output.NamespaceContextImpl;
084: import com.sun.xml.bind.v2.runtime.output.SAXOutput;
085: import com.sun.xml.bind.v2.runtime.output.UTF8XmlOutput;
086: import com.sun.xml.bind.v2.runtime.output.XMLEventWriterOutput;
087: import com.sun.xml.bind.v2.runtime.output.XMLStreamWriterOutput;
088: import com.sun.xml.bind.v2.runtime.output.XmlOutput;
089: import com.sun.xml.bind.v2.util.FatalAdapter;
090:
091: import org.w3c.dom.Document;
092: import org.w3c.dom.Node;
093: import org.xml.sax.SAXException;
094: import org.xml.sax.helpers.XMLFilterImpl;
095:
096: /**
097: * Implementation of {@link Marshaller} interface for the JAXB RI.
098: *
099: * <p>
100: * Eventually all the {@link #marshal} methods call into
101: * the {@link #write} method.
102: *
103: * @author Kohsuke Kawaguchi
104: * @author Vivek Pandey
105: */
106: public/*to make unit tests happy*/final class MarshallerImpl extends
107: AbstractMarshallerImpl implements ValidationEventHandler {
108: /** Indentation string. Default is four whitespaces. */
109: private String indent = " ";
110:
111: /** Used to assign prefixes to namespace URIs. */
112: private NamespacePrefixMapper prefixMapper = null;
113:
114: /** Object that handles character escaping. */
115: private CharacterEscapeHandler escapeHandler = null;
116:
117: /** XML BLOB written after the XML declaration. */
118: private String header = null;
119:
120: /** reference to the context that created this object */
121: final JAXBContextImpl context;
122:
123: protected final XMLSerializer serializer;
124:
125: /**
126: * Non-null if we do the marshal-time validation.
127: */
128: private Schema schema;
129:
130: /** Marshaller.Listener */
131: private Listener externalListener = null;
132:
133: /** Configured for c14n? */
134: private boolean c14nSupport;
135:
136: // while createing XmlOutput those values may be set.
137: // if these are non-null they need to be cleaned up
138: private Flushable toBeFlushed;
139: private Closeable toBeClosed;
140:
141: /**
142: * @param assoc
143: * non-null if the marshaller is working inside {@link BinderImpl}.
144: */
145: public MarshallerImpl(JAXBContextImpl c, AssociationMap assoc) {
146: // initialize datatype converter with ours
147: DatatypeConverter
148: .setDatatypeConverter(DatatypeConverterImpl.theInstance);
149:
150: context = c;
151: serializer = new XMLSerializer(this );
152: c14nSupport = context.c14nSupport;
153:
154: try {
155: setEventHandler(this );
156: } catch (JAXBException e) {
157: throw new AssertionError(e); // impossible
158: }
159: }
160:
161: public JAXBContextImpl getContext() {
162: return context;
163: }
164:
165: /**
166: * Marshals to {@link OutputStream} with the given in-scope namespaces
167: * taken into account.
168: *
169: * @since 2.1.5
170: */
171: public void marshal(Object obj, OutputStream out,
172: NamespaceContext inscopeNamespace) throws JAXBException {
173: write(obj, createWriter(out), new StAXPostInitAction(
174: inscopeNamespace, serializer));
175: }
176:
177: public void marshal(Object obj, XMLStreamWriter writer)
178: throws JAXBException {
179: write(obj, XMLStreamWriterOutput.create(writer, context),
180: new StAXPostInitAction(writer, serializer));
181: }
182:
183: public void marshal(Object obj, XMLEventWriter writer)
184: throws JAXBException {
185: write(obj, new XMLEventWriterOutput(writer),
186: new StAXPostInitAction(writer, serializer));
187: }
188:
189: public void marshal(Object obj, XmlOutput output)
190: throws JAXBException {
191: write(obj, output, null);
192: }
193:
194: /**
195: * Creates {@link XmlOutput} from the given {@link Result} object.
196: */
197: final XmlOutput createXmlOutput(Result result) throws JAXBException {
198: if (result instanceof SAXResult)
199: return new SAXOutput(((SAXResult) result).getHandler());
200:
201: if (result instanceof DOMResult) {
202: final Node node = ((DOMResult) result).getNode();
203:
204: if (node == null) {
205: Document doc = JAXBContextImpl.createDom();
206: ((DOMResult) result).setNode(doc);
207: return new SAXOutput(new SAX2DOMEx(doc));
208: } else {
209: return new SAXOutput(new SAX2DOMEx(node));
210: }
211: }
212: if (result instanceof StreamResult) {
213: StreamResult sr = (StreamResult) result;
214:
215: if (sr.getWriter() != null)
216: return createWriter(sr.getWriter());
217: else if (sr.getOutputStream() != null)
218: return createWriter(sr.getOutputStream());
219: else if (sr.getSystemId() != null) {
220: String fileURL = sr.getSystemId();
221:
222: if (fileURL.startsWith("file:///")) {
223: if (fileURL.substring(8).indexOf(":") > 0)
224: fileURL = fileURL.substring(8);
225: else
226: fileURL = fileURL.substring(7);
227: }
228: if (fileURL.startsWith("file:/")) {
229: // some people use broken URLs like "file:/c:/abc/def/ghi.txt"
230: // so let's make it work with that
231: if (fileURL.substring(6).indexOf(":") > 0)
232: fileURL = fileURL.substring(6);
233: else
234: fileURL = fileURL.substring(5);
235: }
236: // otherwise assume that it's a file name
237:
238: try {
239: FileOutputStream fos = new FileOutputStream(fileURL);
240: assert toBeClosed == null;
241: toBeClosed = fos;
242: return createWriter(fos);
243: } catch (IOException e) {
244: throw new MarshalException(e);
245: }
246: }
247: }
248:
249: // unsupported parameter type
250: throw new MarshalException(Messages.UNSUPPORTED_RESULT.format());
251: }
252:
253: /**
254: * Creates an appropriate post-init action object.
255: */
256: final Runnable createPostInitAction(Result result) {
257: if (result instanceof DOMResult) {
258: Node node = ((DOMResult) result).getNode();
259: return new DomPostInitAction(node, serializer);
260: }
261: return null;
262: }
263:
264: public void marshal(Object target, Result result)
265: throws JAXBException {
266: write(target, createXmlOutput(result),
267: createPostInitAction(result));
268: }
269:
270: /**
271: * Used by {@link BridgeImpl} to write an arbitrary object as a fragment.
272: */
273: protected final <T> void write(Name rootTagName, JaxBeanInfo<T> bi,
274: T obj, XmlOutput out, Runnable postInitAction)
275: throws JAXBException {
276: try {
277: try {
278: prewrite(out, true, postInitAction);
279: serializer.startElement(rootTagName, null);
280: if (bi.jaxbType == Void.class
281: || bi.jaxbType == void.class) {
282: // special case for void
283: serializer.endNamespaceDecls(null);
284: serializer.endAttributes();
285: } else { // normal cases
286: if (obj == null)
287: serializer.writeXsiNilTrue();
288: else
289: serializer.childAsXsiType(obj, "root", bi);
290: }
291: serializer.endElement();
292: postwrite();
293: } catch (SAXException e) {
294: throw new MarshalException(e);
295: } catch (IOException e) {
296: throw new MarshalException(e);
297: } catch (XMLStreamException e) {
298: throw new MarshalException(e);
299: } finally {
300: serializer.close();
301: }
302: } finally {
303: cleanUp();
304: }
305: }
306:
307: /**
308: * All the marshal method invocation eventually comes down to this call.
309: */
310: private void write(Object obj, XmlOutput out,
311: Runnable postInitAction) throws JAXBException {
312: try {
313: if (obj == null)
314: throw new IllegalArgumentException(
315: Messages.NOT_MARSHALLABLE.format());
316:
317: if (schema != null) {
318: // send the output to the validator as well
319: ValidatorHandler validator = schema
320: .newValidatorHandler();
321: validator.setErrorHandler(new FatalAdapter(serializer));
322: // work around a bug in JAXP validator in Tiger
323: XMLFilterImpl f = new XMLFilterImpl() {
324: public void startPrefixMapping(String prefix,
325: String uri) throws SAXException {
326: super .startPrefixMapping(prefix.intern(), uri
327: .intern());
328: }
329: };
330: f.setContentHandler(validator);
331: out = new ForkXmlOutput(new SAXOutput(f) {
332: @Override
333: public void startDocument(XMLSerializer serializer,
334: boolean fragment,
335: int[] nsUriIndex2prefixIndex,
336: NamespaceContextImpl nsContext)
337: throws SAXException, IOException,
338: XMLStreamException {
339: super .startDocument(serializer, false,
340: nsUriIndex2prefixIndex, nsContext);
341: }
342:
343: @Override
344: public void endDocument(boolean fragment)
345: throws SAXException, IOException,
346: XMLStreamException {
347: super .endDocument(false);
348: }
349: }, out);
350: }
351:
352: try {
353: prewrite(out, isFragment(), postInitAction);
354: serializer.childAsRoot(obj);
355: postwrite();
356: } catch (SAXException e) {
357: throw new MarshalException(e);
358: } catch (IOException e) {
359: throw new MarshalException(e);
360: } catch (XMLStreamException e) {
361: throw new MarshalException(e);
362: } finally {
363: serializer.close();
364: }
365: } finally {
366: cleanUp();
367: }
368: }
369:
370: private void cleanUp() {
371: if (toBeFlushed != null)
372: try {
373: toBeFlushed.flush();
374: } catch (IOException e) {
375: // ignore
376: }
377: if (toBeClosed != null)
378: try {
379: toBeClosed.close();
380: } catch (IOException e) {
381: // ignore
382: }
383: toBeFlushed = null;
384: toBeClosed = null;
385: }
386:
387: // common parts between two write methods.
388:
389: private void prewrite(XmlOutput out, boolean fragment,
390: Runnable postInitAction) throws IOException, SAXException,
391: XMLStreamException {
392: serializer.startDocument(out, fragment, getSchemaLocation(),
393: getNoNSSchemaLocation());
394: if (postInitAction != null)
395: postInitAction.run();
396: if (prefixMapper != null) {
397: // be defensive as we work with the user's code
398: String[] decls = prefixMapper.getContextualNamespaceDecls();
399: if (decls != null) { // defensive check
400: for (int i = 0; i < decls.length; i += 2) {
401: String prefix = decls[i];
402: String nsUri = decls[i + 1];
403: if (nsUri != null && prefix != null) // defensive check
404: serializer.addInscopeBinding(nsUri, prefix);
405: }
406: }
407: }
408: serializer.setPrefixMapper(prefixMapper);
409: }
410:
411: private void postwrite() throws IOException, SAXException,
412: XMLStreamException {
413: serializer.endDocument();
414: serializer.reconcileID(); // extra check
415: }
416:
417: //
418: //
419: // create XMLWriter by specifing various type of output.
420: //
421: //
422:
423: protected CharacterEscapeHandler createEscapeHandler(String encoding) {
424: if (escapeHandler != null)
425: // user-specified one takes precedence.
426: return escapeHandler;
427:
428: if (encoding.startsWith("UTF"))
429: // no need for character reference. Use the handler
430: // optimized for that pattern.
431: return MinimumEscapeHandler.theInstance;
432:
433: // otherwise try to find one from the encoding
434: try {
435: // try new JDK1.4 NIO
436: return new NioEscapeHandler(getJavaEncoding(encoding));
437: } catch (Throwable e) {
438: // if that fails, fall back to the dumb mode
439: return DumbEscapeHandler.theInstance;
440: }
441: }
442:
443: public XmlOutput createWriter(Writer w, String encoding) {
444: // XMLWriter doesn't do buffering, so do it here if it looks like a good idea
445: if (!(w instanceof BufferedWriter))
446: w = new BufferedWriter(w);
447:
448: assert toBeFlushed == null;
449: toBeFlushed = w;
450:
451: CharacterEscapeHandler ceh = createEscapeHandler(encoding);
452: XMLWriter xw;
453:
454: if (isFormattedOutput()) {
455: DataWriter d = new DataWriter(w, encoding, ceh);
456: d.setIndentStep(indent);
457: xw = d;
458: } else
459: xw = new XMLWriter(w, encoding, ceh);
460:
461: xw.setXmlDecl(!isFragment());
462: xw.setHeader(header);
463: return new SAXOutput(xw); // TODO: don't we need a better writer?
464: }
465:
466: public XmlOutput createWriter(Writer w) {
467: return createWriter(w, getEncoding());
468: }
469:
470: public XmlOutput createWriter(OutputStream os) throws JAXBException {
471: return createWriter(os, getEncoding());
472: }
473:
474: public XmlOutput createWriter(OutputStream os, String encoding)
475: throws JAXBException {
476: // UTF8XmlOutput does buffering on its own, and
477: // otherwise createWriter(Writer) inserts a buffering,
478: // so no point in doing a buffering here.
479:
480: if (encoding.equals("UTF-8")) {
481: Encoded[] table = context.getUTF8NameTable();
482: final UTF8XmlOutput out;
483: if (isFormattedOutput())
484: out = new IndentingUTF8XmlOutput(os, indent, table);
485: else {
486: if (c14nSupport)
487: out = new C14nXmlOutput(os, table,
488: context.c14nSupport);
489: else
490: out = new UTF8XmlOutput(os, table);
491: }
492: if (header != null)
493: out.setHeader(header);
494: return out;
495: }
496:
497: try {
498: return createWriter(new OutputStreamWriter(os,
499: getJavaEncoding(encoding)), encoding);
500: } catch (UnsupportedEncodingException e) {
501: throw new MarshalException(Messages.UNSUPPORTED_ENCODING
502: .format(encoding), e);
503: }
504: }
505:
506: public Object getProperty(String name) throws PropertyException {
507: if (INDENT_STRING.equals(name))
508: return indent;
509: if (ENCODING_HANDLER.equals(name)
510: || ENCODING_HANDLER2.equals(name))
511: return escapeHandler;
512: if (PREFIX_MAPPER.equals(name))
513: return prefixMapper;
514: if (XMLDECLARATION.equals(name))
515: return !isFragment();
516: if (XML_HEADERS.equals(name))
517: return header;
518: if (C14N.equals(name))
519: return c14nSupport;
520: if (OBJECT_IDENTITY_CYCLE_DETECTION.equals(name))
521: return serializer.getObjectIdentityCycleDetection();
522: ;
523:
524: return super .getProperty(name);
525: }
526:
527: public void setProperty(String name, Object value)
528: throws PropertyException {
529: if (INDENT_STRING.equals(name)) {
530: checkString(name, value);
531: indent = (String) value;
532: return;
533: }
534: if (ENCODING_HANDLER.equals(name)
535: || ENCODING_HANDLER2.equals(name)) {
536: if (!(value instanceof CharacterEscapeHandler))
537: throw new PropertyException(Messages.MUST_BE_X.format(
538: name, CharacterEscapeHandler.class.getName(),
539: value.getClass().getName()));
540: escapeHandler = (CharacterEscapeHandler) value;
541: return;
542: }
543: if (PREFIX_MAPPER.equals(name)) {
544: if (!(value instanceof NamespacePrefixMapper))
545: throw new PropertyException(Messages.MUST_BE_X.format(
546: name, NamespacePrefixMapper.class.getName(),
547: value.getClass().getName()));
548: prefixMapper = (NamespacePrefixMapper) value;
549: return;
550: }
551: if (XMLDECLARATION.equals(name)) {
552: checkBoolean(name, value);
553: // com.sun.xml.bind.xmlDeclaration is an alias for JAXB_FRAGMENT
554: // setting it to false is treated the same as setting fragment to true.
555: super .setProperty(JAXB_FRAGMENT, !(Boolean) value);
556: return;
557: }
558: if (XML_HEADERS.equals(name)) {
559: checkString(name, value);
560: header = (String) value;
561: return;
562: }
563: if (C14N.equals(name)) {
564: checkBoolean(name, value);
565: c14nSupport = (Boolean) value;
566: return;
567: }
568: if (OBJECT_IDENTITY_CYCLE_DETECTION.equals(name)) {
569: checkBoolean(name, value);
570: serializer.setObjectIdentityCycleDetection((Boolean) value);
571: return;
572: }
573:
574: super .setProperty(name, value);
575: }
576:
577: /*
578: * assert that the given object is a Boolean
579: */
580: private void checkBoolean(String name, Object value)
581: throws PropertyException {
582: if (!(value instanceof Boolean))
583: throw new PropertyException(Messages.MUST_BE_X
584: .format(name, Boolean.class.getName(), value
585: .getClass().getName()));
586: }
587:
588: /*
589: * assert that the given object is a String
590: */
591: private void checkString(String name, Object value)
592: throws PropertyException {
593: if (!(value instanceof String))
594: throw new PropertyException(Messages.MUST_BE_X.format(name,
595: String.class.getName(), value.getClass().getName()));
596: }
597:
598: @Override
599: public <A extends XmlAdapter> void setAdapter(Class<A> type,
600: A adapter) {
601: if (type == null)
602: throw new IllegalArgumentException();
603: serializer.putAdapter(type, adapter);
604: }
605:
606: @Override
607: public <A extends XmlAdapter> A getAdapter(Class<A> type) {
608: if (type == null)
609: throw new IllegalArgumentException();
610: if (serializer.containsAdapter(type))
611: // so as not to create a new instance when this method is called
612: return serializer.getAdapter(type);
613: else
614: return null;
615: }
616:
617: @Override
618: public void setAttachmentMarshaller(AttachmentMarshaller am) {
619: serializer.attachmentMarshaller = am;
620: }
621:
622: @Override
623: public AttachmentMarshaller getAttachmentMarshaller() {
624: return serializer.attachmentMarshaller;
625: }
626:
627: public Schema getSchema() {
628: return schema;
629: }
630:
631: public void setSchema(Schema s) {
632: this .schema = s;
633: }
634:
635: /**
636: * Default error handling behavior fot {@link Marshaller}.
637: */
638: public boolean handleEvent(ValidationEvent event) {
639: // draconian by default
640: return false;
641: }
642:
643: public Listener getListener() {
644: return externalListener;
645: }
646:
647: public void setListener(Listener listener) {
648: externalListener = listener;
649: }
650:
651: // features supported
652: protected static final String INDENT_STRING = "com.sun.xml.bind.indentString";
653: protected static final String PREFIX_MAPPER = "com.sun.xml.bind.namespacePrefixMapper";
654: protected static final String ENCODING_HANDLER = "com.sun.xml.bind.characterEscapeHandler";
655: protected static final String ENCODING_HANDLER2 = "com.sun.xml.bind.marshaller.CharacterEscapeHandler";
656: protected static final String XMLDECLARATION = "com.sun.xml.bind.xmlDeclaration";
657: protected static final String XML_HEADERS = "com.sun.xml.bind.xmlHeaders";
658: protected static final String C14N = JAXBRIContext.CANONICALIZATION_SUPPORT;
659: protected static final String OBJECT_IDENTITY_CYCLE_DETECTION = "com.sun.xml.bind.objectIdentitityCycleDetection";
660: }
|