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