1 /*
   2  * Copyright (c) 1997, 2013, 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 <foo>abc def<bar/>ghi</foo>}.
 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 {@code obj},
 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 }