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><foo>abc def<bar/>ghi</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 }