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: }