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