Sun, 18 Jun 2017 23:18:45 +0100
8172297: In java 8, the marshalling with JAX-WS does not escape carriage return
Reviewed-by: lancea
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 */
26 package com.sun.xml.internal.bind.v2.runtime;
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;
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;
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;
70 import org.xml.sax.SAXException;
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;
122 /** The XML printer. */
123 private XmlOutput out;
125 public final NameList nameList;
127 public final int[] knownUri2prefixIndexMap;
129 private final NamespaceContextImpl nsContext;
131 private NamespaceContextImpl.Element nse;
133 // Introduced based on Jersey requirements - to be able to retrieve marshalled name
134 ThreadLocal<Property> currentProperty = new ThreadLocal<Property>();
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;
142 /**
143 * Set to false once we see the start tag of the root element.
144 */
145 private boolean seenRoot = false;
147 /** Marshaller object to which this object belongs. */
148 private final MarshallerImpl marshaller;
150 /** Objects referenced through IDREF. */
151 private final Set<Object> idReferencedObjects = new HashSet<Object>();
153 /** Objects with ID. */
154 private final Set<Object> objectsWithId = new HashSet<Object>();
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>();
162 /** Optional attributes to go with root element. */
163 private String schemaLocation;
164 private String noNsSchemaLocation;
166 /** Lazily created identitiy transformer. */
167 private Transformer identityTransformer;
169 /** Lazily created. */
170 private ContentHandlerAdaptor contentHandlerAdapter;
172 private boolean fragment;
174 /**
175 * Cached instance of {@link Base64Data}.
176 */
177 private Base64Data base64Data;
179 /**
180 * Cached instance of {@link IntData}.
181 */
182 private final IntData intData = new IntData();
184 public AttachmentMarshaller attachmentMarshaller;
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 }
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 }
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 }
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 }
221 public void reportError( ValidationEvent ve ) throws SAXException {
222 ValidationEventHandler handler;
224 try {
225 handler = marshaller.getEventHandler();
226 } catch( JAXBException e ) {
227 throw new SAXException2(e);
228 }
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 }
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 }
250 public void startElement(Name tagName, Object outerPeer) {
251 startElement();
252 nse.setTagName(tagName,outerPeer);
253 }
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 }
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 }
271 public void endNamespaceDecls(Object innerPeer) throws IOException, XMLStreamException {
272 nsContext.collectionMode = false;
273 nse.startElement(out,innerPeer);
274 }
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 }
292 out.endStartTag();
293 }
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 }
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 }
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 }
355 public void leafElement( Name tagName, int data, String fieldName ) throws SAXException, IOException, XMLStreamException {
356 intData.reset(data);
357 leafElement(tagName,intData,fieldName);
358 }
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 }
391 out.text(text,textHasAlreadyPrinted);
392 textHasAlreadyPrinted = true;
393 }
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 }
406 out.text(text,textHasAlreadyPrinted);
407 textHasAlreadyPrinted = true;
408 }
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 }
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 }
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 }
434 public NamespaceContext2 getNamespaceContext() {
435 return nsContext;
436 }
439 public String onID( Object owner, String value ) {
440 objectsWithId.add(owner);
441 return value;
442 }
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 }
463 // TODO: think about the exception handling.
464 // I suppose we don't want to use SAXException. -kk
466 public void childAsRoot(Object obj) throws JAXBException, IOException, SAXException, XMLStreamException {
467 final JaxBeanInfo beanInfo = grammar.getBeanInfo(obj, true);
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);
474 final boolean lookForLifecycleMethods = beanInfo.lookForLifecycleMethods();
475 if (lookForLifecycleMethods) {
476 fireBeforeMarshalEvents(beanInfo, obj);
477 }
479 beanInfo.serializeRoot(obj,this);
481 if (lookForLifecycleMethods) {
482 fireAfterMarshalEvents(beanInfo, obj);
483 }
485 cycleDetectionStack.pop();
486 }
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;
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 }
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 }
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 }
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 }
569 final boolean lookForLifecycleMethods = beanInfo.lookForLifecycleMethods();
570 if (lookForLifecycleMethods) {
571 fireBeforeMarshalEvents(beanInfo, child);
572 }
574 beanInfo.serializeURIs(child,this);
575 endNamespaceDecls(child);
576 beanInfo.serializeAttributes(child,this);
577 endAttributes();
578 beanInfo.serializeBody(child,this);
580 if (lookForLifecycleMethods) {
581 fireAfterMarshalEvents(beanInfo, child);
582 }
584 cycleDetectionStack.pop();
585 }
586 }
589 // the version of childAsXXX where it produces @xsi:type if the expected type name
590 // and the actual type name differs.
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 }
623 boolean asExpected = child.getClass()==expected.jaxbType;
624 JaxBeanInfo actual = expected;
625 QName actualTypeName = null;
627 if((asExpected) && (actual.lookForLifecycleMethods())) {
628 fireBeforeMarshalEvents(actual, child);
629 }
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);
664 if (nillable) {
665 getNamespaceContext().declareNamespace(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
666 }
668 endNamespaceDecls(child);
669 if(!asExpected) {
670 attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE,"type",
671 DatatypeConverter.printQName(actualTypeName,getNamespaceContext()));
672 }
674 actual.serializeAttributes(child,this);
675 boolean nilDefined = actual.isNilIncluded();
676 if ((nillable) && (!nilDefined)) {
677 attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE,"nil","true");
678 }
680 endAttributes();
681 actual.serializeBody(child,this);
683 if (actual.lookForLifecycleMethods()) {
684 fireAfterMarshalEvents(actual, child);
685 }
687 cycleDetectionStack.pop();
688 }
689 }
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 }
707 // then invoke external listener before bean embedded listener
708 Marshaller.Listener externalListener = marshaller.getListener();
709 if (externalListener != null) {
710 externalListener.afterMarshal(currentTarget);
711 }
713 }
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 }
731 // then invoke external listener
732 Marshaller.Listener externalListener = marshaller.getListener();
733 if (externalListener != null) {
734 externalListener.beforeMarshal(currentTarget);
735 }
736 }
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 }
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 }
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 }
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 }
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 }
796 public Transformer getIdentityTransformer() {
797 if (identityTransformer==null)
798 identityTransformer = JAXBContextImpl.createTransformer(grammar.disableSecurityProcessing);
799 return identityTransformer;
800 }
802 public void setPrefixMapper(NamespacePrefixMapper prefixMapper) {
803 nsContext.setPrefixMapper(prefixMapper);
804 }
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();
832 out.startDocument(this,fragment,knownUri2prefixIndexMap,nsContext);
833 }
835 public void endDocument() throws IOException, SAXException, XMLStreamException {
836 out.endDocument(fragment);
837 }
839 public void close() {
840 out = null;
841 clearCurrentProperty();
842 popCoordinator();
843 }
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 }
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;
872 // then look for the current in-scope @XmlMimeType
873 if(expectedMimeType!=null)
874 return expectedMimeType.toString();
876 return null;
877 }
879 private void startElement() {
880 nse = nse.push();
882 if( !seenRoot ) {
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 }
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]);
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 }
920 if(schemaLocation!=null || noNsSchemaLocation!=null) {
921 nsContext.declareNsUri(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
922 }
923 }
925 nsContext.collectionMode = true;
926 textHasAlreadyPrinted = false;
927 }
929 private MimeType expectedMimeType;
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 }
941 /**
942 * True to force inlining.
943 */
944 private boolean inlineBinaryFlag;
946 public boolean setInlineBinaryFlag(boolean value) {
947 boolean old = inlineBinaryFlag;
948 this.inlineBinaryFlag = value;
949 return old;
950 }
952 public boolean getInlineBinaryFlag() {
953 return inlineBinaryFlag;
954 }
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;
967 public QName setSchemaType(QName st) {
968 QName old = schemaType;
969 schemaType = st;
970 return old;
971 }
973 public QName getSchemaType() {
974 return schemaType;
975 }
977 public void setObjectIdentityCycleDetection(boolean val) {
978 cycleDetectionStack.setUseIdentity(val);
979 }
980 public boolean getObjectIdentityCycleDetection() {
981 return cycleDetectionStack.getUseIdentity();
982 }
984 void reconcileID() throws SAXException {
985 // find objects that were not a part of the object graph
986 idReferencedObjects.removeAll(objectsWithId);
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 }
1000 // clear the garbage
1001 idReferencedObjects.clear();
1002 objectsWithId.clear();
1003 }
1005 public boolean handleError(Exception e) {
1006 return handleError(e,cycleDetectionStack.peek(),null);
1007 }
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 }
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 }
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 }
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 }
1045 public ValidationEventLocator getCurrentLocation(String fieldName) {
1046 return new ValidationEventLocatorExImpl(cycleDetectionStack.peek(),fieldName);
1047 }
1049 protected ValidationEventLocator getLocation() {
1050 return getCurrentLocation(null);
1051 }
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 }
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 }
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 }