/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.xml.internal.bind.v2.runtime;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.activation.MimeType;
import javax.xml.bind.DatatypeConverter;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.ValidationEventLocator;
import javax.xml.bind.annotation.DomHandler;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.attachment.AttachmentMarshaller;
import javax.xml.bind.helpers.NotIdentifiableEventImpl;
import javax.xml.bind.helpers.ValidationEventImpl;
import javax.xml.bind.helpers.ValidationEventLocatorImpl;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.sax.SAXResult;
import com.sun.istack.internal.SAXException2;
import com.sun.xml.internal.bind.CycleRecoverable;
import com.sun.xml.internal.bind.api.AccessorException;
import com.sun.xml.internal.bind.marshaller.NamespacePrefixMapper;
import com.sun.xml.internal.bind.util.ValidationEventLocatorExImpl;
import com.sun.xml.internal.bind.v2.WellKnownNamespace;
import com.sun.xml.internal.bind.v2.model.runtime.RuntimeBuiltinLeafInfo;
import com.sun.xml.internal.bind.v2.runtime.output.MTOMXmlOutput;
import com.sun.xml.internal.bind.v2.runtime.output.NamespaceContextImpl;
import com.sun.xml.internal.bind.v2.runtime.output.Pcdata;
import com.sun.xml.internal.bind.v2.runtime.output.XmlOutput;
import com.sun.xml.internal.bind.v2.runtime.property.Property;
import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data;
import com.sun.xml.internal.bind.v2.runtime.unmarshaller.IntData;
import com.sun.xml.internal.bind.v2.util.CollisionCheckStack;
import org.xml.sax.SAXException;
Receives XML serialization event and writes to XmlOutput
.
This object coordinates the overall marshalling efforts across different
content-tree objects and different target formats.
The following CFG gives the proper sequence of method invocation.
MARSHALLING := ELEMENT
ELEMENT := "startElement" NSDECL* "endNamespaceDecls"
ATTRIBUTE* "endAttributes" BODY "endElement"
NSDECL := "declareNamespace"
ATTRIBUTE := "attribute"
ATTVALUES := "text"*
BODY := ( "text" | ELEMENT )*
A marshalling of one element consists of two stages. The first stage is
for marshalling attributes and collecting namespace declarations.
The second stage is for marshalling characters/child elements of that element.
Observe that multiple invocation of "text" is allowed.
Also observe that the namespace declarations are allowed only between
"startElement" and "endAttributes".
Exceptions in marshaller
IOException
, SAXException
, and XMLStreamException
are thrown from XmlOutput
. They are always considered fatal, and therefore caught only by MarshallerImpl
.
AccessorException
can be thrown when an access to a property/field fails, and this is considered as a recoverable error, so it's caught everywhere.
Author: Kohsuke Kawaguchi
/**
* Receives XML serialization event and writes to {@link XmlOutput}.
*
* <p>
* This object coordinates the overall marshalling efforts across different
* content-tree objects and different target formats.
*
* <p>
* The following CFG gives the proper sequence of method invocation.
*
* <pre>
* MARSHALLING := ELEMENT
* ELEMENT := "startElement" NSDECL* "endNamespaceDecls"
* ATTRIBUTE* "endAttributes" BODY "endElement"
*
* NSDECL := "declareNamespace"
*
* ATTRIBUTE := "attribute"
* ATTVALUES := "text"*
*
*
* BODY := ( "text" | ELEMENT )*
* </pre>
*
* <p>
* A marshalling of one element consists of two stages. The first stage is
* for marshalling attributes and collecting namespace declarations.
* The second stage is for marshalling characters/child elements of that element.
*
* <p>
* Observe that multiple invocation of "text" is allowed.
*
* <p>
* Also observe that the namespace declarations are allowed only between
* "startElement" and "endAttributes".
*
* <h2>Exceptions in marshaller</h2>
* <p>
* {@link IOException}, {@link SAXException}, and {@link XMLStreamException}
* are thrown from {@link XmlOutput}. They are always considered fatal, and
* therefore caught only by {@link MarshallerImpl}.
* <p>
* {@link AccessorException} can be thrown when an access to a property/field
* fails, and this is considered as a recoverable error, so it's caught everywhere.
*
* @author Kohsuke Kawaguchi
*/
public final class XMLSerializer extends Coordinator {
public final JAXBContextImpl grammar;
The XML printer. /** The XML printer. */
private XmlOutput out;
public final NameList nameList;
public final int[] knownUri2prefixIndexMap;
private final NamespaceContextImpl nsContext;
private NamespaceContextImpl.Element nse;
// Introduced based on Jersey requirements - to be able to retrieve marshalled name
ThreadLocal<Property> currentProperty = new ThreadLocal<Property>();
Set to true if a text is already written,
and we need to print ' ' for additional text methods.
/**
* Set to true if a text is already written,
* and we need to print ' ' for additional text methods.
*/
private boolean textHasAlreadyPrinted = false;
Set to false once we see the start tag of the root element.
/**
* Set to false once we see the start tag of the root element.
*/
private boolean seenRoot = false;
Marshaller object to which this object belongs. /** Marshaller object to which this object belongs. */
private final MarshallerImpl marshaller;
Objects referenced through IDREF. /** Objects referenced through IDREF. */
private final Set<Object> idReferencedObjects = new HashSet<Object>();
Objects with ID. /** Objects with ID. */
private final Set<Object> objectsWithId = new HashSet<Object>();
Used to detect cycles in the object.
Also used to learn what's being marshalled.
/**
* Used to detect cycles in the object.
* Also used to learn what's being marshalled.
*/
private final CollisionCheckStack<Object> cycleDetectionStack = new CollisionCheckStack<Object>();
Optional attributes to go with root element. /** Optional attributes to go with root element. */
private String schemaLocation;
private String noNsSchemaLocation;
Lazily created identitiy transformer. /** Lazily created identitiy transformer. */
private Transformer identityTransformer;
Lazily created. /** Lazily created. */
private ContentHandlerAdaptor contentHandlerAdapter;
private boolean fragment;
Cached instance of Base64Data
. /**
* Cached instance of {@link Base64Data}.
*/
private Base64Data base64Data;
Cached instance of IntData
. /**
* Cached instance of {@link IntData}.
*/
private final IntData intData = new IntData();
public AttachmentMarshaller attachmentMarshaller;
/*package*/ XMLSerializer( MarshallerImpl _owner ) {
this.marshaller = _owner;
this.grammar = marshaller.context;
nsContext = new NamespaceContextImpl(this);
nameList = marshaller.context.nameList;
knownUri2prefixIndexMap = new int[nameList.namespaceURIs.length];
}
Gets the cached instance of Base64Data
. Deprecated: Base64Data
is no longer cached, so that XMLStreamWriterEx impl can retain the data, like JAX-WS does.
/**
* Gets the cached instance of {@link Base64Data}.
*
* @deprecated
* {@link Base64Data} is no longer cached, so that
* XMLStreamWriterEx impl can retain the data, like JAX-WS does.
*/
public Base64Data getCachedBase64DataInstance() {
return new Base64Data();
}
Gets the ID value from an identifiable object.
/**
* Gets the ID value from an identifiable object.
*/
private String getIdFromObject(Object identifiableObject) throws SAXException, JAXBException {
return grammar.getBeanInfo(identifiableObject,true).getId(identifiableObject,this);
}
private void handleMissingObjectError(String fieldName) throws SAXException, IOException, XMLStreamException {
reportMissingObjectError(fieldName);
// as a marshaller, we should be robust, so we'll continue to marshal
// this document by skipping this missing object.
endNamespaceDecls(null);
endAttributes();
}
public void reportError( ValidationEvent ve ) throws SAXException {
ValidationEventHandler handler;
try {
handler = marshaller.getEventHandler();
} catch( JAXBException e ) {
throw new SAXException2(e);
}
if(!handler.handleEvent(ve)) {
if(ve.getLinkedException() instanceof Exception)
throw new SAXException2((Exception)ve.getLinkedException());
else
throw new SAXException2(ve.getMessage());
}
}
Report an error found as an exception.
Params: - fieldName –
the name of the property being processed when an error is found.
/**
* Report an error found as an exception.
*
* @param fieldName
* the name of the property being processed when an error is found.
*/
public final void reportError(String fieldName, Throwable t) throws SAXException {
ValidationEvent ve = new ValidationEventImpl(ValidationEvent.ERROR,
t.getMessage(), getCurrentLocation(fieldName), t);
reportError(ve);
}
public void startElement(Name tagName, Object outerPeer) {
startElement();
nse.setTagName(tagName,outerPeer);
}
public void startElement(String nsUri, String localName, String preferredPrefix, Object outerPeer) {
startElement();
int idx = nsContext.declareNsUri(nsUri, preferredPrefix, false);
nse.setTagName(idx,localName,outerPeer);
}
Variation of startElement(String, String, String, Object)
that forces a specific prefix. Needed to preserve the prefix when marshalling DOM. /**
* Variation of {@link #startElement(String, String, String, Object)} that forces
* a specific prefix. Needed to preserve the prefix when marshalling DOM.
*/
public void startElementForce(String nsUri, String localName, String forcedPrefix, Object outerPeer) {
startElement();
int idx = nsContext.force(nsUri, forcedPrefix);
nse.setTagName(idx,localName,outerPeer);
}
public void endNamespaceDecls(Object innerPeer) throws IOException, XMLStreamException {
nsContext.collectionMode = false;
nse.startElement(out,innerPeer);
}
Switches to the "marshal child texts/elements" mode.
This method has to be called after the 1st pass is completed.
/**
* Switches to the "marshal child texts/elements" mode.
* This method has to be called after the 1st pass is completed.
*/
public void endAttributes() throws SAXException, IOException, XMLStreamException {
if(!seenRoot) {
seenRoot = true;
if(schemaLocation!=null || noNsSchemaLocation!=null) {
int p = nsContext.getPrefixIndex(WellKnownNamespace.XML_SCHEMA_INSTANCE);
if(schemaLocation!=null)
out.attribute(p,"schemaLocation",schemaLocation);
if(noNsSchemaLocation!=null)
out.attribute(p,"noNamespaceSchemaLocation",noNsSchemaLocation);
}
}
out.endStartTag();
}
Ends marshalling of an element.
Pops the internal stack.
/**
* Ends marshalling of an element.
* Pops the internal stack.
*/
public void endElement() throws SAXException, IOException, XMLStreamException {
nse.endElement(out);
nse = nse.pop();
textHasAlreadyPrinted = false;
}
public void leafElement( Name tagName, String data, String fieldName ) throws SAXException, IOException, XMLStreamException {
if(seenRoot) {
textHasAlreadyPrinted = false;
nse = nse.push();
out.beginStartTag(tagName);
out.endStartTag();
if(data != null)
try {
out.text(data,false);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(Messages.ILLEGAL_CONTENT.format(fieldName, e.getMessage()));
}
out.endTag(tagName);
nse = nse.pop();
} else {
// root element has additional processing like xsi:schemaLocation,
// so we need to go the slow way
startElement(tagName,null);
endNamespaceDecls(null);
endAttributes();
try {
out.text(data, false);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(Messages.ILLEGAL_CONTENT.format(fieldName, e.getMessage()));
}
endElement();
}
}
public void leafElement( Name tagName, Pcdata data, String fieldName ) throws SAXException, IOException, XMLStreamException {
if(seenRoot) {
textHasAlreadyPrinted = false;
nse = nse.push();
out.beginStartTag(tagName);
out.endStartTag();
if(data != null)
out.text(data,false);
out.endTag(tagName);
nse = nse.pop();
} else {
// root element has additional processing like xsi:schemaLocation,
// so we need to go the slow way
startElement(tagName,null);
endNamespaceDecls(null);
endAttributes();
out.text(data,false);
endElement();
}
}
public void leafElement( Name tagName, int data, String fieldName ) throws SAXException, IOException, XMLStreamException {
intData.reset(data);
leafElement(tagName,intData,fieldName);
}
Marshalls text.
This method can be called after the endAttributes()
method to marshal texts inside elements. If the method is called more than once, those texts are considered as separated by whitespaces. For example,
c.startElement("","foo");
c.endAttributes();
c.text("abc");
c.text("def");
c.startElement("","bar");
c.endAttributes();
c.endElement();
c.text("ghi");
c.endElement();
will generate <foo>abc def<bar/>ghi</foo>
. /**
* Marshalls text.
*
* <p>
* This method can be called after the {@link #endAttributes()}
* method to marshal texts inside elements.
* If the method is called more than once, those texts are considered
* as separated by whitespaces. For example,
*
* <pre>
* c.startElement("","foo");
* c.endAttributes();
* c.text("abc");
* c.text("def");
* c.startElement("","bar");
* c.endAttributes();
* c.endElement();
* c.text("ghi");
* c.endElement();
* </pre>
*
* will generate {@code <foo>abc def<bar/>ghi</foo>}.
*/
public void text( String text, String fieldName ) throws SAXException, IOException, XMLStreamException {
// If the assertion fails, it must be a bug of xjc.
// right now, we are not expecting the text method to be called.
if(text==null) {
reportMissingObjectError(fieldName);
return;
}
out.text(text,textHasAlreadyPrinted);
textHasAlreadyPrinted = true;
}
The text(String, String)
method that takes Pcdata
. /**
* The {@link #text(String, String)} method that takes {@link Pcdata}.
*/
public void text( Pcdata text, String fieldName ) throws SAXException, IOException, XMLStreamException {
// If the assertion fails, it must be a bug of xjc.
// right now, we are not expecting the text method to be called.
if(text==null) {
reportMissingObjectError(fieldName);
return;
}
out.text(text,textHasAlreadyPrinted);
textHasAlreadyPrinted = true;
}
public void attribute(String uri, String local, String value) throws SAXException {
int prefix;
if(uri.length()==0) {
// default namespace. don't need prefix
prefix = -1;
} else {
prefix = nsContext.getPrefixIndex(uri);
}
try {
out.attribute(prefix,local,value);
} catch (IOException e) {
throw new SAXException2(e);
} catch (XMLStreamException e) {
throw new SAXException2(e);
}
}
public void attribute(Name name, CharSequence value) throws IOException, XMLStreamException {
// TODO: consider having the version that takes Pcdata.
// it's common for an element to have int attributes
out.attribute(name,value.toString());
}
public NamespaceContext2 getNamespaceContext() {
return nsContext;
}
public String onID( Object owner, String value ) {
objectsWithId.add(owner);
return value;
}
public String onIDREF( Object obj ) throws SAXException {
String id;
try {
id = getIdFromObject(obj);
} catch (JAXBException e) {
reportError(null,e);
return null; // recover by returning null
}
idReferencedObjects.add(obj);
if(id==null) {
reportError( new NotIdentifiableEventImpl(
ValidationEvent.ERROR,
Messages.NOT_IDENTIFIABLE.format(),
new ValidationEventLocatorImpl(obj) ) );
}
return id;
}
// TODO: think about the exception handling.
// I suppose we don't want to use SAXException. -kk
public void childAsRoot(Object obj) throws JAXBException, IOException, SAXException, XMLStreamException {
final JaxBeanInfo beanInfo = grammar.getBeanInfo(obj, true);
// since the same object will be reported to childAsRoot or
// childAsXsiType, don't make it a part of the collision check.
// but we do need to push it so that getXMIMEContentType will work.
cycleDetectionStack.pushNocheck(obj);
final boolean lookForLifecycleMethods = beanInfo.lookForLifecycleMethods();
if (lookForLifecycleMethods) {
fireBeforeMarshalEvents(beanInfo, obj);
}
beanInfo.serializeRoot(obj,this);
if (lookForLifecycleMethods) {
fireAfterMarshalEvents(beanInfo, obj);
}
cycleDetectionStack.pop();
}
Pushes the object to cycleDetectionStack
and also detect any cycles. When a cycle is found, this method tries to recover from it. Returns: the object that should be marshalled instead of the given obj
, or null if the error is found and we need to avoid marshalling this object to prevent infinite recursion. When this method returns null, the error has already been reported.
/**
* Pushes the object to {@link #cycleDetectionStack} and also
* detect any cycles.
*
* When a cycle is found, this method tries to recover from it.
*
* @return
* the object that should be marshalled instead of the given {@code obj},
* or null if the error is found and we need to avoid marshalling this object
* to prevent infinite recursion. When this method returns null, the error
* has already been reported.
*/
private Object pushObject(Object obj, String fieldName) throws SAXException {
if(!cycleDetectionStack.push(obj))
return obj;
// allow the object to nominate its replacement
if(obj instanceof CycleRecoverable) {
obj = ((CycleRecoverable)obj).onCycleDetected(new CycleRecoverable.Context(){
public Marshaller getMarshaller() {
return marshaller;
}
});
if(obj!=null) {
// object nominated its replacement.
// we still need to make sure that the nominated.
// this may cause inifinite recursion on its own.
cycleDetectionStack.pop();
return pushObject(obj,fieldName);
} else
return null;
}
// cycle detected and no one is catching the error.
reportError(new ValidationEventImpl(
ValidationEvent.ERROR,
Messages.CYCLE_IN_MARSHALLER.format(cycleDetectionStack.getCycleString()),
getCurrentLocation(fieldName),
null));
return null;
}
The equivalent of:
childAsURIs(child, fieldName);
endNamespaceDecls();
childAsAttributes(child, fieldName);
endAttributes();
childAsBody(child, fieldName);
This produces the given child object as the sole content of
an element.
Used to reduce the code size in the generated marshaller.
/**
* The equivalent of:
*
* <pre>
* childAsURIs(child, fieldName);
* endNamespaceDecls();
* childAsAttributes(child, fieldName);
* endAttributes();
* childAsBody(child, fieldName);
* </pre>
*
* This produces the given child object as the sole content of
* an element.
* Used to reduce the code size in the generated marshaller.
*/
public final void childAsSoleContent( Object child, String fieldName) throws SAXException, IOException, XMLStreamException {
if(child==null) {
handleMissingObjectError(fieldName);
} else {
child = pushObject(child,fieldName);
if(child==null) {
// error recovery
endNamespaceDecls(null);
endAttributes();
cycleDetectionStack.pop();
}
JaxBeanInfo beanInfo;
try {
beanInfo = grammar.getBeanInfo(child,true);
} catch (JAXBException e) {
reportError(fieldName,e);
// recover by ignore
endNamespaceDecls(null);
endAttributes();
cycleDetectionStack.pop();
return;
}
final boolean lookForLifecycleMethods = beanInfo.lookForLifecycleMethods();
if (lookForLifecycleMethods) {
fireBeforeMarshalEvents(beanInfo, child);
}
beanInfo.serializeURIs(child,this);
endNamespaceDecls(child);
beanInfo.serializeAttributes(child,this);
endAttributes();
beanInfo.serializeBody(child,this);
if (lookForLifecycleMethods) {
fireAfterMarshalEvents(beanInfo, child);
}
cycleDetectionStack.pop();
}
}
// the version of childAsXXX where it produces @xsi:type if the expected type name
// and the actual type name differs.
This method is called when a type child object is found.
This method produces events of the following form:
NSDECL* "endNamespaceDecls" ATTRIBUTE* "endAttributes" BODY
optionally including @xsi:type if necessary.
Params: - child – Object to be marshalled. The
JaxBeanInfo
for this object must return a type name. - expected –
Expected type of the object.
- fieldName –
property name of the parent objeect from which 'o' comes.
Used as a part of the error message in case anything goes wrong
with 'o'.
/**
* This method is called when a type child object is found.
*
* <p>
* This method produces events of the following form:
* <pre>
* NSDECL* "endNamespaceDecls" ATTRIBUTE* "endAttributes" BODY
* </pre>
* optionally including @xsi:type if necessary.
*
* @param child
* Object to be marshalled. The {@link JaxBeanInfo} for
* this object must return a type name.
* @param expected
* Expected type of the object.
* @param fieldName
* property name of the parent objeect from which 'o' comes.
* Used as a part of the error message in case anything goes wrong
* with 'o'.
*/
public final void childAsXsiType( Object child, String fieldName, JaxBeanInfo expected, boolean nillable) throws SAXException, IOException, XMLStreamException {
if(child==null) {
handleMissingObjectError(fieldName);
} else {
child = pushObject(child,fieldName);
if(child==null) { // error recovery
endNamespaceDecls(null);
endAttributes();
return;
}
boolean asExpected = child.getClass()==expected.jaxbType;
JaxBeanInfo actual = expected;
QName actualTypeName = null;
if((asExpected) && (actual.lookForLifecycleMethods())) {
fireBeforeMarshalEvents(actual, child);
}
if(!asExpected) {
try {
actual = grammar.getBeanInfo(child,true);
if (actual.lookForLifecycleMethods()) {
fireBeforeMarshalEvents(actual, child);
}
} catch (JAXBException e) {
reportError(fieldName,e);
endNamespaceDecls(null);
endAttributes();
return; // recover by ignore
}
if(actual==expected)
asExpected = true;
else {
actualTypeName = actual.getTypeName(child);
if(actualTypeName==null) {
reportError(new ValidationEventImpl(
ValidationEvent.ERROR,
Messages.SUBSTITUTED_BY_ANONYMOUS_TYPE.format(
expected.jaxbType.getName(),
child.getClass().getName(),
actual.jaxbType.getName()),
getCurrentLocation(fieldName)));
// recover by not printing @xsi:type
} else {
getNamespaceContext().declareNamespace(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
getNamespaceContext().declareNamespace(actualTypeName.getNamespaceURI(),null,false);
}
}
}
actual.serializeURIs(child,this);
if (nillable) {
getNamespaceContext().declareNamespace(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
}
endNamespaceDecls(child);
if(!asExpected) {
attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE,"type",
DatatypeConverter.printQName(actualTypeName,getNamespaceContext()));
}
actual.serializeAttributes(child,this);
boolean nilDefined = actual.isNilIncluded();
if ((nillable) && (!nilDefined)) {
attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE,"nil","true");
}
endAttributes();
actual.serializeBody(child,this);
if (actual.lookForLifecycleMethods()) {
fireAfterMarshalEvents(actual, child);
}
cycleDetectionStack.pop();
}
}
Invoke the afterMarshal api on the external listener (if it exists) and on the bean embedded
afterMarshal api(if it exists).
This method is called only after the callee has determined that beanInfo.lookForLifecycleMethods == true.
Params: - beanInfo –
- currentTarget –
/**
* Invoke the afterMarshal api on the external listener (if it exists) and on the bean embedded
* afterMarshal api(if it exists).
*
* This method is called only after the callee has determined that beanInfo.lookForLifecycleMethods == true.
*
* @param beanInfo
* @param currentTarget
*/
private void fireAfterMarshalEvents(final JaxBeanInfo beanInfo, Object currentTarget) {
// first invoke bean embedded listener
if (beanInfo.hasAfterMarshalMethod()) {
Method m = beanInfo.getLifecycleMethods().afterMarshal;
fireMarshalEvent(currentTarget, m);
}
// then invoke external listener before bean embedded listener
Marshaller.Listener externalListener = marshaller.getListener();
if (externalListener != null) {
externalListener.afterMarshal(currentTarget);
}
}
Invoke the beforeMarshal api on the external listener (if it exists) and on the bean embedded
beforeMarshal api(if it exists).
This method is called only after the callee has determined that beanInfo.lookForLifecycleMethods == true.
Params: - beanInfo –
- currentTarget –
/**
* Invoke the beforeMarshal api on the external listener (if it exists) and on the bean embedded
* beforeMarshal api(if it exists).
*
* This method is called only after the callee has determined that beanInfo.lookForLifecycleMethods == true.
*
* @param beanInfo
* @param currentTarget
*/
private void fireBeforeMarshalEvents(final JaxBeanInfo beanInfo, Object currentTarget) {
// first invoke bean embedded listener
if (beanInfo.hasBeforeMarshalMethod()) {
Method m = beanInfo.getLifecycleMethods().beforeMarshal;
fireMarshalEvent(currentTarget, m);
}
// then invoke external listener
Marshaller.Listener externalListener = marshaller.getListener();
if (externalListener != null) {
externalListener.beforeMarshal(currentTarget);
}
}
private void fireMarshalEvent(Object target, Method m) {
try {
m.invoke(target, marshaller);
} catch (Exception e) {
// this really only happens if there is a bug in the ri
throw new IllegalStateException(e);
}
}
public void attWildcardAsURIs(Map<QName,String> attributes, String fieldName) {
if(attributes==null) return;
for( Map.Entry<QName,String> e : attributes.entrySet() ) {
QName n = e.getKey();
String nsUri = n.getNamespaceURI();
if(nsUri.length()>0) {
String p = n.getPrefix();
if(p.length()==0) p=null;
nsContext.declareNsUri(nsUri, p, true);
}
}
}
public void attWildcardAsAttributes(Map<QName,String> attributes, String fieldName) throws SAXException {
if(attributes==null) return;
for( Map.Entry<QName,String> e : attributes.entrySet() ) {
QName n = e.getKey();
attribute(n.getNamespaceURI(),n.getLocalPart(),e.getValue());
}
}
Short for the following call sequence:
getNamespaceContext().declareNamespace(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
endNamespaceDecls();
attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE,"nil","true");
endAttributes();
/**
* Short for the following call sequence:
*
* <pre>
getNamespaceContext().declareNamespace(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
endNamespaceDecls();
attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE,"nil","true");
endAttributes();
* </pre>
*/
public final void writeXsiNilTrue() throws SAXException, IOException, XMLStreamException {
getNamespaceContext().declareNamespace(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
endNamespaceDecls(null);
attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE,"nil","true");
endAttributes();
}
public <E> void writeDom(E element, DomHandler<E, ?> domHandler, Object parentBean, String fieldName) throws SAXException {
Source source = domHandler.marshal(element,this);
if(contentHandlerAdapter==null)
contentHandlerAdapter = new ContentHandlerAdaptor(this);
try {
getIdentityTransformer().transform(source,new SAXResult(contentHandlerAdapter));
} catch (TransformerException e) {
reportError(fieldName,e);
}
}
public Transformer getIdentityTransformer() {
if (identityTransformer==null)
identityTransformer = JAXBContextImpl.createTransformer(grammar.disableSecurityProcessing);
return identityTransformer;
}
public void setPrefixMapper(NamespacePrefixMapper prefixMapper) {
nsContext.setPrefixMapper(prefixMapper);
}
Reset this object to write to the specified output.
Params: - schemaLocation –
if non-null, this value is printed on the root element as xsi:schemaLocation
- noNsSchemaLocation –
Similar to 'schemaLocation' but this one works for xsi:noNamespaceSchemaLocation
/**
* Reset this object to write to the specified output.
*
* @param schemaLocation
* if non-null, this value is printed on the root element as xsi:schemaLocation
* @param noNsSchemaLocation
* Similar to 'schemaLocation' but this one works for xsi:noNamespaceSchemaLocation
*/
public void startDocument(XmlOutput out,boolean fragment,String schemaLocation,String noNsSchemaLocation) throws IOException, SAXException, XMLStreamException {
pushCoordinator();
nsContext.reset();
nse = nsContext.getCurrent();
if(attachmentMarshaller!=null && attachmentMarshaller.isXOPPackage())
out = new MTOMXmlOutput(out);
this.out = out;
objectsWithId.clear();
idReferencedObjects.clear();
textHasAlreadyPrinted = false;
seenRoot = false;
this.schemaLocation = schemaLocation;
this.noNsSchemaLocation = noNsSchemaLocation;
this.fragment = fragment;
this.inlineBinaryFlag = false;
this.expectedMimeType = null;
cycleDetectionStack.reset();
out.startDocument(this,fragment,knownUri2prefixIndexMap,nsContext);
}
public void endDocument() throws IOException, SAXException, XMLStreamException {
out.endDocument(fragment);
}
public void close() {
out = null;
clearCurrentProperty();
popCoordinator();
}
This method can be called after startDocument
is called but before the marshalling begins, to set the currently in-scope namespace bindings.
This method is useful to avoid redundant namespace declarations when
the marshalling is producing a sub-document.
/**
* This method can be called after {@link #startDocument} is called
* but before the marshalling begins, to set the currently in-scope namespace
* bindings.
*
* <p>
* This method is useful to avoid redundant namespace declarations when
* the marshalling is producing a sub-document.
*/
public void addInscopeBinding(String nsUri,String prefix) {
nsContext.put(nsUri,prefix);
}
Gets the MIME type with which the binary content shall be printed.
This method shall be used from those RuntimeBuiltinLeafInfo
that are bound to base64Binary.
See Also:
/**
* Gets the MIME type with which the binary content shall be printed.
*
* <p>
* This method shall be used from those {@link RuntimeBuiltinLeafInfo} that are
* bound to base64Binary.
*
* @see JAXBContextImpl#getXMIMEContentType(Object)
*/
public String getXMIMEContentType() {
// xmime:contentType takes precedence
String v = grammar.getXMIMEContentType(cycleDetectionStack.peek());
if(v!=null) return v;
// then look for the current in-scope @XmlMimeType
if(expectedMimeType!=null)
return expectedMimeType.toString();
return null;
}
private void startElement() {
nse = nse.push();
if( !seenRoot ) {
if (grammar.getXmlNsSet() != null) {
for(XmlNs xmlNs : grammar.getXmlNsSet())
nsContext.declareNsUri(
xmlNs.namespaceURI(),
xmlNs.prefix() == null ? "" : xmlNs.prefix(),
xmlNs.prefix() != null);
}
// seenRoot set to true in endAttributes
// first declare all known URIs
String[] knownUris = nameList.namespaceURIs;
for( int i=0; i<knownUris.length; i++ )
knownUri2prefixIndexMap[i] = nsContext.declareNsUri(knownUris[i], null, nameList.nsUriCannotBeDefaulted[i]);
// then declare user-specified namespace URIs.
// work defensively. we are calling an user-defined method.
String[] uris = nsContext.getPrefixMapper().getPreDeclaredNamespaceUris();
if( uris!=null ) {
for (String uri : uris) {
if (uri != null)
nsContext.declareNsUri(uri, null, false);
}
}
String[] pairs = nsContext.getPrefixMapper().getPreDeclaredNamespaceUris2();
if( pairs!=null ) {
for( int i=0; i<pairs.length; i+=2 ) {
String prefix = pairs[i];
String nsUri = pairs[i+1];
if(prefix!=null && nsUri!=null)
// in this case, we don't want the redundant binding consolidation
// to happen (such as declaring the same namespace URI twice with
// different prefixes.) Hence we call the put method directly.
nsContext.put(nsUri,prefix);
}
}
if(schemaLocation!=null || noNsSchemaLocation!=null) {
nsContext.declareNsUri(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
}
}
nsContext.collectionMode = true;
textHasAlreadyPrinted = false;
}
private MimeType expectedMimeType;
This method is used by MimeTypedTransducer
to set the expected MIME type for the encapsulated Transducer
. /**
* This method is used by {@link MimeTypedTransducer} to set the expected MIME type
* for the encapsulated {@link Transducer}.
*/
public MimeType setExpectedMimeType(MimeType expectedMimeType) {
MimeType old = this.expectedMimeType;
this.expectedMimeType = expectedMimeType;
return old;
}
True to force inlining.
/**
* True to force inlining.
*/
private boolean inlineBinaryFlag;
public boolean setInlineBinaryFlag(boolean value) {
boolean old = inlineBinaryFlag;
this.inlineBinaryFlag = value;
return old;
}
public boolean getInlineBinaryFlag() {
return inlineBinaryFlag;
}
Field used to support an XmlSchemaType
annotation. When we are marshalling a property with an effective XmlSchemaType
, this field is set to hold the QName of that type. The Transducer
that actually converts a Java object into XML can look this property to decide how to marshal the value.
/**
* Field used to support an {@link XmlSchemaType} annotation.
*
* <p>
* When we are marshalling a property with an effective {@link XmlSchemaType},
* this field is set to hold the QName of that type. The {@link Transducer} that
* actually converts a Java object into XML can look this property to decide
* how to marshal the value.
*/
private QName schemaType;
public QName setSchemaType(QName st) {
QName old = schemaType;
schemaType = st;
return old;
}
public QName getSchemaType() {
return schemaType;
}
public void setObjectIdentityCycleDetection(boolean val) {
cycleDetectionStack.setUseIdentity(val);
}
public boolean getObjectIdentityCycleDetection() {
return cycleDetectionStack.getUseIdentity();
}
void reconcileID() throws SAXException {
// find objects that were not a part of the object graph
idReferencedObjects.removeAll(objectsWithId);
for( Object idObj : idReferencedObjects ) {
try {
String id = getIdFromObject(idObj);
reportError( new NotIdentifiableEventImpl(
ValidationEvent.ERROR,
Messages.DANGLING_IDREF.format(id),
new ValidationEventLocatorImpl(idObj) ) );
} catch (JAXBException e) {
// this error should have been reported already. just ignore here.
}
}
// clear the garbage
idReferencedObjects.clear();
objectsWithId.clear();
}
public boolean handleError(Exception e) {
return handleError(e,cycleDetectionStack.peek(),null);
}
public boolean handleError(Exception e,Object source,String fieldName) {
return handleEvent(
new ValidationEventImpl(
ValidationEvent.ERROR,
e.getMessage(),
new ValidationEventLocatorExImpl(source,fieldName),
e));
}
public boolean handleEvent(ValidationEvent event) {
try {
return marshaller.getEventHandler().handleEvent(event);
} catch (JAXBException e) {
// impossible
throw new Error(e);
}
}
private void reportMissingObjectError(String fieldName) throws SAXException {
reportError(new ValidationEventImpl(
ValidationEvent.ERROR,
Messages.MISSING_OBJECT.format(fieldName),
getCurrentLocation(fieldName),
new NullPointerException() ));
}
Called when a referenced object doesn't have an ID.
/**
* Called when a referenced object doesn't have an ID.
*/
public void errorMissingId(Object obj) throws SAXException {
reportError( new ValidationEventImpl(
ValidationEvent.ERROR,
Messages.MISSING_ID.format(obj),
new ValidationEventLocatorImpl(obj)) );
}
public ValidationEventLocator getCurrentLocation(String fieldName) {
return new ValidationEventLocatorExImpl(cycleDetectionStack.peek(),fieldName);
}
protected ValidationEventLocator getLocation() {
return getCurrentLocation(null);
}
May return null when the property hasn't been set.
Introduced based on Jersey requirements.
/**
* May return null when the property hasn't been set.
* Introduced based on Jersey requirements.
*/
public Property getCurrentProperty() {
return currentProperty.get();
}
Takes care of cleaning the currentProperty. Must be called from the same thread that created the XMLSerializer.
/**
* Takes care of cleaning the currentProperty. Must be called from the same thread that created the XMLSerializer.
*/
public void clearCurrentProperty() {
if (currentProperty != null) {
currentProperty.remove();
}
}
When called from within the realm of the marshaller, this method returns the current XMLSerializer
in charge. /**
* When called from within the realm of the marshaller, this method
* returns the current {@link XMLSerializer} in charge.
*/
public static XMLSerializer getInstance() {
return (XMLSerializer)Coordinator._getInstance();
}
}