blob: cd22dba35173d6933311f0c70826f35db3556b80 [file] [log] [blame]
/*
* Copyright (c) 1997, 2022 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.glassfish.jaxb.runtime.v2.runtime;
import com.sun.istack.SAXException2;
import org.glassfish.jaxb.runtime.CycleRecoverable;
import org.glassfish.jaxb.runtime.marshaller.NamespacePrefixMapper;
import org.glassfish.jaxb.runtime.api.AccessorException;
import org.glassfish.jaxb.runtime.util.ValidationEventLocatorExImpl;
import org.glassfish.jaxb.runtime.v2.model.runtime.RuntimeBuiltinLeafInfo;
import org.glassfish.jaxb.runtime.v2.runtime.output.MTOMXmlOutput;
import org.glassfish.jaxb.runtime.v2.runtime.output.NamespaceContextImpl;
import org.glassfish.jaxb.runtime.v2.runtime.output.Pcdata;
import org.glassfish.jaxb.runtime.v2.runtime.output.XmlOutput;
import org.glassfish.jaxb.runtime.v2.runtime.property.Property;
import org.glassfish.jaxb.runtime.v2.runtime.unmarshaller.Base64Data;
import org.glassfish.jaxb.runtime.v2.runtime.unmarshaller.IntData;
import org.glassfish.jaxb.runtime.v2.util.CollisionCheckStack;
import jakarta.activation.MimeType;
import jakarta.xml.bind.*;
import jakarta.xml.bind.annotation.DomHandler;
import jakarta.xml.bind.annotation.XmlNs;
import jakarta.xml.bind.attachment.AttachmentMarshaller;
import jakarta.xml.bind.helpers.NotIdentifiableEventImpl;
import jakarta.xml.bind.helpers.ValidationEventImpl;
import jakarta.xml.bind.helpers.ValidationEventLocatorImpl;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
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 java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* 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. */
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<>();
/**
* 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.
*/
private boolean seenRoot = false;
/** Marshaller object to which this object belongs. */
private final MarshallerImpl marshaller;
/** Objects referenced through IDREF. */
private final Set<Object> idReferencedObjects = new HashSet<>();
/** Objects with ID. */
private final Set<Object> objectsWithId = new HashSet<>();
/**
* Used to detect cycles in the object.
* Also used to learn what's being marshalled.
*/
private final CollisionCheckStack<Object> cycleDetectionStack = new CollisionCheckStack<>();
/** Optional attributes to go with root element. */
private String schemaLocation;
private String noNsSchemaLocation;
/** Lazily created identitiy transformer. */
private Transformer identityTransformer;
/** Lazily created. */
private ContentHandlerAdaptor contentHandlerAdapter;
private boolean fragment;
/**
* Cached instance of {@link Base64Data}.
*/
private Base64Data base64Data;
/**
* 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 {@link Base64Data}.
*
* @deprecated
* {@link Base64Data} is no longer cached, so that
* XMLStreamWriterEx impl can retain the data, like JAX-WS does.
*/
@Deprecated
public Base64Data getCachedBase64DataInstance() {
return new Base64Data();
}
/**
* 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.
*
* @param fieldName
* the name of the property being processed when an error is found.
*/
public 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 {@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.
*/
public void endAttributes() throws SAXException, IOException, XMLStreamException {
if(!seenRoot) {
seenRoot = true;
if(schemaLocation!=null || noNsSchemaLocation!=null) {
int p = nsContext.getPrefixIndex(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
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.
*/
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(org.glassfish.jaxb.runtime.v2.runtime.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(org.glassfish.jaxb.runtime.v2.runtime.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.
*
* <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 {@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,
org.glassfish.jaxb.runtime.v2.runtime.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 {@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(){
@Override
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,
org.glassfish.jaxb.runtime.v2.runtime.Messages.CYCLE_IN_MARSHALLER.format(cycleDetectionStack.getCycleString()),
getCurrentLocation(fieldName),
null));
return null;
}
/**
* 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 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.
*
* <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 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,
org.glassfish.jaxb.runtime.v2.runtime.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(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,"xsi",true);
getNamespaceContext().declareNamespace(actualTypeName.getNamespaceURI(),null,false);
}
}
}
actual.serializeURIs(child,this);
if (nillable) {
getNamespaceContext().declareNamespace(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,"xsi",true);
}
endNamespaceDecls(child);
if(!asExpected) {
attribute(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,"type",
DatatypeConverter.printQName(actualTypeName,getNamespaceContext()));
}
actual.serializeAttributes(child,this);
boolean nilDefined = actual.isNilIncluded();
if ((nillable) && (!nilDefined)) {
attribute(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,"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.
*
*/
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.
*
*/
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:
*
* <pre>
getNamespaceContext().declareNamespace(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,"xsi",true);
endNamespaceDecls();
attribute(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,"nil","true");
endAttributes();
* </pre>
*/
public void writeXsiNilTrue() throws SAXException, IOException, XMLStreamException {
getNamespaceContext().declareNamespace(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,"xsi",true);
endNamespaceDecls(null);
attribute(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,"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.
*
* @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 {@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.
*
* <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(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,"xsi",true);
}
}
nsContext.collectionMode = true;
textHasAlreadyPrinted = false;
}
private MimeType expectedMimeType;
/**
* 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.
*/
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 {@link jakarta.xml.bind.annotation.XmlSchemaType} annotation.
*
* <p>
* When we are marshalling a property with an effective {@link jakarta.xml.bind.annotation.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,
org.glassfish.jaxb.runtime.v2.runtime.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));
}
@Override
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,
org.glassfish.jaxb.runtime.v2.runtime.Messages.MISSING_OBJECT.format(fieldName),
getCurrentLocation(fieldName),
new NullPointerException() ));
}
/**
* Called when a referenced object doesn't have an ID.
*/
public void errorMissingId(Object obj) throws SAXException {
reportError( new ValidationEventImpl(
ValidationEvent.ERROR,
org.glassfish.jaxb.runtime.v2.runtime.Messages.MISSING_ID.format(obj),
new ValidationEventLocatorImpl(obj)) );
}
public ValidationEventLocator getCurrentLocation(String fieldName) {
return new ValidationEventLocatorExImpl(cycleDetectionStack.peek(),fieldName);
}
@Override
protected ValidationEventLocator getLocation() {
return getCurrentLocation(null);
}
/**
* 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.
*/
public void clearCurrentProperty() {
if (currentProperty != null) {
currentProperty.remove();
}
}
/**
* When called from within the realm of the marshaller, this method
* returns the current in charge.
*/
public static XMLSerializer getInstance() {
return (XMLSerializer) _getInstance();
}
}