0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common Development
0008: * and Distribution License("CDDL") (collectively, the "License"). You
0009: * may not use this file except in compliance with the License. You can obtain
0010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
0011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
0012: * language governing permissions and limitations under the License.
0013: *
0014: * When distributing the software, include this License Header Notice in each
0015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
0016: * Sun designates this particular file as subject to the "Classpath" exception
0017: * as provided by Sun in the GPL Version 2 section of the License file that
0018: * accompanied this code. If applicable, add the following below the License
0019: * Header, with the fields enclosed by brackets [] replaced by your own
0020: * identifying information: "Portions Copyrighted [year]
0021: * [name of copyright owner]"
0022: *
0023: * Contributor(s):
0024: *
0025: * If you wish your version of this file to be governed by only the CDDL or
0026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
0027: * elects to include this software in this distribution under the [CDDL or GPL
0028: * Version 2] license." If you don't indicate a single choice of license, a
0029: * recipient has the option to distribute your version of this file under
0030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
0031: * its licensees as provided above. However, if you add GPL Version 2 code
0032: * and therefore, elected the GPL Version 2 license, then the option applies
0033: * only if the new code is made subject to such option by the copyright
0034: * holder.
0035: */
0036:
0037: package com.sun.xml.bind.v2.runtime;
0038:
0039: import java.io.IOException;
0040: import java.lang.reflect.Method;
0041: import java.util.HashSet;
0042: import java.util.Map;
0043: import java.util.Set;
0044:
0045: import javax.activation.MimeType;
0046: import javax.xml.bind.DatatypeConverter;
0047: import javax.xml.bind.JAXBException;
0048: import javax.xml.bind.Marshaller;
0049: import javax.xml.bind.ValidationEvent;
0050: import javax.xml.bind.ValidationEventHandler;
0051: import javax.xml.bind.ValidationEventLocator;
0052: import javax.xml.bind.annotation.DomHandler;
0053: import javax.xml.bind.annotation.XmlSchemaType;
0054: import javax.xml.bind.attachment.AttachmentMarshaller;
0055: import javax.xml.bind.helpers.NotIdentifiableEventImpl;
0056: import javax.xml.bind.helpers.ValidationEventImpl;
0057: import javax.xml.bind.helpers.ValidationEventLocatorImpl;
0058: import javax.xml.namespace.QName;
0059: import javax.xml.stream.XMLStreamException;
0060: import javax.xml.transform.Source;
0061: import javax.xml.transform.Transformer;
0062: import javax.xml.transform.TransformerException;
0063: import javax.xml.transform.sax.SAXResult;
0064:
0065: import com.sun.istack.SAXException2;
0066: import com.sun.xml.bind.CycleRecoverable;
0067: import com.sun.xml.bind.api.AccessorException;
0068: import com.sun.xml.bind.marshaller.NamespacePrefixMapper;
0069: import com.sun.xml.bind.util.ValidationEventLocatorExImpl;
0070: import com.sun.xml.bind.v2.WellKnownNamespace;
0071: import com.sun.xml.bind.v2.model.runtime.RuntimeBuiltinLeafInfo;
0072: import com.sun.xml.bind.v2.runtime.output.MTOMXmlOutput;
0073: import com.sun.xml.bind.v2.runtime.output.NamespaceContextImpl;
0074: import com.sun.xml.bind.v2.runtime.output.Pcdata;
0075: import com.sun.xml.bind.v2.runtime.output.XmlOutput;
0076: import com.sun.xml.bind.v2.runtime.unmarshaller.Base64Data;
0077: import com.sun.xml.bind.v2.runtime.unmarshaller.IntData;
0078: import com.sun.xml.bind.v2.util.CollisionCheckStack;
0079:
0080: import org.xml.sax.SAXException;
0081:
0082: /**
0083: * Receives XML serialization event and writes to {@link XmlOutput}.
0084: *
0085: * <p>
0086: * This object coordinates the overall marshalling efforts across different
0087: * content-tree objects and different target formats.
0088: *
0089: * <p>
0090: * The following CFG gives the proper sequence of method invocation.
0091: *
0092: * <pre>
0093: * MARSHALLING := ELEMENT
0094: * ELEMENT := "startElement" NSDECL* "endNamespaceDecls"
0095: * ATTRIBUTE* "endAttributes" BODY "endElement"
0096: *
0097: * NSDECL := "declareNamespace"
0098: *
0099: * ATTRIBUTE := "attribute"
0100: * ATTVALUES := "text"*
0101: *
0102: *
0103: * BODY := ( "text" | ELEMENT )*
0104: * </pre>
0105: *
0106: * <p>
0107: * A marshalling of one element consists of two stages. The first stage is
0108: * for marshalling attributes and collecting namespace declarations.
0109: * The second stage is for marshalling characters/child elements of that element.
0110: *
0111: * <p>
0112: * Observe that multiple invocation of "text" is allowed.
0113: *
0114: * <p>
0115: * Also observe that the namespace declarations are allowed only between
0116: * "startElement" and "endAttributes".
0117: *
0118: * <h2>Exceptions in marshaller</h2>
0119: * <p>
0120: * {@link IOException}, {@link SAXException}, and {@link XMLStreamException}
0121: * are thrown from {@link XmlOutput}. They are always considered fatal, and
0122: * therefore caught only by {@link MarshallerImpl}.
0123: * <p>
0124: * {@link AccessorException} can be thrown when an access to a property/field
0125: * fails, and this is considered as a recoverable error, so it's caught everywhere.
0126: *
0127: * @author Kohsuke Kawaguchi
0128: */
0129: public final class XMLSerializer extends Coordinator {
0130: public final JAXBContextImpl grammar;
0131:
0132: /** The XML printer. */
0133: private XmlOutput out;
0134:
0135: // TODO: fix the access modifier
0136: public final NameList nameList;
0137:
0138: // TODO: fix the access modifier
0139: public final int[] knownUri2prefixIndexMap;
0140:
0141: private final NamespaceContextImpl nsContext;
0142:
0143: private NamespaceContextImpl.Element nse;
0144:
0145: /**
0146: * Set to true if a text is already written,
0147: * and we need to print ' ' for additional text methods.
0148: */
0149: private boolean textHasAlreadyPrinted = false;
0150:
0151: /**
0152: * Set to false once we see the start tag of the root element.
0153: */
0154: private boolean seenRoot = false;
0155:
0156: /** Marshaller object to which this object belongs. */
0157: private final MarshallerImpl marshaller;
0158:
0159: /** Objects referenced through IDREF. */
0160: private final Set<Object> idReferencedObjects = new HashSet<Object>();
0161:
0162: /** Objects with ID. */
0163: private final Set<Object> objectsWithId = new HashSet<Object>();
0164:
0165: /**
0166: * Used to detect cycles in the object.
0167: * Also used to learn what's being marshalled.
0168: */
0169: private final CollisionCheckStack<Object> cycleDetectionStack = new CollisionCheckStack<Object>();
0170:
0171: /** Optional attributes to go with root element. */
0172: private String schemaLocation;
0173: private String noNsSchemaLocation;
0174:
0175: /** Lazily created identitiy transformer. */
0176: private Transformer identityTransformer;
0177:
0178: /** Lazily created. */
0179: private ContentHandlerAdaptor contentHandlerAdapter;
0180:
0181: private boolean fragment;
0182:
0183: /**
0184: * Cached instance of {@link Base64Data}.
0185: */
0186: private Base64Data base64Data;
0187:
0188: /**
0189: * Cached instance of {@link IntData}.
0190: */
0191: private final IntData intData = new IntData();
0192:
0193: public AttachmentMarshaller attachmentMarshaller;
0194:
0195: /*package*/XMLSerializer(MarshallerImpl _owner) {
0196: this .marshaller = _owner;
0197: this .grammar = marshaller.context;
0198: nsContext = new NamespaceContextImpl(this );
0199: nameList = marshaller.context.nameList;
0200: knownUri2prefixIndexMap = new int[nameList.namespaceURIs.length];
0201: }
0202:
0203: /**
0204: * Gets the cached instance of {@link Base64Data}.
0205: *
0206: * @deprecated
0207: * {@link Base64Data} is no longer cached, so that
0208: * XMLStreamWriterEx impl can retain the data, like JAX-WS does.
0209: */
0210: public Base64Data getCachedBase64DataInstance() {
0211: return new Base64Data();
0212: }
0213:
0214: /**
0215: * Gets the ID value from an identifiable object.
0216: */
0217: private String getIdFromObject(Object identifiableObject)
0218: throws SAXException, JAXBException {
0219: return grammar.getBeanInfo(identifiableObject, true).getId(
0220: identifiableObject, this );
0221: }
0222:
0223: private void handleMissingObjectError(String fieldName)
0224: throws SAXException, IOException, XMLStreamException {
0225: reportMissingObjectError(fieldName);
0226: // as a marshaller, we should be robust, so we'll continue to marshal
0227: // this document by skipping this missing object.
0228: endNamespaceDecls(null);
0229: endAttributes();
0230: }
0231:
0232: public void reportError(ValidationEvent ve) throws SAXException {
0233: ValidationEventHandler handler;
0234:
0235: try {
0236: handler = marshaller.getEventHandler();
0237: } catch (JAXBException e) {
0238: throw new SAXException2(e);
0239: }
0240:
0241: if (!handler.handleEvent(ve)) {
0242: if (ve.getLinkedException() instanceof Exception)
0243: throw new SAXException2((Exception) ve
0244: .getLinkedException());
0245: else
0246: throw new SAXException2(ve.getMessage());
0247: }
0248: }
0249:
0250: /**
0251: * Report an error found as an exception.
0252: *
0253: * @param fieldName
0254: * the name of the property being processed when an error is found.
0255: */
0256: public final void reportError(String fieldName, Throwable t)
0257: throws SAXException {
0258: ValidationEvent ve = new ValidationEventImpl(
0259: ValidationEvent.ERROR, t.getMessage(),
0260: getCurrentLocation(fieldName), t);
0261: reportError(ve);
0262: }
0263:
0264: public void startElement(Name tagName, Object outerPeer) {
0265: startElement();
0266: nse.setTagName(tagName, outerPeer);
0267: }
0268:
0269: public void startElement(String nsUri, String localName,
0270: String preferredPrefix, Object outerPeer) {
0271: startElement();
0272: int idx = nsContext.declareNsUri(nsUri, preferredPrefix, false);
0273: nse.setTagName(idx, localName, outerPeer);
0274: }
0275:
0276: /**
0277: * Variation of {@link #startElement(String, String, String, Object)} that forces
0278: * a specific prefix. Needed to preserve the prefix when marshalling DOM.
0279: */
0280: public void startElementForce(String nsUri, String localName,
0281: String forcedPrefix, Object outerPeer) {
0282: startElement();
0283: int idx = nsContext.force(nsUri, forcedPrefix);
0284: nse.setTagName(idx, localName, outerPeer);
0285: }
0286:
0287: public void endNamespaceDecls(Object innerPeer) throws IOException,
0288: XMLStreamException {
0289: nsContext.collectionMode = false;
0290: nse.startElement(out, innerPeer);
0291: }
0292:
0293: /**
0294: * Switches to the "marshal child texts/elements" mode.
0295: * This method has to be called after the 1st pass is completed.
0296: */
0297: public void endAttributes() throws SAXException, IOException,
0298: XMLStreamException {
0299: if (!seenRoot) {
0300: seenRoot = true;
0301: if (schemaLocation != null || noNsSchemaLocation != null) {
0302: int p = nsContext
0303: .getPrefixIndex(WellKnownNamespace.XML_SCHEMA_INSTANCE);
0304: if (schemaLocation != null)
0305: out.attribute(p, "schemaLocation", schemaLocation);
0306: if (noNsSchemaLocation != null)
0307: out.attribute(p, "noNamespaceSchemaLocation",
0308: noNsSchemaLocation);
0309: }
0310: }
0311:
0312: out.endStartTag();
0313: }
0314:
0315: /**
0316: * Ends marshalling of an element.
0317: * Pops the internal stack.
0318: */
0319: public void endElement() throws SAXException, IOException,
0320: XMLStreamException {
0321: nse.endElement(out);
0322: nse = nse.pop();
0323: textHasAlreadyPrinted = false;
0324: }
0325:
0326: public void leafElement(Name tagName, String data, String fieldName)
0327: throws SAXException, IOException, XMLStreamException {
0328: if (seenRoot) {
0329: textHasAlreadyPrinted = false;
0330: nse = nse.push();
0331: out.beginStartTag(tagName);
0332: out.endStartTag();
0333: out.text(data, false);
0334: out.endTag(tagName);
0335: nse = nse.pop();
0336: } else {
0337: // root element has additional processing like xsi:schemaLocation,
0338: // so we need to go the slow way
0339: startElement(tagName, null);
0340: endNamespaceDecls(null);
0341: endAttributes();
0342: out.text(data, false);
0343: endElement();
0344: }
0345: }
0346:
0347: public void leafElement(Name tagName, Pcdata data, String fieldName)
0348: throws SAXException, IOException, XMLStreamException {
0349: if (seenRoot) {
0350: textHasAlreadyPrinted = false;
0351: nse = nse.push();
0352: out.beginStartTag(tagName);
0353: out.endStartTag();
0354: out.text(data, false);
0355: out.endTag(tagName);
0356: nse = nse.pop();
0357: } else {
0358: // root element has additional processing like xsi:schemaLocation,
0359: // so we need to go the slow way
0360: startElement(tagName, null);
0361: endNamespaceDecls(null);
0362: endAttributes();
0363: out.text(data, false);
0364: endElement();
0365: }
0366: }
0367:
0368: public void leafElement(Name tagName, int data, String fieldName)
0369: throws SAXException, IOException, XMLStreamException {
0370: intData.reset(data);
0371: leafElement(tagName, intData, fieldName);
0372: }
0373:
0374: // TODO: consider some of these in future if we expand the writer to use something other than SAX
0375: // void leafElement( QName tagName, byte value, String fieldName ) throws SAXException;
0376: // void leafElement( QName tagName, char value, String fieldName ) throws SAXException;
0377: // void leafElement( QName tagName, short value, String fieldName ) throws SAXException;
0378: // void leafElement( QName tagName, int value, String fieldName ) throws SAXException;
0379: // void leafElement( QName tagName, long value, String fieldName ) throws SAXException;
0380: // void leafElement( QName tagName, float value, String fieldName ) throws SAXException;
0381: // void leafElement( QName tagName, double value, String fieldName ) throws SAXException;
0382: // void leafElement( QName tagName, boolean value, String fieldName ) throws SAXException;
0383:
0384: /**
0385: * Marshalls text.
0386: *
0387: * <p>
0388: * This method can be called after the {@link #endAttributes()}
0389: * method to marshal texts inside elements.
0390: * If the method is called more than once, those texts are considered
0391: * as separated by whitespaces. For example,
0392: *
0393: * <pre>
0394: * c.startElement("","foo");
0395: * c.endAttributes();
0396: * c.text("abc");
0397: * c.text("def");
0398: * c.startElement("","bar");
0399: * c.endAttributes();
0400: * c.endElement();
0401: * c.text("ghi");
0402: * c.endElement();
0403: * </pre>
0404: *
0405: * will generate <code><foo>abc def<bar/>ghi</foo></code>.
0406: */
0407: public void text(String text, String fieldName)
0408: throws SAXException, IOException, XMLStreamException {
0409: // If the assertion fails, it must be a bug of xjc.
0410: // right now, we are not expecting the text method to be called.
0411: if (text == null) {
0412: reportMissingObjectError(fieldName);
0413: return;
0414: }
0415:
0416: out.text(text, textHasAlreadyPrinted);
0417: textHasAlreadyPrinted = true;
0418: }
0419:
0420: /**
0421: * The {@link #text(String, String)} method that takes {@link Pcdata}.
0422: */
0423: public void text(Pcdata text, String fieldName)
0424: throws SAXException, IOException, XMLStreamException {
0425: // If the assertion fails, it must be a bug of xjc.
0426: // right now, we are not expecting the text method to be called.
0427: if (text == null) {
0428: reportMissingObjectError(fieldName);
0429: return;
0430: }
0431:
0432: out.text(text, textHasAlreadyPrinted);
0433: textHasAlreadyPrinted = true;
0434: }
0435:
0436: public void attribute(String uri, String local, String value)
0437: throws SAXException {
0438: int prefix;
0439: if (uri.length() == 0) {
0440: // default namespace. don't need prefix
0441: prefix = -1;
0442: } else {
0443: prefix = nsContext.getPrefixIndex(uri);
0444: }
0445:
0446: try {
0447: out.attribute(prefix, local, value);
0448: } catch (IOException e) {
0449: throw new SAXException2(e);
0450: } catch (XMLStreamException e) {
0451: throw new SAXException2(e);
0452: }
0453: }
0454:
0455: public void attribute(Name name, CharSequence value)
0456: throws IOException, XMLStreamException {
0457: // TODO: consider having the version that takes Pcdata.
0458: // it's common for an element to have int attributes
0459: out.attribute(name, value.toString());
0460: }
0461:
0462: public NamespaceContext2 getNamespaceContext() {
0463: return nsContext;
0464: }
0465:
0466: public String onID(Object owner, String value) {
0467: objectsWithId.add(owner);
0468: return value;
0469: }
0470:
0471: public String onIDREF(Object obj) throws SAXException {
0472: String id;
0473: try {
0474: id = getIdFromObject(obj);
0475: } catch (JAXBException e) {
0476: reportError(null, e);
0477: return null; // recover by returning null
0478: }
0479: idReferencedObjects.add(obj);
0480: if (id == null) {
0481: reportError(new NotIdentifiableEventImpl(
0482: ValidationEvent.ERROR, Messages.NOT_IDENTIFIABLE
0483: .format(), new ValidationEventLocatorImpl(
0484: obj)));
0485: }
0486: return id;
0487: }
0488:
0489: // TODO: think about the exception handling.
0490: // I suppose we don't want to use SAXException. -kk
0491:
0492: public void childAsRoot(Object obj) throws JAXBException,
0493: IOException, SAXException, XMLStreamException {
0494: final JaxBeanInfo beanInfo = grammar.getBeanInfo(obj, true);
0495:
0496: // since the same object will be reported to childAsRoot or
0497: // childAsXsiType, don't make it a part of the collision check.
0498: // but we do need to push it so that getXMIMEContentType will work.
0499: cycleDetectionStack.pushNocheck(obj);
0500:
0501: final boolean lookForLifecycleMethods = beanInfo
0502: .lookForLifecycleMethods();
0503: if (lookForLifecycleMethods) {
0504: fireBeforeMarshalEvents(beanInfo, obj);
0505: }
0506:
0507: beanInfo.serializeRoot(obj, this );
0508:
0509: if (lookForLifecycleMethods) {
0510: fireAfterMarshalEvents(beanInfo, obj);
0511: }
0512:
0513: cycleDetectionStack.pop();
0514: }
0515:
0516: /**
0517: * Pushes the object to {@link #cycleDetectionStack} and also
0518: * detect any cycles.
0519: *
0520: * When a cycle is found, this method tries to recover from it.
0521: *
0522: * @return
0523: * the object that should be marshalled instead of the given <tt>obj</tt>,
0524: * or null if the error is found and we need to avoid marshalling this object
0525: * to prevent infinite recursion. When this method returns null, the error
0526: * has already been reported.
0527: */
0528: private Object pushObject(Object obj, String fieldName)
0529: throws SAXException {
0530: if (!cycleDetectionStack.push(obj))
0531: return obj;
0532:
0533: // allow the object to nominate its replacement
0534: if (obj instanceof CycleRecoverable) {
0535: obj = ((CycleRecoverable) obj)
0536: .onCycleDetected(new CycleRecoverable.Context() {
0537: public Marshaller getMarshaller() {
0538: return marshaller;
0539: }
0540: });
0541: if (obj != null) {
0542: // object nominated its replacement.
0543: // we still need to make sure that the nominated.
0544: // this may cause inifinite recursion on its own.
0545: cycleDetectionStack.pop();
0546: return pushObject(obj, fieldName);
0547: } else
0548: return null;
0549: }
0550:
0551: // cycle detected and no one is catching the error.
0552: reportError(new ValidationEventImpl(ValidationEvent.ERROR,
0553: Messages.CYCLE_IN_MARSHALLER.format(cycleDetectionStack
0554: .getCycleString()),
0555: getCurrentLocation(fieldName), null));
0556: return null;
0557: }
0558:
0559: /**
0560: * The equivalent of:
0561: *
0562: * <pre>
0563: * childAsURIs(child, fieldName);
0564: * endNamespaceDecls();
0565: * childAsAttributes(child, fieldName);
0566: * endAttributes();
0567: * childAsBody(child, fieldName);
0568: * </pre>
0569: *
0570: * This produces the given child object as the sole content of
0571: * an element.
0572: * Used to reduce the code size in the generated marshaller.
0573: */
0574: public final void childAsSoleContent(Object child, String fieldName)
0575: throws SAXException, IOException, XMLStreamException {
0576: if (child == null) {
0577: handleMissingObjectError(fieldName);
0578: } else {
0579: child = pushObject(child, fieldName);
0580: if (child == null) {
0581: // error recovery
0582: endNamespaceDecls(null);
0583: endAttributes();
0584: cycleDetectionStack.pop();
0585: }
0586:
0587: JaxBeanInfo beanInfo;
0588: try {
0589: beanInfo = grammar.getBeanInfo(child, true);
0590: } catch (JAXBException e) {
0591: reportError(fieldName, e);
0592: // recover by ignore
0593: endNamespaceDecls(null);
0594: endAttributes();
0595: cycleDetectionStack.pop();
0596: return;
0597: }
0598:
0599: final boolean lookForLifecycleMethods = beanInfo
0600: .lookForLifecycleMethods();
0601: if (lookForLifecycleMethods) {
0602: fireBeforeMarshalEvents(beanInfo, child);
0603: }
0604:
0605: beanInfo.serializeURIs(child, this );
0606: endNamespaceDecls(child);
0607: beanInfo.serializeAttributes(child, this );
0608: endAttributes();
0609: beanInfo.serializeBody(child, this );
0610:
0611: if (lookForLifecycleMethods) {
0612: fireAfterMarshalEvents(beanInfo, child);
0613: }
0614:
0615: cycleDetectionStack.pop();
0616: }
0617: }
0618:
0619: // the version of childAsXXX where it produces @xsi:type if the expected type name
0620: // and the actual type name differs.
0621:
0622: /**
0623: * This method is called when a type child object is found.
0624: *
0625: * <p>
0626: * This method produces events of the following form:
0627: * <pre>
0628: * NSDECL* "endNamespaceDecls" ATTRIBUTE* "endAttributes" BODY
0629: * </pre>
0630: * optionally including @xsi:type if necessary.
0631: *
0632: * @param child
0633: * Object to be marshalled. The {@link JaxBeanInfo} for
0634: * this object must return a type name.
0635: * @param expected
0636: * Expected type of the object.
0637: * @param fieldName
0638: * property name of the parent objeect from which 'o' comes.
0639: * Used as a part of the error message in case anything goes wrong
0640: * with 'o'.
0641: */
0642: public final void childAsXsiType(Object child, String fieldName,
0643: JaxBeanInfo expected) throws SAXException, IOException,
0644: XMLStreamException {
0645: if (child == null) {
0646: handleMissingObjectError(fieldName);
0647: } else {
0648: child = pushObject(child, fieldName);
0649: if (child == null) { // error recovery
0650: endNamespaceDecls(null);
0651: endAttributes();
0652: return;
0653: }
0654:
0655: boolean asExpected = child.getClass() == expected.jaxbType;
0656: JaxBeanInfo actual = expected;
0657: QName actualTypeName = null;
0658:
0659: if ((asExpected) && (actual.lookForLifecycleMethods())) {
0660: fireBeforeMarshalEvents(actual, child);
0661: }
0662:
0663: if (!asExpected) {
0664: try {
0665: actual = grammar.getBeanInfo(child, true);
0666: if (actual.lookForLifecycleMethods()) {
0667: fireBeforeMarshalEvents(actual, child);
0668: }
0669: } catch (JAXBException e) {
0670: reportError(fieldName, e);
0671: endNamespaceDecls(null);
0672: endAttributes();
0673: return; // recover by ignore
0674: }
0675: if (actual == expected)
0676: asExpected = true;
0677: else {
0678: actualTypeName = actual.getTypeName(child);
0679: if (actualTypeName == null) {
0680: reportError(new ValidationEventImpl(
0681: ValidationEvent.ERROR,
0682: Messages.SUBSTITUTED_BY_ANONYMOUS_TYPE
0683: .format(expected.jaxbType
0684: .getName(), child
0685: .getClass().getName(),
0686: actual.jaxbType
0687: .getName()),
0688: getCurrentLocation(fieldName)));
0689: // recover by not printing @xsi:type
0690: } else {
0691: getNamespaceContext().declareNamespace(
0692: WellKnownNamespace.XML_SCHEMA_INSTANCE,
0693: "xsi", true);
0694: getNamespaceContext().declareNamespace(
0695: actualTypeName.getNamespaceURI(), null,
0696: false);
0697: }
0698: }
0699: }
0700: actual.serializeURIs(child, this );
0701: endNamespaceDecls(child);
0702: if (!asExpected) {
0703: attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE,
0704: "type", DatatypeConverter.printQName(
0705: actualTypeName, getNamespaceContext()));
0706: }
0707: actual.serializeAttributes(child, this );
0708: endAttributes();
0709: actual.serializeBody(child, this );
0710:
0711: if (actual.lookForLifecycleMethods()) {
0712: fireAfterMarshalEvents(actual, child);
0713: }
0714:
0715: cycleDetectionStack.pop();
0716: }
0717: }
0718:
0719: /**
0720: * Invoke the afterMarshal api on the external listener (if it exists) and on the bean embedded
0721: * afterMarshal api(if it exists).
0722: *
0723: * This method is called only after the callee has determined that beanInfo.lookForLifecycleMethods == true.
0724: *
0725: * @param beanInfo
0726: * @param currentTarget
0727: */
0728: private void fireAfterMarshalEvents(final JaxBeanInfo beanInfo,
0729: Object currentTarget) {
0730: // first invoke bean embedded listener
0731: if (beanInfo.hasAfterMarshalMethod()) {
0732: Method m = beanInfo.getLifecycleMethods().afterMarshal;
0733: fireMarshalEvent(currentTarget, m);
0734: }
0735:
0736: // then invoke external listener before bean embedded listener
0737: Marshaller.Listener externalListener = marshaller.getListener();
0738: if (externalListener != null) {
0739: externalListener.afterMarshal(currentTarget);
0740: }
0741:
0742: }
0743:
0744: /**
0745: * Invoke the beforeMarshal api on the external listener (if it exists) and on the bean embedded
0746: * beforeMarshal api(if it exists).
0747: *
0748: * This method is called only after the callee has determined that beanInfo.lookForLifecycleMethods == true.
0749: *
0750: * @param beanInfo
0751: * @param currentTarget
0752: */
0753: private void fireBeforeMarshalEvents(final JaxBeanInfo beanInfo,
0754: Object currentTarget) {
0755: // first invoke bean embedded listener
0756: if (beanInfo.hasBeforeMarshalMethod()) {
0757: Method m = beanInfo.getLifecycleMethods().beforeMarshal;
0758: fireMarshalEvent(currentTarget, m);
0759: }
0760:
0761: // then invoke external listener
0762: Marshaller.Listener externalListener = marshaller.getListener();
0763: if (externalListener != null) {
0764: externalListener.beforeMarshal(currentTarget);
0765: }
0766: }
0767:
0768: private void fireMarshalEvent(Object target, Method m) {
0769: try {
0770: m.invoke(target, marshaller);
0771: } catch (Exception e) {
0772: // this really only happens if there is a bug in the ri
0773: throw new IllegalStateException(e);
0774: }
0775: }
0776:
0777: public void attWildcardAsURIs(Map<QName, String> attributes,
0778: String fieldName) {
0779: if (attributes == null)
0780: return;
0781: for (Map.Entry<QName, String> e : attributes.entrySet()) {
0782: QName n = e.getKey();
0783: String nsUri = n.getNamespaceURI();
0784: if (nsUri.length() > 0) {
0785: String p = n.getPrefix();
0786: if (p.length() == 0)
0787: p = null;
0788: nsContext.declareNsUri(nsUri, p, true);
0789: }
0790: }
0791: }
0792:
0793: public void attWildcardAsAttributes(Map<QName, String> attributes,
0794: String fieldName) throws SAXException {
0795: if (attributes == null)
0796: return;
0797: for (Map.Entry<QName, String> e : attributes.entrySet()) {
0798: QName n = e.getKey();
0799: attribute(n.getNamespaceURI(), n.getLocalPart(), e
0800: .getValue());
0801: }
0802: }
0803:
0804: /**
0805: * Short for the following call sequence:
0806: *
0807: * <pre>
0808: getNamespaceContext().declareNamespace(WellKnownNamespace.XML_SCHEMA_INSTANCE,"xsi",true);
0809: endNamespaceDecls();
0810: attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE,"nil","true");
0811: endAttributes();
0812: * </pre>
0813: */
0814: public final void writeXsiNilTrue() throws SAXException,
0815: IOException, XMLStreamException {
0816: getNamespaceContext().declareNamespace(
0817: WellKnownNamespace.XML_SCHEMA_INSTANCE, "xsi", true);
0818: endNamespaceDecls(null);
0819: attribute(WellKnownNamespace.XML_SCHEMA_INSTANCE, "nil", "true");
0820: endAttributes();
0821: }
0822:
0823: public <E> void writeDom(E element, DomHandler<E, ?> domHandler,
0824: Object parentBean, String fieldName) throws SAXException {
0825: Source source = domHandler.marshal(element, this );
0826: if (contentHandlerAdapter == null)
0827: contentHandlerAdapter = new ContentHandlerAdaptor(this );
0828: try {
0829: getIdentityTransformer().transform(source,
0830: new SAXResult(contentHandlerAdapter));
0831: } catch (TransformerException e) {
0832: reportError(fieldName, e);
0833: }
0834: }
0835:
0836: public Transformer getIdentityTransformer() {
0837: if (identityTransformer == null)
0838: identityTransformer = JAXBContextImpl.createTransformer();
0839: return identityTransformer;
0840: }
0841:
0842: public void setPrefixMapper(NamespacePrefixMapper prefixMapper) {
0843: nsContext.setPrefixMapper(prefixMapper);
0844: }
0845:
0846: /**
0847: * Reset this object to write to the specified output.
0848: *
0849: * @param schemaLocation
0850: * if non-null, this value is printed on the root element as xsi:schemaLocation
0851: * @param noNsSchemaLocation
0852: * Similar to 'schemaLocation' but this one works for xsi:noNamespaceSchemaLocation
0853: */
0854: public void startDocument(XmlOutput out, boolean fragment,
0855: String schemaLocation, String noNsSchemaLocation)
0856: throws IOException, SAXException, XMLStreamException {
0857: setThreadAffinity();
0858: pushCoordinator();
0859: nsContext.reset();
0860: nse = nsContext.getCurrent();
0861: if (attachmentMarshaller != null
0862: && attachmentMarshaller.isXOPPackage())
0863: out = new MTOMXmlOutput(out);
0864: this .out = out;
0865: objectsWithId.clear();
0866: idReferencedObjects.clear();
0867: textHasAlreadyPrinted = false;
0868: seenRoot = false;
0869: this .schemaLocation = schemaLocation;
0870: this .noNsSchemaLocation = noNsSchemaLocation;
0871: this .fragment = fragment;
0872: this .inlineBinaryFlag = false;
0873: this .expectedMimeType = null;
0874: cycleDetectionStack.reset();
0875:
0876: out.startDocument(this , fragment, knownUri2prefixIndexMap,
0877: nsContext);
0878: }
0879:
0880: public void endDocument() throws IOException, SAXException,
0881: XMLStreamException {
0882: out.endDocument(fragment);
0883: }
0884:
0885: public void close() {
0886: popCoordinator();
0887: resetThreadAffinity();
0888: }
0889:
0890: /**
0891: * This method can be called after {@link #startDocument} is called
0892: * but before the marshalling begins, to set the currently in-scope namespace
0893: * bindings.
0894: *
0895: * <p>
0896: * This method is useful to avoid redundant namespace declarations when
0897: * the marshalling is producing a sub-document.
0898: */
0899: public void addInscopeBinding(String nsUri, String prefix) {
0900: nsContext.put(nsUri, prefix);
0901: }
0902:
0903: /**
0904: * Gets the MIME type with which the binary content shall be printed.
0905: *
0906: * <p>
0907: * This method shall be used from those {@link RuntimeBuiltinLeafInfo} that are
0908: * bound to base64Binary.
0909: *
0910: * @see JAXBContextImpl#getXMIMEContentType(Object)
0911: */
0912: public String getXMIMEContentType() {
0913: // xmime:contentType takes precedence
0914: String v = grammar.getXMIMEContentType(cycleDetectionStack
0915: .peek());
0916: if (v != null)
0917: return v;
0918:
0919: // then look for the current in-scope @XmlMimeType
0920: if (expectedMimeType != null)
0921: return expectedMimeType.toString();
0922:
0923: return null;
0924: }
0925:
0926: private void startElement() {
0927: nse = nse.push();
0928:
0929: if (!seenRoot) {
0930: // seenRoot set to true in endAttributes
0931: // first declare all known URIs
0932: String[] knownUris = nameList.namespaceURIs;
0933: for (int i = 0; i < knownUris.length; i++)
0934: knownUri2prefixIndexMap[i] = nsContext.declareNsUri(
0935: knownUris[i], null,
0936: nameList.nsUriCannotBeDefaulted[i]);
0937:
0938: // then declare user-specified namespace URIs.
0939: // work defensively. we are calling an user-defined method.
0940: String[] uris = nsContext.getPrefixMapper()
0941: .getPreDeclaredNamespaceUris();
0942: if (uris != null) {
0943: for (String uri : uris) {
0944: if (uri != null)
0945: nsContext.declareNsUri(uri, null, false);
0946: }
0947: }
0948: String[] pairs = nsContext.getPrefixMapper()
0949: .getPreDeclaredNamespaceUris2();
0950: if (pairs != null) {
0951: for (int i = 0; i < pairs.length; i += 2) {
0952: String prefix = pairs[i];
0953: String nsUri = pairs[i + 1];
0954: if (prefix != null && nsUri != null)
0955: // in this case, we don't want the redundant binding consolidation
0956: // to happen (such as declaring the same namespace URI twice with
0957: // different prefixes.) Hence we call the put method directly.
0958: nsContext.put(nsUri, prefix);
0959: }
0960: }
0961:
0962: if (schemaLocation != null || noNsSchemaLocation != null) {
0963: nsContext.declareNsUri(
0964: WellKnownNamespace.XML_SCHEMA_INSTANCE, "xsi",
0965: true);
0966: }
0967: }
0968:
0969: nsContext.collectionMode = true;
0970: textHasAlreadyPrinted = false;
0971: }
0972:
0973: private MimeType expectedMimeType;
0974:
0975: /**
0976: * This method is used by {@link MimeTypedTransducer} to set the expected MIME type
0977: * for the encapsulated {@link Transducer}.
0978: */
0979: public MimeType setExpectedMimeType(MimeType expectedMimeType) {
0980: MimeType old = this .expectedMimeType;
0981: this .expectedMimeType = expectedMimeType;
0982: return old;
0983: }
0984:
0985: /**
0986: * True to force inlining.
0987: */
0988: private boolean inlineBinaryFlag;
0989:
0990: public boolean setInlineBinaryFlag(boolean value) {
0991: boolean old = inlineBinaryFlag;
0992: this .inlineBinaryFlag = value;
0993: return old;
0994: }
0995:
0996: public boolean getInlineBinaryFlag() {
0997: return inlineBinaryFlag;
0998: }
0999:
1000: /**
1001: * Field used to support an {@link XmlSchemaType} annotation.
1002: *
1003: * <p>
1004: * When we are marshalling a property with an effective {@link XmlSchemaType},
1005: * this field is set to hold the QName of that type. The {@link Transducer} that
1006: * actually converts a Java object into XML can look this property to decide
1007: * how to marshal the value.
1008: */
1009: private QName schemaType;
1010:
1011: public QName setSchemaType(QName st) {
1012: QName old = schemaType;
1013: schemaType = st;
1014: return old;
1015: }
1016:
1017: public QName getSchemaType() {
1018: return schemaType;
1019: }
1020:
1021: public void setObjectIdentityCycleDetection(boolean val) {
1022: cycleDetectionStack.setUseIdentity(val);
1023: }
1024:
1025: public boolean getObjectIdentityCycleDetection() {
1026: return cycleDetectionStack.getUseIdentity();
1027: }
1028:
1029: void reconcileID() throws SAXException {
1030: // find objects that were not a part of the object graph
1031: idReferencedObjects.removeAll(objectsWithId);
1032:
1033: for (Object idObj : idReferencedObjects) {
1034: try {
1035: String id = getIdFromObject(idObj);
1036: reportError(new NotIdentifiableEventImpl(
1037: ValidationEvent.ERROR, Messages.DANGLING_IDREF
1038: .format(id),
1039: new ValidationEventLocatorImpl(idObj)));
1040: } catch (JAXBException e) {
1041: // this error should have been reported already. just ignore here.
1042: }
1043: }
1044:
1045: // clear the garbage
1046: idReferencedObjects.clear();
1047: objectsWithId.clear();
1048: }
1049:
1050: public boolean handleError(Exception e) {
1051: return handleError(e, cycleDetectionStack.peek(), null);
1052: }
1053:
1054: public boolean handleError(Exception e, Object source,
1055: String fieldName) {
1056: return handleEvent(new ValidationEventImpl(
1057: ValidationEvent.ERROR, e.getMessage(),
1058: new ValidationEventLocatorExImpl(source, fieldName), e));
1059: }
1060:
1061: public boolean handleEvent(ValidationEvent event) {
1062: try {
1063: return marshaller.getEventHandler().handleEvent(event);
1064: } catch (JAXBException e) {
1065: // impossible
1066: throw new Error(e);
1067: }
1068: }
1069:
1070: private void reportMissingObjectError(String fieldName)
1071: throws SAXException {
1072: reportError(new ValidationEventImpl(ValidationEvent.ERROR,
1073: Messages.MISSING_OBJECT.format(fieldName),
1074: getCurrentLocation(fieldName),
1075: new NullPointerException()));
1076: }
1077:
1078: /**
1079: * Called when a referenced object doesn't have an ID.
1080: */
1081: public void errorMissingId(Object obj) throws SAXException {
1082: reportError(new ValidationEventImpl(ValidationEvent.ERROR,
1083: Messages.MISSING_ID.format(obj),
1084: new ValidationEventLocatorImpl(obj)));
1085: }
1086:
1087: public ValidationEventLocator getCurrentLocation(String fieldName) {
1088: return new ValidationEventLocatorExImpl(cycleDetectionStack
1089: .peek(), fieldName);
1090: }
1091:
1092: protected ValidationEventLocator getLocation() {
1093: return getCurrentLocation(null);
1094: }
1095:
1096: /**
1097: * When called from within the realm of the marshaller, this method
1098: * returns the current {@link XMLSerializer} in charge.
1099: */
1100: public static XMLSerializer getInstance() {
1101: return (XMLSerializer) Coordinator._getInstance();
1102: }
1103: }
|