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