http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlParserContext.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlParserContext.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlParserContext.java new file mode 100755 index 0000000..322cdc8 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlParserContext.java @@ -0,0 +1,213 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.xml; + +import static com.ibm.juno.core.xml.XmlParserProperties.*; + +import java.io.*; +import java.lang.reflect.*; + +import javax.xml.stream.*; +import javax.xml.stream.util.*; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.parser.*; +import com.ibm.juno.core.utils.*; + +/** + * Context object that lives for the duration of a single parsing of {@link XmlParser}. + * <p> + * + * @author James Bognar ([email protected]) + */ +public final class XmlParserContext extends ParserContext { + + /** + * XMLSchema namespace URI ({@link String}, default=<js>"http://www.w3.org/2001/XMLSchema-instance"</js>). + * <p> + * The XMLSchema namespace. + */ + private final String xsiNs; + private final boolean + trimWhitespace, + validating, + coalescing, + replaceEntityReferences, + preserveRootElement; + private final XMLReporter reporter; + private final XMLResolver resolver; + private final XMLEventAllocator eventAllocator; + private XMLStreamReader xmlStreamReader; + + + /** + * Create a new parser context with the specified options. + * + * @param beanContext The bean context being used. + * @param pp The default parser properties. + * @param xpp The default XML parser properties. + * @param op The override properties. + * @param javaMethod The java method that called this parser, usually the method in a REST servlet. + * @param outer The outer object for instantiating top-level non-static inner classes. + */ + public XmlParserContext(BeanContext beanContext, ParserProperties pp, XmlParserProperties xpp, ObjectMap op, Method javaMethod, Object outer) { + super(beanContext, pp, op, javaMethod, outer); + if (op == null || op.isEmpty()) { + xsiNs = xpp.getXsiNs(); + trimWhitespace = xpp.isTrimWhitespace(); + validating = xpp.isValidating(); + coalescing = xpp.isCoalescing(); + replaceEntityReferences = xpp.isReplaceEntityReferences(); + reporter = xpp.getReporter(); + resolver = xpp.getResolver(); + eventAllocator = xpp.getEventAllocator(); + preserveRootElement = xpp.isPreserveRootElement(); + } else { + xsiNs = op.getString(XML_xsiNs, xpp.getXsiNs()); + trimWhitespace = op.getBoolean(XML_trimWhitespace, xpp.isTrimWhitespace()); + validating = op.getBoolean(XML_validating, xpp.isValidating()); + coalescing = op.getBoolean(XML_coalescing, xpp.isCoalescing()); + replaceEntityReferences = op.getBoolean(XML_replaceEntityReferences, xpp.isReplaceEntityReferences()); + reporter = (XMLReporter)op.get(XML_reporter, xpp.getReporter()); + resolver = (XMLResolver)op.get(XML_resolver, xpp.getResolver()); + eventAllocator = (XMLEventAllocator)op.get(XML_eventAllocator, xpp.getEventAllocator()); + preserveRootElement = op.getBoolean(XML_preserveRootElement, xpp.isPreserveRootElement()); + } + } + + /** + * Returns the {@link XmlParserProperties#XML_xsiNs} setting in this context. + * + * @return The {@link XmlParserProperties#XML_xsiNs} setting in this context. + */ + public String getXsiNs() { + return xsiNs; + } + + /** + * Returns the {@link XmlParserProperties#XML_trimWhitespace} setting in this context. + * + * @return The {@link XmlParserProperties#XML_trimWhitespace} setting in this context. + */ + public boolean isTrimWhitespace() { + return trimWhitespace; + } + + /** + * Returns the {@link XmlParserProperties#XML_preserveRootElement} setting in this context. + * + * @return The {@link XmlParserProperties#XML_preserveRootElement} setting in this context. + */ + public boolean isPreserveRootElement() { + return preserveRootElement; + } + + /** + * Returns the {@link XmlParserProperties#XML_validating} setting in this context. + * + * @return The {@link XmlParserProperties#XML_validating} setting in this context. + */ + public boolean isValidating() { + return validating; + } + + /** + * Returns the {@link XmlParserProperties#XML_coalescing} setting in this context. + * + * @return The {@link XmlParserProperties#XML_coalescing} setting in this context. + */ + public boolean isCoalescing() { + return coalescing; + } + + /** + * Returns the {@link XmlParserProperties#XML_replaceEntityReferences} setting in this context. + * + * @return The {@link XmlParserProperties#XML_replaceEntityReferences} setting in this context. + */ + public boolean isReplaceEntityReferences() { + return replaceEntityReferences; + } + + /** + * Returns the {@link XmlParserProperties#XML_reporter} setting in this context. + * + * @return The {@link XmlParserProperties#XML_reporter} setting in this context. + */ + public XMLReporter getReporter() { + return reporter; + } + + /** + * Returns the {@link XmlParserProperties#XML_resolver} setting in this context. + * + * @return The {@link XmlParserProperties#XML_resolver} setting in this context. + */ + public XMLResolver getResolver() { + return resolver; + } + + /** + * Returns the {@link XmlParserProperties#XML_eventAllocator} setting in this context. + * + * @return The {@link XmlParserProperties#XML_eventAllocator} setting in this context. + */ + public XMLEventAllocator getEventAllocator() { + return eventAllocator; + } + + + /** + * Wrap the specified reader in a STAX reader based on settings in this context. + * + * @param r The input reader. + * @param estimatedSize The estimated size of the contents of the reader. + * @return The new STAX reader. + * @throws ParseException If problem occurred trying to create reader. + */ + public XMLStreamReader getReader(Reader r, int estimatedSize) throws ParseException { + try { + r = IOUtils.getBufferedReader(r, estimatedSize); + XMLInputFactory factory = XMLInputFactory.newInstance(); + factory.setProperty(XMLInputFactory.IS_VALIDATING, validating); + factory.setProperty(XMLInputFactory.IS_COALESCING, coalescing); + factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, replaceEntityReferences); + if (factory.isPropertySupported(XMLInputFactory.REPORTER) && reporter != null) + factory.setProperty(XMLInputFactory.REPORTER, reporter); + if (factory.isPropertySupported(XMLInputFactory.RESOLVER) && resolver != null) + factory.setProperty(XMLInputFactory.RESOLVER, resolver); + if (factory.isPropertySupported(XMLInputFactory.ALLOCATOR) && eventAllocator != null) + factory.setProperty(XMLInputFactory.ALLOCATOR, eventAllocator); + xmlStreamReader = factory.createXMLStreamReader(r); + xmlStreamReader.nextTag(); + } catch (Error e) { + close(); + throw new ParseException(e.getLocalizedMessage()); + } catch (XMLStreamException e) { + close(); + throw new ParseException(e); + } + + return xmlStreamReader; + } + + /** + * Silently closes the XML stream returned by the call to {@link #getReader(Reader, int)}. + */ + @Override /* ParserContext */ + public void close() throws ParseException { + super.close(); + try { + if (xmlStreamReader != null) + xmlStreamReader.close(); + } catch (XMLStreamException e) { + // Ignore. + } + } +}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlParserProperties.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlParserProperties.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlParserProperties.class new file mode 100755 index 0000000..c5e5e4f Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlParserProperties.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlParserProperties.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlParserProperties.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlParserProperties.java new file mode 100755 index 0000000..23b9503 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlParserProperties.java @@ -0,0 +1,244 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * © Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.xml; + +import javax.xml.stream.*; +import javax.xml.stream.util.*; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.parser.*; + +/** + * Configurable properties on the {@link XmlParser} class. + * <p> + * Use the {@link XmlParser#setProperty(String, Object)} method to set property values. + * <p> + * In addition to these properties, the following properties are also applicable for {@link XmlParser}. + * <ul> + * <li>{@link ParserProperties} + * <li>{@link BeanContextProperties} + * </ul> + * + * @author James Bognar ([email protected]) + */ +public final class XmlParserProperties implements Cloneable { + + /** + * XMLSchema-instance namespace URI ({@link String}, default=<js>"http://www.w3.org/2001/XMLSchema-instance"</js>). + * <p> + * The XMLSchema namespace. + */ + public static final String XML_xsiNs = "XmlParser.xsiNs"; + + /** + * Trim whitespace from text elements ({@link Boolean}, default=<jk>false</jk>). + * <p> + * If <jk>true</jk>, whitespace in text elements will be automatically trimmed. + */ + public static final String XML_trimWhitespace = "XmlParser.trimWhitespace"; + + /** + * Set validating mode ({@link Boolean}, default=<jk>false</jk>). + * <p> + * If <jk>true</jk>, XML document will be validated. + * See {@link XMLInputFactory#IS_VALIDATING} for more info. + */ + public static final String XML_validating = "XmlParser.validating"; + + /** + * Set coalescing mode ({@link Boolean}, default=<jk>false</jk>). + * <p> + * If <jk>true</jk>, XML text elements will be coalesced. + * See {@link XMLInputFactory#IS_COALESCING} for more info. + */ + public static final String XML_coalescing = "XmlParser.coalescing"; + + /** + * Replace entity references ({@link Boolean}, default=<jk>true</jk>). + * <p> + * If <jk>true</jk>, entity references will be replace during parsing. + * See {@link XMLInputFactory#IS_REPLACING_ENTITY_REFERENCES} for more info. + */ + public static final String XML_replaceEntityReferences = "XmlParser.replaceEntityReferences"; + + /** + * XML reporter ({@link XMLReporter}, default=<jk>null</jk>). + * <p> + * Associates an {@link XMLReporter} with this parser. + * <p> + * Note: Reporters are not copied to new parsers during a clone. + */ + public static final String XML_reporter = "XmlParser.reporter"; + + /** + * XML resolver ({@link XMLResolver}, default=<jk>null</jk>). + * <p> + * Associates an {@link XMLResolver} with this parser. + */ + public static final String XML_resolver = "XmlParser.resolver"; + + /** + * XML event allocator. ({@link XMLEventAllocator}, default=<jk>false</jk>). + * <p> + * Associates an {@link XMLEventAllocator} with this parser. + */ + public static final String XML_eventAllocator = "XmlParser.eventAllocator"; + + /** + * Preserve root element during generalized parsing ({@link Boolean}, default=<jk>false</jk>). + * <p> + * If <jk>true</jk>, when parsing into a generic {@link ObjectMap}, the map will + * contain a single entry whose key is the root element name. + * + * Example: + * <table class='styled'> + * <tr> + * <td>XML</td> + * <td>ObjectMap.toString(), preserveRootElement==false</td> + * <td>ObjectMap.toString(), preserveRootElement==true</td> + * </tr> + * <tr> + * <td><code><xt><root><a></xt>foobar<xt></a></root></xt><code></td> + * <td><code>{ a:<js>'foobar'</js> }</code></td> + * <td><code>{ root: { a:<js>'foobar'</js> }}</code></td> + * </tr> + * </table> + * + */ + public static final String XML_preserveRootElement = "XmlParser.preserveRootElement"; + + private String xsiNs = "http://www.w3.org/2001/XMLSchema-instance"; + private boolean + trimWhitespace = false, + validating = false, + coalescing = false, + replaceEntityReferences = true, + preserveRootElement = false; + private XMLReporter reporter; + private XMLResolver resolver; + private XMLEventAllocator eventAllocator; + + /** + * Sets the specified property value. + * @param property The property name. + * @param value The property value. + * @return <jk>true</jk> if property name was valid and property was set. + * @throws LockedException If bean context is locked. + */ + protected boolean setProperty(String property, Object value) throws LockedException { + BeanContext bc = BeanContext.DEFAULT; + if (property.equals(XML_trimWhitespace)) + trimWhitespace = bc.convertToType(value, Boolean.class); + else if (property.equals(XML_validating)) + validating = bc.convertToType(value, Boolean.class); + else if (property.equals(XML_coalescing)) + coalescing = bc.convertToType(value, Boolean.class); + else if (property.equals(XML_replaceEntityReferences)) + replaceEntityReferences = bc.convertToType(value, Boolean.class); + else if (property.equals(XML_xsiNs)) + xsiNs = value.toString(); + else if (property.equals(XML_reporter) && value instanceof XMLReporter) + reporter = (XMLReporter)value; + else if (property.equals(XML_resolver) && value instanceof XMLResolver) + resolver = (XMLResolver)value; + else if (property.equals(XML_eventAllocator) && value instanceof XMLEventAllocator) + eventAllocator = (XMLEventAllocator)value; + else if (property.equals(XML_preserveRootElement)) + preserveRootElement = bc.convertToType(value, Boolean.class); + else + return false; + return true; + } + + /** + * Returns the current {@link #XML_xsiNs} value. + * @return The current {@link #XML_xsiNs} value. + */ + public String getXsiNs() { + return xsiNs; + } + + /** + * Returns the current {@link #XML_trimWhitespace} value. + * @return The current {@link #XML_trimWhitespace} value. + */ + public boolean isTrimWhitespace() { + return trimWhitespace; + } + + /** + * Returns the current {@link #XML_preserveRootElement} value. + * @return The current {@link #XML_preserveRootElement} value. + */ + public boolean isPreserveRootElement() { + return preserveRootElement; + } + + /** + * Returns the current {@link #XML_validating} value. + * @return The current {@link #XML_validating} value. + */ + public boolean isValidating() { + return validating; + } + + /** + * Returns the current {@link #XML_coalescing} value. + * @return The current {@link #XML_coalescing} value. + */ + public boolean isCoalescing() { + return coalescing; + } + + /** + * Returns the current {@link #XML_replaceEntityReferences} value. + * @return The current {@link #XML_replaceEntityReferences} value. + */ + public boolean isReplaceEntityReferences() { + return replaceEntityReferences; + } + + /** + * Returns the current {@link #XML_reporter} value. + * @return The current {@link #XML_reporter} value. + */ + public XMLReporter getReporter() { + return reporter; + } + + /** + * Returns the current {@link #XML_reporter} value. + * @return The current {@link #XML_reporter} value. + */ + public XMLResolver getResolver() { + return resolver; + } + + /** + * Returns the current {@link #XML_eventAllocator} value. + * @return The current {@link #XML_eventAllocator} value. + */ + public XMLEventAllocator getEventAllocator() { + return eventAllocator; + } + + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Object */ + public XmlParserProperties clone() { + try { + return (XmlParserProperties)super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); // Shouldn't happen. + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaDocSerializer.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaDocSerializer.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaDocSerializer.class new file mode 100755 index 0000000..4aa659b Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaDocSerializer.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaDocSerializer.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaDocSerializer.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaDocSerializer.java new file mode 100755 index 0000000..0c3b5f5 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaDocSerializer.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.xml; + +import java.io.*; + +import com.ibm.juno.core.serializer.*; + +/** + * Serializes POJO metadata to HTTP responses as XML. + * + * + * <h6 class='topic'>Media types</h6> + * <p> + * Handles <code>Accept</code> types: <code>text/xml+schema</code> + * <p> + * Produces <code>Content-Type</code> types: <code>text/xml</code> + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Same as {@link XmlSchemaSerializer}, except prepends <code><xt><?xml</xt> <xa>version</xa>=<xs>'1.0'</xs> <xa>encoding</xa>=<xs>'UTF-8'</xs><xt>?></xt></code> to the response + * to make it a valid XML document. + * + * + * @author James Bognar ([email protected]) + */ +public class XmlSchemaDocSerializer extends XmlSchemaSerializer { + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Serializer */ + protected void doSerialize(Object o, Writer out, SerializerContext ctx) throws IOException, SerializeException { + XmlSerializerContext xctx = (XmlSerializerContext)ctx; + XmlSerializerWriter w = xctx.getWriter(out); + w.append("<?xml") + .attr("version", "1.0") + .attr("encoding", "UTF-8") + .appendln("?>"); + super.doSerialize(o, w, ctx); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$1.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$1.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$1.class new file mode 100755 index 0000000..98bc0e8 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$1.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$QueueEntry.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$QueueEntry.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$QueueEntry.class new file mode 100755 index 0000000..7948545 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$QueueEntry.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$Schema.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$Schema.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$Schema.class new file mode 100755 index 0000000..fa87011 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$Schema.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$Schemas.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$Schemas.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$Schemas.class new file mode 100755 index 0000000..fba563f Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer$Schemas.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer.class new file mode 100755 index 0000000..1361066 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer.java new file mode 100755 index 0000000..bc40ae4 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSchemaSerializer.java @@ -0,0 +1,588 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.xml; + +import static com.ibm.juno.core.xml.annotation.XmlFormat.*; + +import java.io.*; +import java.lang.reflect.*; +import java.text.*; +import java.util.*; +import java.util.regex.*; + +import javax.xml.*; +import javax.xml.transform.stream.*; +import javax.xml.validation.*; + +import org.w3c.dom.bootstrap.*; +import org.w3c.dom.ls.*; +import org.xml.sax.*; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.annotation.*; +import com.ibm.juno.core.serializer.*; +import com.ibm.juno.core.utils.*; +import com.ibm.juno.core.xml.annotation.*; + +/** + * Serializes POJO metadata to HTTP responses as XML. + * + * + * <h6 class='topic'>Media types</h6> + * <p> + * Handles <code>Accept</code> types: <code>text/xml+schema</code> + * <p> + * Produces <code>Content-Type</code> types: <code>text/xml</code> + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Produces the XML-schema representation of the XML produced by the {@link XmlSerializer} class with the same properties. + * + * + * <h6 class='topic'>Configurable properties</h6> + * <p> + * This class has the following properties associated with it: + * <ul> + * <li>{@link XmlSerializerProperties} + * <li>{@link SerializerProperties} + * <li>{@link BeanContextProperties} + * </ul> + * + * @author James Bognar ([email protected]) + */ +@Produces(value="text/xml+schema",contentType="text/xml") +public class XmlSchemaSerializer extends XmlSerializer { + + @Override /* XmlSerializer */ + protected void doSerialize(Object o, Writer out, SerializerContext ctx) throws IOException, SerializeException { + XmlSerializerContext xctx = (XmlSerializerContext)ctx; + + if (xctx.isEnableNamespaces() && xctx.isAutoDetectNamespaces()) + findNsfMappings(o, xctx); + + Namespace xs = xctx.getXsNamespace(); + Namespace[] allNs = ArrayUtils.append(new Namespace[]{xctx.getDefaultNamespace()}, xctx.getNamespaces()); + + Schemas s = new Schemas(xs, xctx.getDefaultNamespace(), allNs, xctx); + s.process(o, xctx); + s.serializeTo(out); + } + + /** + * Returns an XML-Schema validator based on the output returned by {@link #doSerialize(Object, Writer, SerializerContext)}; + * + * @param o The object to serialize. + * @param ctx The serializer context object return by {@link #createContext(ObjectMap, Method)}.<br> + * Can be <jk>null</jk>. + * @return The new validator. + * @throws SAXException If a problem was detected in the XML-Schema output produced by this serializer. + * @throws SerializeException If a problem occurred trying to convert the output. + */ + public Validator getValidator(Object o, SerializerContext ctx) throws SerializeException, SAXException { + String xmlSchema = serialize(o, ctx); + + // create a SchemaFactory capable of understanding WXS schemas + SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + + if (xmlSchema.indexOf('\u0000') != -1) { + + // Break it up into a map of namespaceURI->schema document + final Map<String,String> schemas = new HashMap<String,String>(); + String[] ss = xmlSchema.split("\u0000"); + xmlSchema = ss[0]; + for (String s : ss) { + Matcher m = pTargetNs.matcher(s); + if (m.find()) + schemas.put(m.group(1), s); + } + + // Create a custom resolver + factory.setResourceResolver( + new LSResourceResolver() { + + @Override /* LSResourceResolver */ + public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) { + + String schema = schemas.get(namespaceURI); + if (schema == null) + throw new RuntimeException(MessageFormat.format("No schema found for namespaceURI ''{0}''", namespaceURI)); + + try { + DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance(); + DOMImplementationLS domImplementationLS = (DOMImplementationLS)registry.getDOMImplementation("LS 3.0"); + LSInput in = domImplementationLS.createLSInput(); + in.setCharacterStream(new StringReader(schema)); + in.setSystemId(systemId); + return in; + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + ); + } + return factory.newSchema(new StreamSource(new StringReader(xmlSchema))).newValidator(); + } + + private static Pattern pTargetNs = Pattern.compile("targetNamespace=['\"]([^'\"]+)['\"]"); + + + /* An instance of a global element, global attribute, or XML type to be serialized. */ + private static class QueueEntry { + Namespace ns; + String name; + ClassMeta<?> cm; + QueueEntry(Namespace ns, String name, ClassMeta<?> cm) { + this.ns = ns; + this.name = name; + this.cm = cm; + } + } + + /* An encapsulation of all schemas present in the metamodel of the serialized object. */ + private class Schemas extends LinkedHashMap<Namespace,Schema> { + + private static final long serialVersionUID = 1L; + + private Namespace defaultNs; + private LinkedList<QueueEntry> + elementQueue = new LinkedList<QueueEntry>(), + attributeQueue = new LinkedList<QueueEntry>(), + typeQueue = new LinkedList<QueueEntry>(); + + private Schemas(Namespace xs, Namespace defaultNs, Namespace[] allNs, XmlSerializerContext ctx) throws IOException { + this.defaultNs = defaultNs; + for (Namespace ns : allNs) + put(ns, new Schema(this, xs, ns, defaultNs, allNs, ctx)); + } + + private Schema getSchema(Namespace ns) { + if (ns == null) + ns = defaultNs; + Schema s = get(ns); + if (s == null) + throw new RuntimeException("No schema defined for namespace '"+ns+"'"); + return s; + } + + private void process(Object o, SerializerContext ctx) throws IOException { + ClassMeta<?> cm = ctx.getBeanContext().getClassMetaForObject(o); + Namespace ns = defaultNs; + if (cm == null) + queueElement(ns, "null", object()); + else { + XmlClassMeta xmlMeta = cm.getXmlMeta(); + if (xmlMeta.getElementName() != null && xmlMeta.getNamespace() != null) + ns = xmlMeta.getNamespace(); + queueElement(ns, xmlMeta.getElementName(), cm); + } + processQueue(); + } + + + private void processQueue() throws IOException { + boolean b; + do { + b = false; + while (! elementQueue.isEmpty()) { + QueueEntry q = elementQueue.removeFirst(); + b |= getSchema(q.ns).processElement(q.name, q.cm); + } + while (! typeQueue.isEmpty()) { + QueueEntry q = typeQueue.removeFirst(); + b |= getSchema(q.ns).processType(q.name, q.cm); + } + while (! attributeQueue.isEmpty()) { + QueueEntry q = attributeQueue.removeFirst(); + b |= getSchema(q.ns).processAttribute(q.name, q.cm); + } + } while (b); + } + + private void queueElement(Namespace ns, String name, ClassMeta<?> cm) { + elementQueue.add(new QueueEntry(ns, name, cm)); + } + + private void queueType(Namespace ns, String name, ClassMeta<?> cm) { + if (name == null) + name = XmlUtils.encodeElementName(cm.toString()); + typeQueue.add(new QueueEntry(ns, name, cm)); + } + + private void queueAttribute(Namespace ns, String name, ClassMeta<?> cm) { + attributeQueue.add(new QueueEntry(ns, name, cm)); + } + + private void serializeTo(Writer w) throws IOException { + boolean b = false; + for (Schema s : values()) { + if (b) + w.append('\u0000'); + w.append(s.toString()); + b = true; + } + } + } + + /* An encapsulation of a single schema. */ + private class Schema { + private StringWriter sw = new StringWriter(); + private XmlSerializerWriter w; + private XmlSerializerContext ctx; + private Namespace defaultNs, targetNs; + private Schemas schemas; + private Set<String> + processedTypes = new HashSet<String>(), + processedAttributes = new HashSet<String>(), + processedElements = new HashSet<String>(); + + public Schema(Schemas schemas, Namespace xs, Namespace targetNs, Namespace defaultNs, Namespace[] allNs, XmlSerializerContext ctx) throws IOException { + this.schemas = schemas; + this.defaultNs = defaultNs; + this.targetNs = targetNs; + this.ctx = ctx; + w = new XmlSerializerWriter(sw, ctx.isUseIndentation(), ctx.getQuoteChar(), null, null, true, null); + int i = ctx.getIndent(); + w.oTag(i, "schema"); + w.attr("xmlns", xs.getUri()); + w.attr("targetNamespace", targetNs.getUri()); + w.attr("elementFormDefault", "qualified"); + if (targetNs != defaultNs) + w.attr("attributeFormDefault", "qualified"); + for (Namespace ns2 : allNs) + w.attr("xmlns", ns2.name, ns2.uri); + w.append('>').nl(); + for (Namespace ns : allNs) { + if (ns != targetNs) { + w.oTag(i+1, "import") + .attr("namespace", ns.getUri()) + .attr("schemaLocation", ns.getName()+".xsd") + .append("/>").nl(); + } + } + } + + private boolean processElement(String name, ClassMeta<?> cm) throws IOException { + if (processedElements.contains(name)) + return false; + processedElements.add(name); + + ClassMeta<?> ft = cm.getFilteredClassMeta(); + int i = ctx.getIndent() + 1; + if (name == null) + name = getElementName(ft); + Namespace ns = first(ft.getXmlMeta().getNamespace(), defaultNs); + String type = getXmlType(ns, ft); + + w.oTag(i, "element") + .attr("name", XmlUtils.encodeElementName(name)) + .attr("type", type) + .append('/').append('>').nl(); + + schemas.queueType(ns, null, ft); + schemas.processQueue(); + return true; + } + + private boolean processAttribute(String name, ClassMeta<?> cm) throws IOException { + if (processedAttributes.contains(name)) + return false; + processedAttributes.add(name); + + int i = ctx.getIndent() + 1; + String type = getXmlAttrType(cm); + + w.oTag(i, "attribute") + .attr("name", name) + .attr("type", type) + .append('/').append('>').nl(); + + return true; + } + + private boolean processType(String name, ClassMeta<?> cm) throws IOException { + if (processedTypes.contains(name)) + return false; + processedTypes.add(name); + + int i = ctx.getIndent() + 1; + + cm = cm.getFilteredClassMeta(); + + w.oTag(i, "complexType") + .attr("name", name); + + // This element can have mixed content if: + // 1) It's a generic Object (so it can theoretically be anything) + // 2) The bean has a property defined with @XmlFormat.CONTENT. + if ((cm.isBean() && cm.getBeanMeta().getXmlMeta().getXmlContentProperty() != null) || cm.isObject()) + w.attr("mixed", "true"); + + w.cTag().nl(); + + if (! (cm.isMap() || cm.isBean() || cm.hasToObjectMapMethod() || cm.isCollection() || cm.isArray() || (cm.isAbstract() && ! cm.isNumber()) || cm.isObject())) { + String base = getXmlAttrType(cm); + w.sTag(i+1, "simpleContent").nl(); + w.oTag(i+2, "extension") + .attr("base", base); + if (ctx.isAddJsonTypeAttrs() || (ctx.isAddJsonStringTypeAttrs() && base.equals("string"))) { + w.cTag().nl(); + w.oTag(i+3, "attribute") + .attr("name", "type") + .attr("type", "string") + .ceTag().nl(); + w.eTag(i+2, "extension").nl(); + } else { + w.ceTag().nl(); + } + w.eTag(i+1, "simpleContent").nl(); + + } else { + + //----- Bean ----- + if (cm.isBean()) { + BeanMeta<?> bm = cm.getBeanMeta(); + + boolean hasChildElements = false; + + for (BeanPropertyMeta<?> pMeta : bm.getPropertyMetas()) + if (pMeta.getXmlMeta().getXmlFormat() != XmlFormat.ATTR && pMeta.getXmlMeta().getXmlFormat() != XmlFormat.CONTENT) + hasChildElements = true; + + if (bm.getXmlMeta().getXmlContentProperty() != null) { + w.sTag(i+1, "sequence").nl(); + w.oTag(i+2, "any") + .attr("processContents", "skip") + .attr("minOccurs", 0) + .ceTag().nl(); + w.eTag(i+1, "sequence").nl(); + + } else if (hasChildElements) { + w.sTag(i+1, "sequence").nl(); + + boolean hasOtherNsElement = false; + + for (BeanPropertyMeta<?> pMeta : bm.getPropertyMetas()) { + XmlBeanPropertyMeta<?> xmlMeta = pMeta.getXmlMeta(); + if (xmlMeta.getXmlFormat() != XmlFormat.ATTR) { + boolean isCollapsed = xmlMeta.getXmlFormat() == COLLAPSED; + ClassMeta<?> ct2 = pMeta.getClassMeta(); + String childName = pMeta.getName(); + if (isCollapsed) { + if (xmlMeta.getChildName() != null) + childName = xmlMeta.getChildName(); + ct2 = pMeta.getClassMeta().getElementType(); + } + Namespace cNs = first(xmlMeta.getNamespace(), ct2.getXmlMeta().getNamespace(), cm.getXmlMeta().getNamespace(), defaultNs); + if (xmlMeta.getNamespace() == null) { + w.oTag(i+2, "element") + .attr("name", XmlUtils.encodeElementName(childName), true) + .attr("type", getXmlType(cNs, ct2)); + if (isCollapsed) { + w.attr("minOccurs", 0); + w.attr("maxOccurs", "unbounded"); + } else { + if (! ctx.isTrimNulls()) + w.attr("nillable", true); + else + w.attr("minOccurs", 0); + } + + w.ceTag().nl(); + } else { + // Child element is in another namespace. + schemas.queueElement(cNs, pMeta.getName(), ct2); + hasOtherNsElement = true; + } + + } + } + + // If this bean has any child elements in another namespace, + // we need to add an <any> element. + if (hasOtherNsElement) + w.oTag(i+2, "any") + .attr("minOccurs", 0) + .attr("maxOccurs", "unbounded") + .ceTag().nl(); + w.eTag(i+1, "sequence").nl(); + } + + for (BeanPropertyMeta<?> pMeta : bm.getXmlMeta().getXmlAttrProperties().values()) { + Namespace pNs = pMeta.getXmlMeta().getNamespace(); + if (pNs == null) + pNs = defaultNs; + + // If the bean attribute has a different namespace than the bean, then it needs to + // be added as a top-level entry in the appropriate schema file. + if (pNs != targetNs) { + schemas.queueAttribute(pNs, pMeta.getName(), pMeta.getClassMeta()); + w.oTag(i+1, "attribute") + //.attr("name", pMeta.getName(), true) + .attr("ref", pNs.getName() + ':' + pMeta.getName()) + .ceTag().nl(); + } + + // Otherwise, it's just a plain attribute of this bean. + else { + w.oTag(i+1, "attribute") + .attr("name", pMeta.getName(), true) + .attr("type", getXmlAttrType(pMeta.getClassMeta())) + .ceTag().nl(); + } + } + + //----- Collection ----- + } else if (cm.isCollection() || cm.isArray()) { + ClassMeta<?> elementType = cm.getElementType(); + if (elementType.isObject()) { + w.sTag(i+1, "sequence").nl(); + w.oTag(i+2, "any") + .attr("processContents", "skip") + .attr("maxOccurs", "unbounded") + .attr("minOccurs", "0") + .ceTag().nl(); + w.eTag(i+1, "sequence").nl(); + } else { + Namespace cNs = first(elementType.getXmlMeta().getNamespace(), cm.getXmlMeta().getNamespace(), defaultNs); + schemas.queueType(cNs, null, elementType); + w.sTag(i+1, "sequence").nl(); + w.oTag(i+2, "choice") + .attr("minOccurs", 0) + .attr("maxOccurs", "unbounded") + .cTag().nl(); + w.oTag(i+3, "element") + .attr("name", XmlUtils.encodeElementName(getElementName(elementType))) + .attr("type", getXmlType(cNs, elementType)) + .ceTag().nl(); + w.oTag(i+3, "element") + .attr("name", "null") + .attr("type", "string") + .ceTag().nl(); + w.eTag(i+2, "choice").nl(); + w.eTag(i+1, "sequence").nl(); + } + + //----- Map ----- + } else if (cm.isMap() || cm.hasToObjectMapMethod() || cm.isAbstract() || cm.isObject()) { + w.sTag(i+1, "sequence").nl(); + w.oTag(i+2, "any") + .attr("processContents", "skip") + .attr("maxOccurs", "unbounded") + .attr("minOccurs", "0") + .ceTag().nl(); + w.eTag(i+1, "sequence").nl(); + } + + if (ctx.isAddClassAttrs()) { + w.oTag(i+1, "attribute") + .attr("name", "_class") + .attr("type", "string") + .ceTag().nl(); + } + if (ctx.isAddJsonTypeAttrs()) { + w.oTag(i+1, "attribute") + .attr("name", "type") + .attr("type", "string") + .ceTag().nl(); + } + } + + w.eTag(i, "complexType").nl(); + schemas.processQueue(); + + return true; + } + + private String getElementName(ClassMeta<?> cm) { + cm = cm.getFilteredClassMeta(); + String name = cm.getXmlMeta().getElementName(); + + if (name == null) { + if (cm.isBoolean()) + name = "boolean"; + else if (cm.isNumber()) + name = "number"; + else if (cm.isArray() || cm.isCollection()) + name = "array"; + else if (! (cm.isMap() || cm.hasToObjectMapMethod() || cm.isBean() || cm.isCollection() || cm.isArray() || cm.isObject() || cm.isAbstract())) + name = "string"; + else + name = "object"; + } + return name; + } + + @Override /* Object */ + public String toString() { + try { + w.eTag(ctx.getIndent(), "schema").nl(); + } catch (IOException e) { + throw new RuntimeException(e); // Shouldn't happen. + } + return sw.toString(); + } + + private String getXmlType(Namespace currentNs, ClassMeta<?> cm) { + String name = null; + cm = cm.getFilteredClassMeta(); + if (currentNs == targetNs && ! ctx.isAddJsonTypeAttrs()) { + if (cm.isBoolean()) + name = "boolean"; + else if (cm.isNumber()) { + if (cm.isDecimal()) + name = "decimal"; + else + name = "integer"; + } + if (name == null && ! ctx.isAddJsonStringTypeAttrs()) { + if (! (cm.isMap() || cm.hasToObjectMapMethod() || cm.isBean() || cm.isCollection() || cm.isArray() || cm.isObject() || cm.isAbstract())) + name = "string"; + } + } + if (name == null) { + name = XmlUtils.encodeElementName(cm.toString()); + schemas.queueType(currentNs, name, cm); + return currentNs.getName() + ":" + name; + } + + return name; + } + } + + private <T> T first(T...tt) { + for (T t : tt) + if (t != null) + return t; + return null; + } + + + private static String getXmlAttrType(ClassMeta<?> cm) { + if (cm.isBoolean()) + return "boolean"; + if (cm.isNumber()) { + if (cm.isDecimal()) + return "decimal"; + return "integer"; + } + return "string"; + } + + @Override /* Serializer */ + public XmlSerializerContext createContext(ObjectMap properties, Method javaMethod) { + // This serializer must always have namespaces enabled. + if (properties == null) + properties = new ObjectMap(); + properties.put(XmlSerializerProperties.XML_enableNamespaces, true); + return new XmlSerializerContext(getBeanContext(), sp, xsp, properties, javaMethod); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$Simple.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$Simple.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$Simple.class new file mode 100755 index 0000000..bc8d4b1 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$Simple.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$SimpleSq.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$SimpleSq.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$SimpleSq.class new file mode 100755 index 0000000..3ddea63 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$SimpleSq.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$SimpleXmlJsonSq.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$SimpleXmlJsonSq.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$SimpleXmlJsonSq.class new file mode 100755 index 0000000..d1b38a7 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$SimpleXmlJsonSq.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$Sq.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$Sq.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$Sq.class new file mode 100755 index 0000000..2db7192 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$Sq.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$SqReadable.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$SqReadable.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$SqReadable.class new file mode 100755 index 0000000..71e44be Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$SqReadable.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$XmlJson.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$XmlJson.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$XmlJson.class new file mode 100755 index 0000000..8d4a34b Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$XmlJson.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$XmlJsonSq.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$XmlJsonSq.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$XmlJsonSq.class new file mode 100755 index 0000000..4264fee Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer$XmlJsonSq.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer.class new file mode 100755 index 0000000..221a0e7 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer.java new file mode 100755 index 0000000..8b75856 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializer.java @@ -0,0 +1,725 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.xml; + +import static com.ibm.juno.core.serializer.SerializerProperties.*; +import static com.ibm.juno.core.xml.XmlSerializerProperties.*; +import static com.ibm.juno.core.xml.annotation.XmlFormat.*; + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.annotation.*; +import com.ibm.juno.core.filter.*; +import com.ibm.juno.core.json.*; +import com.ibm.juno.core.serializer.*; +import com.ibm.juno.core.xml.annotation.*; + +/** + * Serializes POJO models to XML. + * + * + * <h6 class='topic'>Media types</h6> + * <p> + * Handles <code>Accept</code> types: <code>text/xml</code> + * <p> + * Produces <code>Content-Type</code> types: <code>text/xml</code> + * + * + * <h6 class='topic'>Description</h6> + * <p> + * See the {@link JsonSerializer} class for details on how Java models map to JSON. + * <p> + * For example, the following JSON... + * <p class='bcode'> + * { + * name:<js>'John Smith'</js>, + * address: { + * streetAddress: <js>'21 2nd Street'</js>, + * city: <js>'New York'</js>, + * state: <js>'NY'</js>, + * postalCode: <js>10021</js> + * }, + * phoneNumbers: [ + * <js>'212 555-1111'</js>, + * <js>'212 555-2222'</js> + * ], + * additionalInfo: <jk>null</jk>, + * remote: <jk>false</jk>, + * height: <js>62.4</js>, + * <js>'fico score'</js>: <js>' > 640'</js> + * } + * <p> + * ...maps to the following XML... + * <p class='bcode'> + * <xt><object></xt> + * <xt><name</xt> <xa>type</xa>=<xs>'string'</xs><xt>></xt>John Smith<xt></name></xt> + * <xt><address</xt> <xa>type</xa>=<xs>'object'</xs><xt>></xt> + * <xt><streetAddress</xt> <xa>type</xa>=<xs>'string'</xs><xt>></xt>21 2nd Street<xt></streetAddress></xt> + * <xt><city</xt> <xa>type</xa>=<xs>'string'</xs><xt>></xt>New York<xt></city></xt> + * <xt><state</xt> <xa>type</xa>=<xs>'string'</xs><xt>></xt>NY<xt></state></xt> + * <xt><postalCode</xt> <xa>type</xa>=<xs>'number'</xs><xt>></xt>10021<xt></postalCode></xt> + * <xt></address></xt> + * <xt><phoneNumbers</xt> <xa>type</xa>=<xs>'array'</xs><xt>></xt> + * <xt><string></xt>212 555-1111<xt></string></xt> + * <xt><string></xt>212 555-2222<xt></string></xt> + * <xt></phoneNumbers></xt> + * <xt><additionalInfo</xt> <xa>type</xa>=<xs>'null'</xs><xt>></additionalInfo></xt> + * <xt><remote</xt> <xa>type</xa>=<xs>'boolean'</xs><xt>></xt>false<xt></remote></xt> + * <xt><height</xt> <xa>type</xa>=<xs>'number'</xs><xt>></xt>62.4<xt></height></xt> + * <xt><fico_x0020_score</xt> <xa>type</xa>=<xs>'string'</xs><xt>></xt> &gt; 640<xt></fico_x0020_score></xt> + * <xt></object></xt> + * <p> + * This serializer provides several serialization options. Typically, one of the predefined <jsf>DEFAULT</jsf> serializers will be sufficient. + * However, custom serializers can be constructed to fine-tune behavior. + * <p> + * If an attribute name contains any non-valid XML element characters, they will be escaped using standard {@code _x####_} notation. + * + * + * <h6 class='topic'>Configurable properties</h6> + * <p> + * This class has the following properties associated with it: + * <ul> + * <li>{@link XmlSerializerProperties} + * <li>{@link SerializerProperties} + * <li>{@link BeanContextProperties} + * </ul> + * + * + * <h6 class='topic'>Behavior-specific subclasses</h6> + * <p> + * The following direct subclasses are provided for convenience: + * <ul> + * <li>{@link Sq} - Default serializer, single quotes. + * <li>{@link SqReadable} - Default serializer, single quotes, whitespace added. + * <li>{@link XmlJson} - Default serializer with JSON attribute tags. + * <li>{@link XmlJsonSq} - Default serializer with JSON attribute tags, single quotes. + * </ul> + * + * + * @author James Bognar ([email protected]) + */ +@SuppressWarnings({ "rawtypes", "unchecked" }) +@Produces("text/xml") +public class XmlSerializer extends WriterSerializer { + + /** Default serializer, all default settings. */ + public static final XmlSerializer DEFAULT = new XmlSerializer().lock(); + + /** Default serializer, single quotes. */ + public static final XmlSerializer DEFAULT_SQ = new XmlSerializer.Sq().lock(); + + /** Default serializer, single quotes, whitespace added. */ + public static final XmlSerializer DEFAULT_SQ_READABLE = new XmlSerializer.SqReadable().lock(); + + /** Default serializer with JSON attribute tags. */ + public static final XmlSerializer DEFAULT_XMLJSON = new XmlSerializer.XmlJson().lock(); + + /** Default serializer with JSON attribute tags, single quotes. */ + public static final XmlSerializer DEFAULT_XMLJSON_SQ = new XmlSerializer.XmlJsonSq().lock(); + + /** Default serializer without namespaces. */ + public static final XmlSerializer DEFAULT_SIMPLE = new XmlSerializer.Simple().lock(); + + /** Default serializer without namespaces, with single quotes. */ + public static final XmlSerializer DEFAULT_SIMPLE_SQ = new XmlSerializer.SimpleSq().lock(); + + /** Default serializer without namespaces, with JSON attribute tags and single quotes. */ + public static final XmlSerializer DEFAULT_SIMPLE_XMLJSON_SQ = new XmlSerializer.SimpleXmlJsonSq().lock(); + + + /** Default serializer, single quotes. */ + public static class Sq extends XmlSerializer { + /** Constructor */ + public Sq() { + setProperty(SERIALIZER_quoteChar, '\''); + } + } + + /** Default serializer, single quotes, whitespace added. */ + public static class SqReadable extends Sq { + /** Constructor */ + public SqReadable() { + setProperty(SERIALIZER_useIndentation, true); + } + } + + /** Default serializer with JSON attribute tags. */ + @Produces(value="text/xml+json",contentType="text/xml") + public static class XmlJson extends XmlSerializer { + /** Constructor */ + public XmlJson() { + setProperty(XML_addJsonTypeAttrs, true); + } + } + + /** Default serializer with JSON attribute tags, single quotes. */ + public static class XmlJsonSq extends XmlJson { + /** Constructor */ + public XmlJsonSq() { + setProperty(SERIALIZER_quoteChar, '\''); + } + } + + /** Default serializer without namespaces. */ + @Produces(value="text/xml+simple",contentType="text/xml") + public static class Simple extends XmlSerializer { + /** Constructor */ + public Simple() { + setProperty(XML_enableNamespaces, false); + } + } + + /** Default serializer without namespaces, single quotes. */ + public static class SimpleSq extends Simple { + /** Constructor */ + public SimpleSq() { + setProperty(SERIALIZER_quoteChar, '\''); + } + } + + /** Default serializer with JSON attribute tags, single quotes. */ + public static class SimpleXmlJsonSq extends SimpleSq { + /** Constructor */ + public SimpleXmlJsonSq() { + setProperty(XML_addJsonTypeAttrs, true); + } + } + + /** XML serializer properties currently set on this serializer. */ + protected transient XmlSerializerProperties xsp = new XmlSerializerProperties(); + + + /** + * Recursively searches for the XML namespaces on the specified POJO and adds them to the serializer context object. + * + * @param o The POJO to check. + * @param ctx The context that exists for the duration of a single serialization. + * @throws SerializeException + */ + protected void findNsfMappings(Object o, XmlSerializerContext ctx) throws SerializeException { + BeanContext bc = ctx.getBeanContext(); + ClassMeta<?> aType = null; // The actual type + aType = ctx.push(null, o, null); + + if (aType != null) { + Namespace ns = aType.getXmlMeta().getNamespace(); + if (ns != null) { + if (ns.uri != null) + ctx.addNamespace(ns); + else + ns = null; + } + } + + // Handle recursion + if (aType != null && ! aType.isPrimitive()) { + + BeanMap bm = null; + if (aType.isBeanMap()) { + bm = (BeanMap)o; + } else if (aType.isBean()) { + bm = bc.forBean(o); + } else if (aType.isDelegate()) { + ClassMeta innerType = ((Delegate)o).getClassMeta(); + Namespace ns = innerType.getXmlMeta().getNamespace(); + if (ns != null) { + if (ns.uri != null) + ctx.addNamespace(ns); + else + ns = null; + } + + if (innerType.isBean()) { + for (BeanPropertyMeta bpm : (Collection<BeanPropertyMeta>)innerType.getBeanMeta().getPropertyMetas()) { + ns = bpm.getXmlMeta().getNamespace(); + if (ns != null && ns.uri != null) + ctx.addNamespace(ns); + } + + } else if (innerType.isMap()) { + for (Object o2 : ((Map)o).values()) + findNsfMappings(o2, ctx); + } else if (innerType.isCollection()) { + for (Object o2 : ((Collection)o)) + findNsfMappings(o2, ctx); + } + + } else if (aType.isMap()) { + for (Object o2 : ((Map)o).values()) + findNsfMappings(o2, ctx); + } else if (aType.isCollection()) { + for (Object o2 : ((Collection)o)) + findNsfMappings(o2, ctx); + } else if (aType.isArray() && ! aType.getElementType().isPrimitive()) { + for (Object o2 : ((Object[])o)) + findNsfMappings(o2, ctx); + } + if (bm != null) { + for (BeanMapEntry p : (Set<BeanMapEntry>)bm.entrySet()) { + + Namespace ns = p.getMeta().getXmlMeta().getNamespace(); + if (ns != null && ns.uri != null) + ctx.addNamespace(ns); + + try { + findNsfMappings(p.getValue(), ctx); + } catch (Throwable x) { + // Ignore + } + } + } + } + + ctx.pop(); + } + + /** + * Workhorse method. + * + * @param out The writer to send the output to. + * @param o The object to serialize. + * @param eType The expected type if this is a bean property value being serialized. + * @param ctx The serializer context. + * @param elementName The root element name. + * @param elementNamespace The namespace of the element. + * @param addNamespaceUris Flag indicating that namespace URIs need to be added. + * @param format The format to serialize the output to. + * @param pMeta The bean property metadata if this is a bean property being serialized. + * @return The same writer passed in so that calls to the writer can be chained. + * @throws IOException If a problem occurred trying to write to the writer. + * @throws SerializeException If a problem occurred trying to convert the output. + */ + protected XmlSerializerWriter serializeAnything(XmlSerializerWriter out, Object o, ClassMeta eType, + XmlSerializerContext ctx, String elementName, Namespace elementNamespace, boolean addNamespaceUris, + XmlFormat format, BeanPropertyMeta<?> pMeta) throws IOException, SerializeException { + + BeanContext bc = ctx.getBeanContext(); + String ts = null; // The type string (e.g. <type> or <x x='type'> + int indent = ctx.indent; // Current indentation + ClassMeta<?> aType = null; // The actual type + ClassMeta<?> wType = null; // The wrapped type + ClassMeta<?> gType = object(); // The generic type + + aType = ctx.push(elementName, o, eType); + + if (eType == null) + eType = object(); + + // Handle recursion + if (aType == null) { + o = null; + aType = object(); + } + + if (o != null) { + + if (aType.isDelegate()) { + wType = aType; + aType = ((Delegate)o).getClassMeta(); + } + + gType = aType.getFilteredClassMeta(); + + // Filter if necessary + PojoFilter filter = aType.getPojoFilter(); + if (filter != null) { + o = filter.filter(o); + + // If the filter's getFilteredClass() method returns Object, we need to figure out + // the actual type now. + if (gType.isObject()) + gType = bc.getClassMetaForObject(o); + } + } else { + gType = eType.getFilteredClassMeta(); + } + + String classAttr = null; + if (ctx.isAddClassAttrs()) { + if (o != null && ! eType.equals(aType)) + classAttr = aType.toString(); + else if (o == null) + classAttr = eType.toString(); + } + + // char '\0' is interpreted as null. + if (o != null && gType.isChar() && ((Character)o).charValue() == 0) + o = null; + + boolean isCollapsed = false; // If 'true', this is a collection and we're not rendering the outer element. + + // Get the JSON type string. + if (gType.isCharSequence() || gType.isChar()) + ts = "string"; + else if (gType.isNumber()) + ts = "number"; + else if (gType.isBoolean()) + ts = "boolean"; + else if (gType.isMap() || gType.isBean() || gType.hasToObjectMapMethod()) { + isCollapsed = gType.getXmlMeta().getFormat() == XmlFormat.COLLAPSED; + ts = "object"; + } + else if (gType.isCollection() || gType.isArray()) { + isCollapsed = (format == COLLAPSED && ! addNamespaceUris); + ts = "array"; + } + else + ts = "string"; + + + // Is there a name associated with this bean? + if (elementName == null) + elementName = gType.getXmlMeta().getElementName(); + if (elementName == null) + elementName = aType.getXmlMeta().getElementName(); + + // If the value is null then it's either going to be <null/> or <XmlSerializer nil='true'/> + // depending on whether the element has a name. + boolean isNullTag = (elementName == null && o == null); + + if (isNullTag) + ts = "null"; + + if (ctx.isEnableNamespaces()) { + if (elementNamespace == null) + elementNamespace = gType.getXmlMeta().getNamespace(); + if (elementNamespace == null) + elementNamespace = aType.getXmlMeta().getNamespace(); + if (elementNamespace != null && elementNamespace.uri == null) + elementNamespace = null; + if (elementNamespace == null) + elementNamespace = ctx.getDefaultNamespace(); + } else { + elementNamespace = null; + } + + // Do we need a carriage return after the start tag? + boolean cr = o != null && (gType.isMap() || gType.isCollection() || gType.isArray() || gType.isBean() || gType.hasToObjectMapMethod()); + + String en = (elementName == null ? ts : elementName); + boolean encodeEn = elementName != null; + String ns = (elementNamespace == null ? null : elementNamespace.name); + String xsi = null, dns = null, elementNs = null; + if (ctx.isEnableNamespaces()) { + xsi = ctx.getXsiNamespace().name; + dns = elementName == null && ctx.getDefaultNamespace() != null ? ctx.getDefaultNamespace().name : null; + elementNs = elementName == null ? dns : ns; + if (elementName == null) + elementNamespace = null; + } + + // Render the start tag. + if (! isCollapsed) { + out.oTag(indent, elementNs, en, encodeEn); + if (addNamespaceUris) { + out.attr((String)null, "xmlns", ctx.getDefaultNamespace().getUri()); + + for (Namespace n : ctx.getNamespaces()) + out.attr("xmlns", n.getName(), n.getUri()); + + Namespace xsiNs = ctx.getXsiNamespace(); + if (xsiNs != null) + out.attr("xmlns", xsiNs.name, xsiNs.uri); + } + if (elementName != null && ctx.isAddJsonTypeAttrs() && (ctx.isAddJsonStringTypeAttrs() || ! ts.equals("string"))) + out.attr(dns, "type", ts); + if (classAttr != null) + out.attr(dns, "_class", classAttr); + if (o == null) { + if (! isNullTag) + out.attr(xsi, "nil", "true"); + if ((gType.isBoolean() || gType.isNumber()) && ! gType.isNullable()) + o = gType.getPrimitiveDefault(); + } + + if (o != null && !(gType.isMap() || gType.isBean() || gType.hasToObjectMapMethod())) + out.append('>'); + + if (cr && !(gType.isMap() || gType.isBean() || gType.hasToObjectMapMethod())) + out.nl(); + } + + boolean hasChildren = true; + + // Render the tag contents. + if (o != null) { + if (gType.isUri() || (pMeta != null && pMeta.isUri())) + out.appendUri(o); + else if (gType.isCharSequence() || gType.isChar()) + out.encodeText(o); + else if (gType.isNumber() || gType.isBoolean()) + out.append(o); + else if (gType.isMap() || (wType != null && wType.isMap())) { + if (o instanceof BeanMap) + hasChildren = serializeBeanMap(out, (BeanMap)o, elementNamespace, ctx, isCollapsed); + else + hasChildren = serializeMap(out, (Map)o, gType, ctx); + } + else if (gType.hasToObjectMapMethod()) + hasChildren = serializeMap(out, gType.toObjectMap(o), gType, ctx); + else if (gType.isBean()) + hasChildren = serializeBeanMap(out, bc.forBean(o), elementNamespace, ctx, isCollapsed); + else if (gType.isCollection() || (wType != null && wType.isCollection())) { + if (isCollapsed) + ctx.indent--; + serializeCollection(out, (Collection)o, gType, ctx, pMeta); + if (isCollapsed) + ctx.indent++; + } + else if (gType.isArray()) { + if (isCollapsed) + ctx.indent--; + serializeCollection(out, toList(gType.getInnerClass(), o), gType, ctx, pMeta); + if (isCollapsed) + ctx.indent++; + } + else + out.encodeText(o); + } + + ctx.pop(); + + // Render the end tag. + if (! isCollapsed) { + if (o == null || ! hasChildren) + out.append('/').append('>').nl(); + else + out.i(cr ? indent : 0).eTag(elementNs, en, encodeEn).nl(); + } + + return out; + } + + private boolean serializeMap(XmlSerializerWriter out, Map m, ClassMeta<?> type, XmlSerializerContext ctx) throws IOException, SerializeException { + + m = sort(ctx, m); + + ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); + + boolean hasChildren = false; + for (Iterator i = m.entrySet().iterator(); i.hasNext();) { + Map.Entry e = (Map.Entry)i.next(); + + Object k = e.getKey(); + if (k == null) { + k = "\u0000"; + } else { + k = generalize(ctx, k, keyType); + } + + Object value = e.getValue(); + + if (! hasChildren) { + hasChildren = true; + out.append('>').nl(); + } + serializeAnything(out, value, valueType, ctx, k.toString(), null, false, NORMAL, null); + } + return hasChildren; + } + + private boolean serializeBeanMap(XmlSerializerWriter out, BeanMap m, Namespace elementNs, XmlSerializerContext ctx, boolean isCollapsed) throws IOException, SerializeException { + boolean hasChildren = false; + BeanMeta bm = m.getMeta(); + + Map<String,BeanPropertyMeta> xmlAttrs = bm.getXmlMeta().getXmlAttrProperties(); + Object content = null; + for (BeanPropertyMeta p : xmlAttrs.values()) { + + String key = p.getName(); + Object value = null; + try { + value = p.get(m); + } catch (StackOverflowError e) { + throw e; + } catch (Throwable x) { + ctx.addBeanGetterWarning(p, x); + } + + if (canIgnoreValue(ctx, p.getClassMeta(), key, value)) + continue; + + Namespace ns = (ctx.isEnableNamespaces() && p.getXmlMeta().getNamespace() != elementNs ? p.getXmlMeta().getNamespace() : null); + + if (p.isBeanUri() || p.isUri()) + out.attrUri(ns, key, value); + else + out.attr(ns, key, value); + } + + boolean hasContent = false; + + for (BeanMapEntry p : (Set<BeanMapEntry>)m.entrySet()) { + BeanPropertyMeta pMeta = p.getMeta(); + XmlFormat xf = pMeta.getXmlMeta().getXmlFormat(); + + if (xf == CONTENT) { + content = p.getValue(); + hasContent = true; + } else if (xf == ATTR) { + // Do nothing + } else { + String key = p.getKey(); + Object value = null; + try { + value = p.getValue(); + } catch (StackOverflowError e) { + throw e; + } catch (Throwable x) { + ctx.addWarning("Could not call getValue() on property ''{0}'', {1}", key, x.getLocalizedMessage()); + } + + if (canIgnoreValue(ctx, pMeta.getClassMeta(), key, value)) + continue; + + if (! hasChildren) { + hasChildren = true; + out.appendIf(! isCollapsed, '>').nl(); + } + serializeAnything(out, value, pMeta.getClassMeta(), ctx, key, pMeta.getXmlMeta().getNamespace(), false, pMeta.getXmlMeta().getXmlFormat(), pMeta); + } + } + if ((! hasContent) || canIgnoreValue(ctx, string(), null, content)) + return hasChildren; + out.append(">").cr(ctx.indent); + + // Serialize XML content. + XmlContentHandler h = bm.getXmlMeta().getXmlContentHandler(); + if (h != null) + try { + h.serialize(out, m.getBean()); + } catch (Exception e) { + throw new SerializeException(e); + } + else + out.encodeText(content); + out.nl(); + return true; + } + + private XmlSerializerWriter serializeCollection(XmlSerializerWriter out, Collection c, ClassMeta<?> type, XmlSerializerContext ctx, BeanPropertyMeta<?> ppMeta) throws IOException, SerializeException { + + c = sort(ctx, c); + + ClassMeta<?> elementType = type.getElementType(); + + String eName = null; + Namespace eNs = null; + + if (ppMeta != null) { + eName = ppMeta.getXmlMeta().getChildName(); + eNs = ppMeta.getXmlMeta().getNamespace(); + } + + if (eName == null) { + eName = type.getXmlMeta().getChildName(); + eNs = type.getXmlMeta().getNamespace(); + } + + if (eName == null && ! elementType.isObject()) { + eName = elementType.getXmlMeta().getElementName(); + eNs = elementType.getXmlMeta().getNamespace(); + } + + for (Iterator i = c.iterator(); i.hasNext();) { + Object value = i.next(); + serializeAnything(out, value, elementType, ctx, eName, eNs, false, NORMAL, null); + } + return out; + } + + /** + * Returns the schema serializer based on the settings of this serializer. + * @return The schema serializer. + */ + public XmlSerializer getSchemaSerializer() { + XmlSchemaSerializer s = new XmlSchemaSerializer(); + s.beanContextFactory = this.beanContextFactory; + s.sp = this.sp; + s.xsp = this.xsp; + return s; + } + + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Serializer */ + protected void doSerialize(Object o, Writer out, SerializerContext ctx) throws IOException, SerializeException { + XmlSerializerContext xctx = (XmlSerializerContext)ctx; + if (xctx.isEnableNamespaces() && xctx.isAutoDetectNamespaces()) + findNsfMappings(o, xctx); + serializeAnything(xctx.getWriter(out), o, null, xctx, null, null, xctx.isEnableNamespaces() && xctx.isAddNamespaceUrlsToRoot(), NORMAL, null); + } + + @Override /* Serializer */ + public XmlSerializerContext createContext(ObjectMap properties, Method javaMethod) { + return new XmlSerializerContext(getBeanContext(), sp, xsp, properties, javaMethod); + } + + @Override /* CoreApi */ + public XmlSerializer setProperty(String property, Object value) throws LockedException { + checkLock(); + if (! xsp.setProperty(property, value)) + super.setProperty(property, value); + return this; + } + + @Override /* CoreApi */ + public XmlSerializer setProperties(ObjectMap properties) throws LockedException { + for (Map.Entry<String,Object> e : properties.entrySet()) + setProperty(e.getKey(), e.getValue()); + return this; + } + + @Override /* CoreApi */ + public XmlSerializer addNotBeanClasses(Class<?>...classes) throws LockedException { + super.addNotBeanClasses(classes); + return this; + } + + @Override /* CoreApi */ + public XmlSerializer addFilters(Class<?>...classes) throws LockedException { + super.addFilters(classes); + return this; + } + + @Override /* CoreApi */ + public <T> XmlSerializer addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException { + super.addImplClass(interfaceClass, implClass); + return this; + } + + @Override /* CoreApi */ + public XmlSerializer setClassLoader(ClassLoader classLoader) throws LockedException { + super.setClassLoader(classLoader); + return this; + } + + @Override /* Lockable */ + public XmlSerializer lock() { + super.lock(); + return this; + } + + @Override /* Lockable */ + public XmlSerializer clone() { + try { + XmlSerializer c = (XmlSerializer)super.clone(); + c.xsp = xsp.clone(); + return c; + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); // Shouldn't happen. + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializerContext.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializerContext.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializerContext.class new file mode 100755 index 0000000..16e777a Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializerContext.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializerContext.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializerContext.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializerContext.java new file mode 100755 index 0000000..0ea37fa --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializerContext.java @@ -0,0 +1,202 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.xml; + +import static com.ibm.juno.core.xml.NamespaceFactory.*; +import static com.ibm.juno.core.xml.XmlSerializerProperties.*; + +import java.io.*; +import java.lang.reflect.*; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.serializer.*; +import com.ibm.juno.core.utils.*; + +/** + * Context object that lives for the duration of a single serialization of the {@link XmlSerializer}. + * <p> + * See {@link SerializerContext} for details. + * + * @author James Bognar ([email protected]) + */ +@SuppressWarnings("hiding") +public class XmlSerializerContext extends SerializerContext { + + private final boolean + addJsonTypeAttrs, + addJsonStringTypeAttrs, + autoDetectNamespaces, + enableNamespaces, + addNamespaceUrlsToRoot; + + private Namespace + defaultNamespace, + xsiNamespace, + xsNamespace; + + private Namespace[] namespaces = new Namespace[0]; + + /** + * Constructor. + * @param beanContext The bean context being used by the serializer. + * @param sp Default general serializer properties. + * @param xsp Default XML serializer properties. + * @param op Override properties. + * @param javaMethod Java method that invoked this serializer. + * When using the REST API, this is the Java method invoked by the REST call. + * Can be used to access annotations defined on the method or class. + */ + public XmlSerializerContext(BeanContext beanContext, SerializerProperties sp, XmlSerializerProperties xsp, ObjectMap op, Method javaMethod) { + super(beanContext, sp, op, javaMethod); + if (op == null || op.isEmpty()) { + addJsonTypeAttrs = xsp.addJsonTypeAttrs; + addJsonStringTypeAttrs = xsp.addJsonStringTypeAttrs; + enableNamespaces = xsp.enableNamespaces; + autoDetectNamespaces = xsp.autoDetectNamespaces; + addNamespaceUrlsToRoot = xsp.addNamespaceUrlsToRoot; + addNamespaces(xsp.namespaces); + setDefaultNamespace(xsp.defaultNamespace); + xsiNamespace = xsp.xsiNamespace; + xsNamespace = xsp.xsNamespace; + } else { + addJsonTypeAttrs = op.getBoolean(XML_addJsonTypeAttrs, xsp.addJsonTypeAttrs); + addJsonStringTypeAttrs = op.getBoolean(XML_addJsonStringTypeAttrs, xsp.addJsonStringTypeAttrs); + enableNamespaces = op.getBoolean(XML_enableNamespaces, xsp.enableNamespaces); + autoDetectNamespaces = op.getBoolean(XML_autoDetectNamespaces, xsp.autoDetectNamespaces); + addNamespaceUrlsToRoot = op.getBoolean(XML_addNamespaceUrisToRoot, xsp.addNamespaceUrlsToRoot); + namespaces = (op.containsKey(XML_namespaces) ? parseNamespaces(op.get(XML_namespaces)) : xsp.namespaces); + setDefaultNamespace(op.containsKey(XML_defaultNamespaceUri) ? op.getString(XML_defaultNamespaceUri) : xsp.defaultNamespace); + xsiNamespace = (op.containsKey(XML_xsiNamespace) ? parseNamespace(op.get(XML_xsiNamespace)) : xsp.xsiNamespace); + xsNamespace = (op.containsKey(XML_xsNamespace) ? parseNamespace(op.get(XML_xsNamespace)) : xsp.xsNamespace); + } + } + + private void setDefaultNamespace(String s) { + if (s == null) + return; + if (StringUtils.startsWith(s, '{')) + defaultNamespace = parseNamespace(s); + else if (! s.startsWith("http://")) + defaultNamespace = get(s, "http://unknown"); + else + defaultNamespace = get(null, s); + } + + private void addNamespaces(Namespace...namespaces) { + for (Namespace ns : namespaces) + addNamespace(ns); + } + + void addNamespace(Namespace ns) { + if (ns == defaultNamespace) + return; + + for (Namespace n : namespaces) + if (n == ns) + return; + + if (defaultNamespace != null && (ns.uri.equals(defaultNamespace.uri) || ns.name.equals(defaultNamespace.name))) + defaultNamespace = ns; + else + namespaces = ArrayUtils.append(namespaces, ns); + } + + /** + * Returns the list of namespaces being used in the current XML serialization. + * + * @return The list of namespaces being used in the current XML serialization. + */ + public Namespace[] getNamespaces() { + return namespaces; + } + + /** + * Returns the {@link XmlSerializerProperties#XML_addJsonTypeAttrs} setting value in this context. + * + * @return The {@link XmlSerializerProperties#XML_addJsonTypeAttrs} setting value in this context. + */ + public final boolean isAddJsonTypeAttrs() { + return addJsonTypeAttrs; + } + + /** + * Returns the {@link XmlSerializerProperties#XML_addJsonStringTypeAttrs} setting value in this context. + * + * @return The {@link XmlSerializerProperties#XML_addJsonStringTypeAttrs} setting value in this context. + */ + public final boolean isAddJsonStringTypeAttrs() { + return addJsonStringTypeAttrs; + } + + /** + * Returns the {@link XmlSerializerProperties#XML_autoDetectNamespaces} setting value in this context. + * + * @return The {@link XmlSerializerProperties#XML_autoDetectNamespaces} setting value in this context. + */ + public final boolean isAutoDetectNamespaces() { + return enableNamespaces && autoDetectNamespaces; + } + + /** + * Returns the {@link XmlSerializerProperties#XML_enableNamespaces} setting value in this context. + * + * @return The {@link XmlSerializerProperties#XML_enableNamespaces} setting value in this context. + */ + public final boolean isEnableNamespaces() { + return enableNamespaces; + } + + /** + * Returns the {@link XmlSerializerProperties#XML_addNamespaceUrisToRoot} setting value in this context. + * + * @return The {@link XmlSerializerProperties#XML_addNamespaceUrisToRoot} setting value in this context. + */ + public final boolean isAddNamespaceUrlsToRoot() { + return addNamespaceUrlsToRoot; + } + + /** + * Returns the {@link XmlSerializerProperties#XML_defaultNamespaceUri} setting value in this context. + * + * @return The {@link XmlSerializerProperties#XML_defaultNamespaceUri} setting value in this context. + */ + public final Namespace getDefaultNamespace() { + return defaultNamespace; + } + + /** + * Returns the {@link XmlSerializerProperties#XML_xsiNamespace} setting value in this context. + * + * @return The {@link XmlSerializerProperties#XML_xsiNamespace} setting value in this context. + */ + public final Namespace getXsiNamespace() { + return xsiNamespace; + } + + /** + * Returns the {@link XmlSerializerProperties#XML_xsNamespace} setting value in this context. + * + * @return The {@link XmlSerializerProperties#XML_xsNamespace} setting value in this context. + */ + public final Namespace getXsNamespace() { + return xsNamespace; + } + + /** + * Wraps the specified writer in a {@link XmlSerializerWriter} if it is not already an instance of that class. + * + * @param out The writer being wrapped. + * @return The wrapped writer. + */ + public XmlSerializerWriter getWriter(Writer out) { + if (out instanceof XmlSerializerWriter) + return (XmlSerializerWriter)out; + return new XmlSerializerWriter(out, isUseIndentation(), getQuoteChar(), getRelativeUriBase(), getAbsolutePathUriBase(), isEnableNamespaces(), getDefaultNamespace()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializerProperties.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializerProperties.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializerProperties.class new file mode 100755 index 0000000..1fa4859 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/xml/XmlSerializerProperties.class differ
