View Javadoc
1   /*
2    * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package com.sun.xml.internal.bind.v2.runtime;
27  
28  import java.io.IOException;
29  import java.lang.reflect.Method;
30  import java.util.HashSet;
31  import java.util.Map;
32  import java.util.Set;
33  
34  import javax.activation.MimeType;
35  import javax.xml.bind.DatatypeConverter;
36  import javax.xml.bind.JAXBException;
37  import javax.xml.bind.Marshaller;
38  import javax.xml.bind.ValidationEvent;
39  import javax.xml.bind.ValidationEventHandler;
40  import javax.xml.bind.ValidationEventLocator;
41  import javax.xml.bind.annotation.DomHandler;
42  import javax.xml.bind.annotation.XmlNs;
43  import javax.xml.bind.attachment.AttachmentMarshaller;
44  import javax.xml.bind.helpers.NotIdentifiableEventImpl;
45  import javax.xml.bind.helpers.ValidationEventImpl;
46  import javax.xml.bind.helpers.ValidationEventLocatorImpl;
47  import javax.xml.namespace.QName;
48  import javax.xml.stream.XMLStreamException;
49  import javax.xml.transform.Source;
50  import javax.xml.transform.Transformer;
51  import javax.xml.transform.TransformerException;
52  import javax.xml.transform.sax.SAXResult;
53  
54  import com.sun.istack.internal.SAXException2;
55  import com.sun.xml.internal.bind.CycleRecoverable;
56  import com.sun.xml.internal.bind.api.AccessorException;
57  import com.sun.xml.internal.bind.marshaller.NamespacePrefixMapper;
58  import com.sun.xml.internal.bind.util.ValidationEventLocatorExImpl;
59  import com.sun.xml.internal.bind.v2.WellKnownNamespace;
60  import com.sun.xml.internal.bind.v2.model.runtime.RuntimeBuiltinLeafInfo;
61  import com.sun.xml.internal.bind.v2.runtime.output.MTOMXmlOutput;
62  import com.sun.xml.internal.bind.v2.runtime.output.NamespaceContextImpl;
63  import com.sun.xml.internal.bind.v2.runtime.output.Pcdata;
64  import com.sun.xml.internal.bind.v2.runtime.output.XmlOutput;
65  import com.sun.xml.internal.bind.v2.runtime.property.Property;
66  import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data;
67  import com.sun.xml.internal.bind.v2.runtime.unmarshaller.IntData;
68  import com.sun.xml.internal.bind.v2.util.CollisionCheckStack;
69  
70  import org.xml.sax.SAXException;
71  
72  /**
73   * Receives XML serialization event and writes to {@link XmlOutput}.
74   *
75   * <p>
76   * This object coordinates the overall marshalling efforts across different
77   * content-tree objects and different target formats.
78   *
79   * <p>
80   * The following CFG gives the proper sequence of method invocation.
81   *
82   * <pre>
83   * MARSHALLING  :=  ELEMENT
84   * ELEMENT      :=  "startElement" NSDECL* "endNamespaceDecls"
85   *                        ATTRIBUTE* "endAttributes" BODY "endElement"
86   *
87   * NSDECL       :=  "declareNamespace"
88   *
89   * ATTRIBUTE    :=  "attribute"
90   * ATTVALUES    :=  "text"*
91   *
92   *
93   * BODY         :=  ( "text" | ELEMENT )*
94   * </pre>
95   *
96   * <p>
97   * A marshalling of one element consists of two stages. The first stage is
98   * for marshalling attributes and collecting namespace declarations.
99   * The second stage is for marshalling characters/child elements of that element.
100  *
101  * <p>
102  * Observe that multiple invocation of "text" is allowed.
103  *
104  * <p>
105  * Also observe that the namespace declarations are allowed only between
106  * "startElement" and "endAttributes".
107  *
108  * <h2>Exceptions in marshaller</h2>
109  * <p>
110  * {@link IOException}, {@link SAXException}, and {@link XMLStreamException}
111  * are thrown from {@link XmlOutput}. They are always considered fatal, and
112  * therefore caught only by {@link MarshallerImpl}.
113  * <p>
114  * {@link AccessorException} can be thrown when an access to a property/field
115  * fails, and this is considered as a recoverable error, so it's caught everywhere.
116  *
117  * @author  Kohsuke Kawaguchi
118  */
119 public final class XMLSerializer extends Coordinator {
120     public final JAXBContextImpl grammar;
121 
122     /** The XML printer. */
123     private XmlOutput out;
124 
125     public final NameList nameList;
126 
127     public final int[] knownUri2prefixIndexMap;
128 
129     private final NamespaceContextImpl nsContext;
130 
131     private NamespaceContextImpl.Element nse;
132 
133     // Introduced based on Jersey requirements - to be able to retrieve marshalled name
134     ThreadLocal<Property> currentProperty = new ThreadLocal<Property>();
135 
136     /**
137      * Set to true if a text is already written,
138      * and we need to print ' ' for additional text methods.
139      */
140     private boolean textHasAlreadyPrinted = false;
141 
142     /**
143      * Set to false once we see the start tag of the root element.
144      */
145     private boolean seenRoot = false;
146 
147     /** Marshaller object to which this object belongs. */
148     private final MarshallerImpl marshaller;
149 
150     /** Objects referenced through IDREF. */
151     private final Set<Object> idReferencedObjects = new HashSet<Object>();
152 
153     /** Objects with ID. */
154     private final Set<Object> objectsWithId = new HashSet<Object>();
155 
156     /**
157      * Used to detect cycles in the object.
158      * Also used to learn what's being marshalled.
159      */
160     private final CollisionCheckStack<Object> cycleDetectionStack = new CollisionCheckStack<Object>();
161 
162     /** Optional attributes to go with root element. */
163     private String schemaLocation;
164     private String noNsSchemaLocation;
165 
166     /** Lazily created identitiy transformer. */
167     private Transformer identityTransformer;
168 
169     /** Lazily created. */
170     private ContentHandlerAdaptor contentHandlerAdapter;
171 
172     private boolean fragment;
173 
174     /**
175      * Cached instance of {@link Base64Data}.
176      */
177     private Base64Data base64Data;
178 
179     /**
180      * Cached instance of {@link IntData}.
181      */
182     private final IntData intData = new IntData();
183 
184     public AttachmentMarshaller attachmentMarshaller;
185 
186     /*package*/ XMLSerializer( MarshallerImpl _owner ) {
187         this.marshaller = _owner;
188         this.grammar = marshaller.context;
189         nsContext = new NamespaceContextImpl(this);
190         nameList = marshaller.context.nameList;
191         knownUri2prefixIndexMap = new int[nameList.namespaceURIs.length];
192     }
193 
194     /**
195      * Gets the cached instance of {@link Base64Data}.
196      *
197      * @deprecated
198      *      {@link Base64Data} is no longer cached, so that
199      *      XMLStreamWriterEx impl can retain the data, like JAX-WS does.
200      */
201     public Base64Data getCachedBase64DataInstance() {
202         return new Base64Data();
203     }
204 
205     /**
206      * Gets the ID value from an identifiable object.
207      */
208     private String getIdFromObject(Object identifiableObject) throws SAXException, JAXBException {
209         return grammar.getBeanInfo(identifiableObject,true).getId(identifiableObject,this);
210     }
211 
212     private void handleMissingObjectError(String fieldName) throws SAXException, IOException, XMLStreamException {
213         reportMissingObjectError(fieldName);
214         // as a marshaller, we should be robust, so we'll continue to marshal
215         // this document by skipping this missing object.
216         endNamespaceDecls(null);
217         endAttributes();
218     }
219 
220 
221     public void reportError( ValidationEvent ve ) throws SAXException {
222         ValidationEventHandler handler;
223 
224         try {
225             handler = marshaller.getEventHandler();
226         } catch( JAXBException e ) {
227             throw new SAXException2(e);
228         }
229 
230         if(!handler.handleEvent(ve)) {
231             if(ve.getLinkedException() instanceof Exception)
232                 throw new SAXException2((Exception)ve.getLinkedException());
233             else
234                 throw new SAXException2(ve.getMessage());
235         }
236     }
237 
238     /**
239      * Report an error found as an exception.
240      *
241      * @param fieldName
242      *      the name of the property being processed when an error is found.
243      */
244     public final void reportError(String fieldName, Throwable t) throws SAXException {
245         ValidationEvent ve = new ValidationEventImpl(ValidationEvent.ERROR,
246             t.getMessage(), getCurrentLocation(fieldName), t);
247         reportError(ve);
248     }
249 
250     public void startElement(Name tagName, Object outerPeer) {
251         startElement();
252         nse.setTagName(tagName,outerPeer);
253     }
254 
255     public void startElement(String nsUri, String localName, String preferredPrefix, Object outerPeer) {
256         startElement();
257         int idx = nsContext.declareNsUri(nsUri, preferredPrefix, false);
258         nse.setTagName(idx,localName,outerPeer);
259     }
260 
261     /**
262      * Variation of {@link #startElement(String, String, String, Object)} that forces
263      * a specific prefix. Needed to preserve the prefix when marshalling DOM.
264      */
265     public void startElementForce(String nsUri, String localName, String forcedPrefix, Object outerPeer) {
266         startElement();
267         int idx = nsContext.force(nsUri, forcedPrefix);
268         nse.setTagName(idx,localName,outerPeer);
269     }
270 
271     public void endNamespaceDecls(Object innerPeer) throws IOException, XMLStreamException {
272         nsContext.collectionMode = false;
273         nse.startElement(out,innerPeer);
274     }
275 
276     /**
277      * Switches to the "marshal child texts/elements" mode.
278      * This method has to be called after the 1st pass is completed.
279      */
280     public void endAttributes() throws SAXException, IOException, XMLStreamException  {
281         if(!seenRoot) {
282             seenRoot = true;
283             if(schemaLocation!=null || noNsSchemaLocation!=null) {
284                 int p = nsContext.getPrefixIndex(WellKnownNamespace.XML_SCHEMA_INSTANCE);
285                 if(schemaLocation!=null)
286                     out.attribute(p,"schemaLocation",schemaLocation);
287                 if(noNsSchemaLocation!=null)
288                     out.attribute(p,"noNamespaceSchemaLocation",noNsSchemaLocation);
289             }
290         }
291 
292         out.endStartTag();
293     }
294 
295     /**
296      * Ends marshalling of an element.
297      * Pops the internal stack.
298      */
299     public void endElement() throws SAXException, IOException, XMLStreamException {
300         nse.endElement(out);
301         nse = nse.pop();
302         textHasAlreadyPrinted = false;
303     }
304 
305     public void leafElement( Name tagName, String data, String fieldName ) throws SAXException, IOException, XMLStreamException {
306         if(seenRoot) {
307             textHasAlreadyPrinted = false;
308             nse = nse.push();
309             out.beginStartTag(tagName);
310             out.endStartTag();
311             if(data != null)
312                 try {
313                         out.text(data,false);
314                 } catch (IllegalArgumentException e) {
315                     throw new IllegalArgumentException(Messages.ILLEGAL_CONTENT.format(fieldName, e.getMessage()));
316                 }
317             out.endTag(tagName);
318             nse = nse.pop();
319         } else {
320             // root element has additional processing like xsi:schemaLocation,
321             // so we need to go the slow way
322             startElement(tagName,null);
323             endNamespaceDecls(null);
324             endAttributes();
325                 try {
326                     out.text(data, false);
327                 } catch (IllegalArgumentException e) {
328                     throw new IllegalArgumentException(Messages.ILLEGAL_CONTENT.format(fieldName, e.getMessage()));
329                 }
330             endElement();
331         }
332     }
333 
334     public void leafElement( Name tagName, Pcdata data, String fieldName ) throws SAXException, IOException, XMLStreamException {
335         if(seenRoot) {
336             textHasAlreadyPrinted = false;
337             nse = nse.push();
338             out.beginStartTag(tagName);
339             out.endStartTag();
340             if(data != null)
341                 out.text(data,false);
342             out.endTag(tagName);
343             nse = nse.pop();
344         } else {
345             // root element has additional processing like xsi:schemaLocation,
346             // so we need to go the slow way
347             startElement(tagName,null);
348             endNamespaceDecls(null);
349             endAttributes();
350             out.text(data,false);
351             endElement();
352         }
353     }
354 
355     public void leafElement( Name tagName, int data, String fieldName ) throws SAXException, IOException, XMLStreamException {
356         intData.reset(data);
357         leafElement(tagName,intData,fieldName);
358     }
359 
360     /**
361      * Marshalls text.
362      *
363      * <p>
364      * This method can be called after the {@link #endAttributes()}
365      * method to marshal texts inside elements.
366      * If the method is called more than once, those texts are considered
367      * as separated by whitespaces. For example,
368      *
369      * <pre>
370      * c.startElement("","foo");
371      * c.endAttributes();
372      * c.text("abc");
373      * c.text("def");
374      *   c.startElement("","bar");
375      *   c.endAttributes();
376      *   c.endElement();
377      * c.text("ghi");
378      * c.endElement();
379      * </pre>
380      *
381      * will generate <code>&lt;foo>abc def&lt;bar/>ghi&lt;/foo></code>.
382      */
383     public void text( String text, String fieldName ) throws SAXException, IOException, XMLStreamException {
384         // If the assertion fails, it must be a bug of xjc.
385         // right now, we are not expecting the text method to be called.
386         if(text==null) {
387             reportMissingObjectError(fieldName);
388             return;
389         }
390 
391         out.text(text,textHasAlreadyPrinted);
392         textHasAlreadyPrinted = true;
393     }
394 
395     /**
396      * The {@link #text(String, String)} method that takes {@link Pcdata}.
397      */
398     public void text( Pcdata text, String fieldName ) throws SAXException, IOException, XMLStreamException {
399         // If the assertion fails, it must be a bug of xjc.
400         // right now, we are not expecting the text method to be called.
401         if(text==null) {
402             reportMissingObjectError(fieldName);
403             return;
404         }
405 
406         out.text(text,textHasAlreadyPrinted);
407         textHasAlreadyPrinted = true;
408     }
409 
410     public void attribute(String uri, String local, String value) throws SAXException {
411         int prefix;
412         if(uri.length()==0) {
413             // default namespace. don't need prefix
414             prefix = -1;
415         } else {
416             prefix = nsContext.getPrefixIndex(uri);
417         }
418 
419         try {
420             out.attribute(prefix,local,value);
421         } catch (IOException e) {
422             throw new SAXException2(e);
423         } catch (XMLStreamException e) {
424             throw new SAXException2(e);
425         }
426     }
427 
428     public void attribute(Name name, CharSequence value) throws IOException, XMLStreamException {
429         // TODO: consider having the version that takes Pcdata.
430         // it's common for an element to have int attributes
431         out.attribute(name,value.toString());
432     }
433 
434     public NamespaceContext2 getNamespaceContext() {
435         return nsContext;
436     }
437 
438 
439     public String onID( Object owner, String value ) {
440         objectsWithId.add(owner);
441         return value;
442     }
443 
444     public String onIDREF( Object obj ) throws SAXException {
445         String id;
446         try {
447             id = getIdFromObject(obj);
448         } catch (JAXBException e) {
449             reportError(null,e);
450             return null; // recover by returning null
451         }
452         idReferencedObjects.add(obj);
453         if(id==null) {
454             reportError( new NotIdentifiableEventImpl(
455                 ValidationEvent.ERROR,
456                 Messages.NOT_IDENTIFIABLE.format(),
457                 new ValidationEventLocatorImpl(obj) ) );
458         }
459         return id;
460     }
461 
462 
463     // TODO: think about the exception handling.
464     // I suppose we don't want to use SAXException. -kk
465 
466     public void childAsRoot(Object obj) throws JAXBException, IOException, SAXException, XMLStreamException {
467         final JaxBeanInfo beanInfo = grammar.getBeanInfo(obj, true);
468 
469         // since the same object will be reported to childAsRoot or
470         // childAsXsiType, don't make it a part of the collision check.
471         // but we do need to push it so that getXMIMEContentType will work.
472         cycleDetectionStack.pushNocheck(obj);
473 
474         final boolean lookForLifecycleMethods = beanInfo.lookForLifecycleMethods();
475         if (lookForLifecycleMethods) {
476             fireBeforeMarshalEvents(beanInfo, obj);
477         }
478 
479         beanInfo.serializeRoot(obj,this);
480 
481         if (lookForLifecycleMethods) {
482             fireAfterMarshalEvents(beanInfo, obj);
483         }
484 
485         cycleDetectionStack.pop();
486     }
487 
488     /**
489      * Pushes the object to {@link #cycleDetectionStack} and also
490      * detect any cycles.
491      *
492      * When a cycle is found, this method tries to recover from it.
493      *
494      * @return
495      *      the object that should be marshalled instead of the given <tt>obj</tt>,
496      *      or null if the error is found and we need to avoid marshalling this object
497      *      to prevent infinite recursion. When this method returns null, the error
498      *      has already been reported.
499      */
500     private Object pushObject(Object obj, String fieldName) throws SAXException {
501         if(!cycleDetectionStack.push(obj))
502             return obj;
503 
504         // allow the object to nominate its replacement
505         if(obj instanceof CycleRecoverable) {
506             obj = ((CycleRecoverable)obj).onCycleDetected(new CycleRecoverable.Context(){
507                 public Marshaller getMarshaller() {
508                     return marshaller;
509                 }
510             });
511             if(obj!=null) {
512                 // object nominated its replacement.
513                 // we still need to make sure that the nominated.
514                 // this may cause inifinite recursion on its own.
515                 cycleDetectionStack.pop();
516                 return pushObject(obj,fieldName);
517             } else
518                 return null;
519         }
520 
521         // cycle detected and no one is catching the error.
522         reportError(new ValidationEventImpl(
523             ValidationEvent.ERROR,
524             Messages.CYCLE_IN_MARSHALLER.format(cycleDetectionStack.getCycleString()),
525             getCurrentLocation(fieldName),
526             null));
527         return null;
528     }
529 
530     /**
531      * The equivalent of:
532      *
533      * <pre>
534      * childAsURIs(child, fieldName);
535      * endNamespaceDecls();
536      * childAsAttributes(child, fieldName);
537      * endAttributes();
538      * childAsBody(child, fieldName);
539      * </pre>
540      *
541      * This produces the given child object as the sole content of
542      * an element.
543      * Used to reduce the code size in the generated marshaller.
544      */
545     public final void childAsSoleContent( Object child, String fieldName) throws SAXException, IOException, XMLStreamException {
546         if(child==null) {
547             handleMissingObjectError(fieldName);
548         } else {
549             child = pushObject(child,fieldName);
550             if(child==null) {
551                 // error recovery
552                 endNamespaceDecls(null);
553                 endAttributes();
554                 cycleDetectionStack.pop();
555             }
556 
557             JaxBeanInfo beanInfo;
558             try {
559                 beanInfo = grammar.getBeanInfo(child,true);
560             } catch (JAXBException e) {
561                 reportError(fieldName,e);
562                 // recover by ignore
563                 endNamespaceDecls(null);
564                 endAttributes();
565                 cycleDetectionStack.pop();
566                 return;
567             }
568 
569             final boolean lookForLifecycleMethods = beanInfo.lookForLifecycleMethods();
570             if (lookForLifecycleMethods) {
571                 fireBeforeMarshalEvents(beanInfo, child);
572             }
573 
574             beanInfo.serializeURIs(child,this);
575             endNamespaceDecls(child);
576             beanInfo.serializeAttributes(child,this);
577             endAttributes();
578             beanInfo.serializeBody(child,this);
579 
580             if (lookForLifecycleMethods) {
581                 fireAfterMarshalEvents(beanInfo, child);
582             }
583 
584             cycleDetectionStack.pop();
585         }
586     }
587 
588 
589     // the version of childAsXXX where it produces @xsi:type if the expected type name
590     // and the actual type name differs.
591 
592     /**
593      * This method is called when a type child object is found.
594      *
595      * <p>
596      * This method produces events of the following form:
597      * <pre>
598      * NSDECL* "endNamespaceDecls" ATTRIBUTE* "endAttributes" BODY
599      * </pre>
600      * optionally including @xsi:type if necessary.
601      *
602      * @param child
603      *      Object to be marshalled. The {@link JaxBeanInfo} for
604      *      this object must return a type name.
605      * @param expected
606      *      Expected type of the object.
607      * @param fieldName
608      *      property name of the parent objeect from which 'o' comes.
609      *      Used as a part of the error message in case anything goes wrong
610      *      with 'o'.
611      */
612     public final void childAsXsiType( Object child, String fieldName, JaxBeanInfo expected, boolean nillable) throws SAXException, IOException, XMLStreamException {
613         if(child==null) {
614             handleMissingObjectError(fieldName);
615         } else {
616             child = pushObject(child,fieldName);
617             if(child==null) { // error recovery
618                 endNamespaceDecls(null);
619                 endAttributes();
620                 return;
621             }
622 
623             boolean asExpected = child.getClass()==expected.jaxbType;
624             JaxBeanInfo actual = expected;
625             QName actualTypeName = null;
626 
627             if((asExpected) && (actual.lookForLifecycleMethods())) {
628                 fireBeforeMarshalEvents(actual, child);
629             }
630 
631             if(!asExpected) {
632                 try {
633                     actual = grammar.getBeanInfo(child,true);
634                     if (actual.lookForLifecycleMethods()) {
635                         fireBeforeMarshalEvents(actual, child);
636                     }
637                 } catch (JAXBException e) {
638                     reportError(fieldName,e);
639                     endNamespaceDecls(null);
640                     endAttributes();
641                     return; // recover by ignore
642                 }
643                 if(actual==expected)
644                     asExpected = true;
645                 else {
646                     actualTypeName = actual.getTypeName(child);
647                     if(actualTypeName==null) {
648                         reportError(new ValidationEventImpl(
649                                 ValidationEvent.ERROR,
650                                 Messages.SUBSTITUTED_BY_ANONYMOUS_TYPE.format(
651                                     expected.jaxbType.getName(),
652                                     child.getClass().getName(),
653                                     actual.jaxbType.getName()),
654                                 getCurrentLocation(fieldName)));
655                         // recover by not printing @xsi:type
656                     } else {
657                         getNamespaceContext().declareNamespace(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
658                         getNamespaceContext().declareNamespace(actualTypeName.getNamespaceURI(),null,false);
659                     }
660                 }
661             }
662             actual.serializeURIs(child,this);
663 
664             if (nillable) {
665                 getNamespaceContext().declareNamespace(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
666             }
667 
668             endNamespaceDecls(child);
669             if(!asExpected) {
670                 attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE,"type",
671                     DatatypeConverter.printQName(actualTypeName,getNamespaceContext()));
672             }
673 
674             actual.serializeAttributes(child,this);
675             boolean nilDefined = actual.isNilIncluded();
676             if ((nillable) && (!nilDefined)) {
677                 attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE,"nil","true");
678             }
679 
680             endAttributes();
681             actual.serializeBody(child,this);
682 
683             if (actual.lookForLifecycleMethods()) {
684                 fireAfterMarshalEvents(actual, child);
685             }
686 
687             cycleDetectionStack.pop();
688         }
689     }
690 
691     /**
692      * Invoke the afterMarshal api on the external listener (if it exists) and on the bean embedded
693      * afterMarshal api(if it exists).
694      *
695      * This method is called only after the callee has determined that beanInfo.lookForLifecycleMethods == true.
696      *
697      * @param beanInfo
698      * @param currentTarget
699      */
700     private void fireAfterMarshalEvents(final JaxBeanInfo beanInfo, Object currentTarget) {
701         // first invoke bean embedded listener
702         if (beanInfo.hasAfterMarshalMethod()) {
703             Method m = beanInfo.getLifecycleMethods().afterMarshal;
704             fireMarshalEvent(currentTarget, m);
705         }
706 
707         // then invoke external listener before bean embedded listener
708         Marshaller.Listener externalListener = marshaller.getListener();
709         if (externalListener != null) {
710             externalListener.afterMarshal(currentTarget);
711         }
712 
713     }
714 
715     /**
716      * Invoke the beforeMarshal api on the external listener (if it exists) and on the bean embedded
717      * beforeMarshal api(if it exists).
718      *
719      * This method is called only after the callee has determined that beanInfo.lookForLifecycleMethods == true.
720      *
721      * @param beanInfo
722      * @param currentTarget
723      */
724     private void fireBeforeMarshalEvents(final JaxBeanInfo beanInfo, Object currentTarget) {
725         // first invoke bean embedded listener
726         if (beanInfo.hasBeforeMarshalMethod()) {
727             Method m = beanInfo.getLifecycleMethods().beforeMarshal;
728             fireMarshalEvent(currentTarget, m);
729         }
730 
731         // then invoke external listener
732         Marshaller.Listener externalListener = marshaller.getListener();
733         if (externalListener != null) {
734             externalListener.beforeMarshal(currentTarget);
735         }
736     }
737 
738     private void fireMarshalEvent(Object target, Method m) {
739         try {
740             m.invoke(target, marshaller);
741         } catch (Exception e) {
742             // this really only happens if there is a bug in the ri
743             throw new IllegalStateException(e);
744         }
745     }
746 
747     public void attWildcardAsURIs(Map<QName,String> attributes, String fieldName) {
748         if(attributes==null)    return;
749         for( Map.Entry<QName,String> e : attributes.entrySet() ) {
750             QName n = e.getKey();
751             String nsUri = n.getNamespaceURI();
752             if(nsUri.length()>0) {
753                 String p = n.getPrefix();
754                 if(p.length()==0)   p=null;
755                 nsContext.declareNsUri(nsUri, p, true);
756             }
757         }
758     }
759 
760     public void attWildcardAsAttributes(Map<QName,String> attributes, String fieldName) throws SAXException {
761         if(attributes==null)    return;
762         for( Map.Entry<QName,String> e : attributes.entrySet() ) {
763             QName n = e.getKey();
764             attribute(n.getNamespaceURI(),n.getLocalPart(),e.getValue());
765         }
766     }
767 
768     /**
769      * Short for the following call sequence:
770      *
771      * <pre>
772          getNamespaceContext().declareNamespace(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
773          endNamespaceDecls();
774          attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE,"nil","true");
775          endAttributes();
776      * </pre>
777      */
778     public final void writeXsiNilTrue() throws SAXException, IOException, XMLStreamException {
779         getNamespaceContext().declareNamespace(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
780         endNamespaceDecls(null);
781         attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE,"nil","true");
782         endAttributes();
783     }
784 
785     public <E> void writeDom(E element, DomHandler<E, ?> domHandler, Object parentBean, String fieldName) throws SAXException {
786         Source source = domHandler.marshal(element,this);
787         if(contentHandlerAdapter==null)
788             contentHandlerAdapter = new ContentHandlerAdaptor(this);
789         try {
790             getIdentityTransformer().transform(source,new SAXResult(contentHandlerAdapter));
791         } catch (TransformerException e) {
792             reportError(fieldName,e);
793         }
794     }
795 
796     public Transformer getIdentityTransformer() {
797         if (identityTransformer==null)
798             identityTransformer = JAXBContextImpl.createTransformer(grammar.disableSecurityProcessing);
799         return identityTransformer;
800     }
801 
802     public void setPrefixMapper(NamespacePrefixMapper prefixMapper) {
803         nsContext.setPrefixMapper(prefixMapper);
804     }
805 
806     /**
807      * Reset this object to write to the specified output.
808      *
809      * @param schemaLocation
810      *      if non-null, this value is printed on the root element as xsi:schemaLocation
811      * @param noNsSchemaLocation
812      *      Similar to 'schemaLocation' but this one works for xsi:noNamespaceSchemaLocation
813      */
814     public void startDocument(XmlOutput out,boolean fragment,String schemaLocation,String noNsSchemaLocation) throws IOException, SAXException, XMLStreamException {
815         pushCoordinator();
816         nsContext.reset();
817         nse = nsContext.getCurrent();
818         if(attachmentMarshaller!=null && attachmentMarshaller.isXOPPackage())
819             out = new MTOMXmlOutput(out);
820         this.out = out;
821         objectsWithId.clear();
822         idReferencedObjects.clear();
823         textHasAlreadyPrinted = false;
824         seenRoot = false;
825         this.schemaLocation = schemaLocation;
826         this.noNsSchemaLocation = noNsSchemaLocation;
827         this.fragment = fragment;
828         this.inlineBinaryFlag = false;
829         this.expectedMimeType = null;
830         cycleDetectionStack.reset();
831 
832         out.startDocument(this,fragment,knownUri2prefixIndexMap,nsContext);
833     }
834 
835     public void endDocument() throws IOException, SAXException, XMLStreamException {
836         out.endDocument(fragment);
837     }
838 
839     public void close() {
840         out = null;
841         clearCurrentProperty();
842         popCoordinator();
843     }
844 
845     /**
846      * This method can be called after {@link #startDocument} is called
847      * but before the marshalling begins, to set the currently in-scope namespace
848      * bindings.
849      *
850      * <p>
851      * This method is useful to avoid redundant namespace declarations when
852      * the marshalling is producing a sub-document.
853      */
854     public void addInscopeBinding(String nsUri,String prefix) {
855         nsContext.put(nsUri,prefix);
856     }
857 
858     /**
859      * Gets the MIME type with which the binary content shall be printed.
860      *
861      * <p>
862      * This method shall be used from those {@link RuntimeBuiltinLeafInfo} that are
863      * bound to base64Binary.
864      *
865      * @see JAXBContextImpl#getXMIMEContentType(Object)
866      */
867     public String getXMIMEContentType() {
868         // xmime:contentType takes precedence
869         String v = grammar.getXMIMEContentType(cycleDetectionStack.peek());
870         if(v!=null)     return v;
871 
872         // then look for the current in-scope @XmlMimeType
873         if(expectedMimeType!=null)
874             return expectedMimeType.toString();
875 
876         return null;
877     }
878 
879     private void startElement() {
880         nse = nse.push();
881 
882         if( !seenRoot ) {
883 
884             if (grammar.getXmlNsSet() != null) {
885                 for(XmlNs xmlNs : grammar.getXmlNsSet())
886                     nsContext.declareNsUri(
887                         xmlNs.namespaceURI(),
888                         xmlNs.prefix() == null ? "" : xmlNs.prefix(),
889                         xmlNs.prefix() != null);
890             }
891 
892             // seenRoot set to true in endAttributes
893             // first declare all known URIs
894             String[] knownUris = nameList.namespaceURIs;
895             for( int i=0; i<knownUris.length; i++ )
896                 knownUri2prefixIndexMap[i] = nsContext.declareNsUri(knownUris[i], null, nameList.nsUriCannotBeDefaulted[i]);
897 
898             // then declare user-specified namespace URIs.
899             // work defensively. we are calling an user-defined method.
900             String[] uris = nsContext.getPrefixMapper().getPreDeclaredNamespaceUris();
901             if( uris!=null ) {
902                 for (String uri : uris) {
903                     if (uri != null)
904                         nsContext.declareNsUri(uri, null, false);
905                 }
906             }
907             String[] pairs = nsContext.getPrefixMapper().getPreDeclaredNamespaceUris2();
908             if( pairs!=null ) {
909                 for( int i=0; i<pairs.length; i+=2 ) {
910                     String prefix = pairs[i];
911                     String nsUri = pairs[i+1];
912                     if(prefix!=null && nsUri!=null)
913                         // in this case, we don't want the redundant binding consolidation
914                         // to happen (such as declaring the same namespace URI twice with
915                         // different prefixes.) Hence we call the put method directly.
916                         nsContext.put(nsUri,prefix);
917                 }
918             }
919 
920             if(schemaLocation!=null || noNsSchemaLocation!=null) {
921                 nsContext.declareNsUri(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
922             }
923         }
924 
925         nsContext.collectionMode = true;
926         textHasAlreadyPrinted = false;
927     }
928 
929     private MimeType expectedMimeType;
930 
931     /**
932      * This method is used by {@link MimeTypedTransducer} to set the expected MIME type
933      * for the encapsulated {@link Transducer}.
934      */
935     public MimeType setExpectedMimeType(MimeType expectedMimeType) {
936         MimeType old = this.expectedMimeType;
937         this.expectedMimeType = expectedMimeType;
938         return old;
939     }
940 
941     /**
942      * True to force inlining.
943      */
944     private boolean inlineBinaryFlag;
945 
946     public boolean setInlineBinaryFlag(boolean value) {
947         boolean old = inlineBinaryFlag;
948         this.inlineBinaryFlag = value;
949         return old;
950     }
951 
952     public boolean getInlineBinaryFlag() {
953         return inlineBinaryFlag;
954     }
955 
956     /**
957      * Field used to support an {@link XmlSchemaType} annotation.
958      *
959      * <p>
960      * When we are marshalling a property with an effective {@link XmlSchemaType},
961      * this field is set to hold the QName of that type. The {@link Transducer} that
962      * actually converts a Java object into XML can look this property to decide
963      * how to marshal the value.
964      */
965     private QName schemaType;
966 
967     public QName setSchemaType(QName st) {
968         QName old = schemaType;
969         schemaType = st;
970         return old;
971     }
972 
973     public QName getSchemaType() {
974         return schemaType;
975     }
976 
977     public void setObjectIdentityCycleDetection(boolean val) {
978         cycleDetectionStack.setUseIdentity(val);
979     }
980     public boolean getObjectIdentityCycleDetection() {
981         return cycleDetectionStack.getUseIdentity();
982     }
983 
984     void reconcileID() throws SAXException {
985         // find objects that were not a part of the object graph
986         idReferencedObjects.removeAll(objectsWithId);
987 
988         for( Object idObj : idReferencedObjects ) {
989             try {
990                 String id = getIdFromObject(idObj);
991                 reportError( new NotIdentifiableEventImpl(
992                     ValidationEvent.ERROR,
993                     Messages.DANGLING_IDREF.format(id),
994                     new ValidationEventLocatorImpl(idObj) ) );
995             } catch (JAXBException e) {
996                 // this error should have been reported already. just ignore here.
997             }
998         }
999 
1000         // clear the garbage
1001         idReferencedObjects.clear();
1002         objectsWithId.clear();
1003     }
1004 
1005     public boolean handleError(Exception e) {
1006         return handleError(e,cycleDetectionStack.peek(),null);
1007     }
1008 
1009     public boolean handleError(Exception e,Object source,String fieldName) {
1010         return handleEvent(
1011             new ValidationEventImpl(
1012                 ValidationEvent.ERROR,
1013                 e.getMessage(),
1014                 new ValidationEventLocatorExImpl(source,fieldName),
1015                     e));
1016     }
1017 
1018     public boolean handleEvent(ValidationEvent event) {
1019         try {
1020             return marshaller.getEventHandler().handleEvent(event);
1021         } catch (JAXBException e) {
1022             // impossible
1023             throw new Error(e);
1024         }
1025     }
1026 
1027     private void reportMissingObjectError(String fieldName) throws SAXException {
1028         reportError(new ValidationEventImpl(
1029             ValidationEvent.ERROR,
1030             Messages.MISSING_OBJECT.format(fieldName),
1031                 getCurrentLocation(fieldName),
1032             new NullPointerException() ));
1033     }
1034 
1035     /**
1036      * Called when a referenced object doesn't have an ID.
1037      */
1038     public void errorMissingId(Object obj) throws SAXException {
1039         reportError( new ValidationEventImpl(
1040             ValidationEvent.ERROR,
1041             Messages.MISSING_ID.format(obj),
1042             new ValidationEventLocatorImpl(obj)) );
1043     }
1044 
1045     public ValidationEventLocator getCurrentLocation(String fieldName) {
1046         return new ValidationEventLocatorExImpl(cycleDetectionStack.peek(),fieldName);
1047     }
1048 
1049     protected ValidationEventLocator getLocation() {
1050         return getCurrentLocation(null);
1051     }
1052 
1053     /**
1054      * May return null when the property hasn't been set.
1055      * Introduced based on Jersey requirements.
1056      */
1057     public Property getCurrentProperty() {
1058         return currentProperty.get();
1059     }
1060 
1061     /**
1062      * Takes care of cleaning the currentProperty. Must be called from the same thread that created the XMLSerializer.
1063      */
1064     public void clearCurrentProperty() {
1065         if (currentProperty != null) {
1066             currentProperty.remove();
1067         }
1068     }
1069 
1070     /**
1071      * When called from within the realm of the marshaller, this method
1072      * returns the current {@link XMLSerializer} in charge.
1073      */
1074     public static XMLSerializer getInstance() {
1075         return (XMLSerializer)Coordinator._getInstance();
1076     }
1077 }