http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaDocSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaDocSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaDocSerializerSession.java new file mode 100644 index 0000000..23429a4 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaDocSerializerSession.java @@ -0,0 +1,53 @@ +// *************************************************************************************************************************** +// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * +// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * +// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * +// * with the License. You may obtain a copy of the License at * +// * * +// * http://www.apache.org/licenses/LICENSE-2.0 * +// * * +// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * +// * specific language governing permissions and limitations under the License. * +// *************************************************************************************************************************** +package org.apache.juneau.xml; + +import org.apache.juneau.serializer.*; + +/** + * Session object that lives for the duration of a single use of {@link XmlSchemaDocSerializer}. + * + * <p> + * This class is NOT thread safe. + * It is typically discarded after one-time use although it can be reused within the same thread. + */ +public class XmlSchemaDocSerializerSession extends XmlSchemaSerializerSession { + + /** + * Create a new session using properties specified in the context. + * + * @param ctx + * The context creating this session object. + * The context contains all the configuration settings for this object. + * @param args + * Runtime arguments. + * These specify session-level information such as locale and URI context. + * It also include session-level properties that override the properties defined on the bean and + * serializer contexts. + * <br>If <jk>null</jk>, defaults to {@link SerializerSessionArgs#DEFAULT}. + */ + protected XmlSchemaDocSerializerSession(XmlSerializerContext ctx, SerializerSessionArgs args) { + super(ctx, args); + } + + @Override /* SerializerSession */ + protected void doSerialize(SerializerPipe out, Object o) throws Exception { + XmlWriter w = getXmlWriter(out); + w.append("<?xml") + .attr("version", "1.0") + .attr("encoding", "UTF-8") + .appendln("?>"); + w.flush(); + super.doSerialize(out, o); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializer.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializer.java index c1af712..26ba629 100644 --- a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializer.java @@ -12,26 +12,9 @@ // *************************************************************************************************************************** package org.apache.juneau.xml; -import static org.apache.juneau.xml.annotation.XmlFormat.*; -import static org.apache.juneau.internal.ArrayUtils.*; - - -import java.io.*; -import java.lang.reflect.*; -import java.util.*; -import java.util.regex.*; - -import javax.xml.*; -import javax.xml.transform.stream.*; -import javax.xml.validation.*; - import org.apache.juneau.*; import org.apache.juneau.annotation.*; -import org.apache.juneau.http.*; import org.apache.juneau.serializer.*; -import org.apache.juneau.xml.annotation.*; -import org.w3c.dom.bootstrap.*; -import org.w3c.dom.ls.*; /** * Serializes POJO metadata to HTTP responses as XML. @@ -58,540 +41,17 @@ import org.w3c.dom.ls.*; @Produces(value="text/xml+schema",contentType="text/xml") public class XmlSchemaSerializer extends XmlSerializer { - private final XmlSerializerContext ctx; - /** * Constructor. * * @param propertyStore Initialize with the specified config property store. */ public XmlSchemaSerializer(PropertyStore propertyStore) { - this(propertyStore, null); - } - - /** - * Constructor - * - * @param propertyStore The property store containing all the settings for this object. - * @param overrideProperties A set of overridden settings, typically defined by the class itself. - */ - public XmlSchemaSerializer(PropertyStore propertyStore, Map<String,Object> overrideProperties) { - super(propertyStore); - this.ctx = this.propertyStore.create(overrideProperties).getContext(XmlSerializerContext.class); - } - - @Override /* XmlSerializer */ - protected void doSerialize(SerializerSession session, SerializerOutput out, Object o) throws Exception { - XmlSerializerSession s = (XmlSerializerSession)session; - - if (s.isEnableNamespaces() && s.isAutoDetectNamespaces()) - findNsfMappings(s, o); - - Namespace xs = s.getXsNamespace(); - Namespace[] allNs = append(new Namespace[]{s.getDefaultNamespace()}, s.getNamespaces()); - - Schemas schemas = new Schemas(s, xs, s.getDefaultNamespace(), allNs); - schemas.process(s, o); - schemas.serializeTo(out.getWriter()); - } - - /** - * Returns an XML-Schema validator based on the output returned by {@link #doSerialize(SerializerSession, SerializerOutput, Object)}; - * - * @param session - * The serializer session object return by {@link #createSession(ObjectMap, Method, Locale, TimeZone, - * MediaType, UriContext)}. - * Can be <jk>null</jk>. - * @param out The target writer. - * @param o The object to serialize. - * @return The new validator. - * @throws Exception If a problem was detected in the XML-Schema output produced by this serializer. - */ - public Validator getValidator(SerializerSession session, SerializerOutput out, Object o) throws Exception { - doSerialize(session, out, o); - String xmlSchema = out.getWriter().toString(); - - // 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 FormattedRuntimeException("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(XmlSerializerSession session, Namespace xs, Namespace defaultNs, Namespace[] allNs) throws IOException { - this.defaultNs = defaultNs; - for (Namespace ns : allNs) - put(ns, new Schema(session, this, xs, ns, defaultNs, allNs)); - } - - private Schema getSchema(Namespace ns) { - if (ns == null) - ns = defaultNs; - Schema s = get(ns); - if (s == null) - throw new FormattedRuntimeException("No schema defined for namespace ''{0}''", ns); - return s; - } - - private void process(SerializerSession session, Object o) throws IOException { - ClassMeta<?> cm = session.getClassMetaForObject(o); - Namespace ns = defaultNs; - if (cm == null) - queueElement(ns, "null", object()); - else { - XmlClassMeta xmlMeta = cm.getExtendedMeta(XmlClassMeta.class); - if (cm.getDictionaryName() != null && xmlMeta.getNamespace() != null) - ns = xmlMeta.getNamespace(); - queueElement(ns, cm.getDictionaryName(), 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); - 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 XmlWriter w; - private XmlSerializerSession session; - private Namespace defaultNs, targetNs; - private Schemas schemas; - private Set<String> - processedTypes = new HashSet<String>(), - processedAttributes = new HashSet<String>(), - processedElements = new HashSet<String>(); - - public Schema(XmlSerializerSession session, Schemas schemas, Namespace xs, Namespace targetNs, Namespace defaultNs, Namespace[] allNs) throws IOException { - this.schemas = schemas; - this.defaultNs = defaultNs; - this.targetNs = targetNs; - this.session = session; - w = new XmlWriter(sw, session.isUseWhitespace(), session.getMaxIndent(), session.isTrimStrings(), session.getQuoteChar(), null, true, null); - int i = session.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(i); - for (Namespace ns : allNs) { - if (ns != targetNs) { - w.oTag(i+1, "import") - .attr("namespace", ns.getUri()) - .attr("schemaLocation", ns.getName()+".xsd") - .append("/>").nl(i+1); - } - } - } - - private boolean processElement(String name, ClassMeta<?> cm) throws IOException { - if (processedElements.contains(name)) - return false; - processedElements.add(name); - - ClassMeta<?> ft = cm.getSerializedClassMeta(); - int i = session.getIndent() + 1; - if (name == null) - name = getElementName(ft); - Namespace ns = first(ft.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs); - String type = getXmlType(ns, ft); - - w.oTag(i, "element") - .attr("name", XmlUtils.encodeElementName(name)) - .attr("type", type) - .append('/').append('>').nl(i); - - 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 = session.getIndent() + 1; - String type = getXmlAttrType(cm); - - w.oTag(i, "attribute") - .attr("name", name) - .attr("type", type) - .append('/').append('>').nl(i); - - return true; - } - - private boolean processType(String name, ClassMeta<?> cm) throws IOException { - if (processedTypes.contains(name)) - return false; - processedTypes.add(name); - - int i = session.getIndent() + 1; - - cm = cm.getSerializedClassMeta(); - XmlBeanMeta xbm = cm.isBean() ? cm.getBeanMeta().getExtendedMeta(XmlBeanMeta.class) : null; - - 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 ((xbm != null && (xbm.getContentFormat() != null && xbm.getContentFormat().isOneOf(TEXT,TEXT_PWS,MIXED,MIXED_PWS,XMLTEXT))) || ! cm.isMapOrBean()) - w.attr("mixed", "true"); - - w.cTag().nl(i); - - if (! (cm.isMapOrBean() || cm.isCollectionOrArray() || (cm.isAbstract() && ! cm.isNumber()) || cm.isObject())) { - w.oTag(i+1, "attribute").attr("name", session.getBeanTypePropertyName(cm)).attr("type", "string").ceTag().nl(i+1); - - } else { - - //----- Bean ----- - if (cm.isBean()) { - BeanMeta<?> bm = cm.getBeanMeta(); - - boolean hasChildElements = false; - - for (BeanPropertyMeta pMeta : bm.getPropertyMetas()) { - XmlFormat pMetaFormat = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getXmlFormat(); - if (pMetaFormat != XmlFormat.ATTR) - hasChildElements = true; - } - - XmlBeanMeta xbm2 = bm.getExtendedMeta(XmlBeanMeta.class); - if (xbm2.getContentProperty() != null && xbm2.getContentFormat() == ELEMENTS) { - w.sTag(i+1, "sequence").nl(i+1); - w.oTag(i+2, "any") - .attr("processContents", "skip") - .attr("minOccurs", 0) - .ceTag().nl(i+2); - w.eTag(i+1, "sequence").nl(i+1); - - } else if (hasChildElements) { - - boolean hasOtherNsElement = false; - boolean hasCollapsed = false; - - for (BeanPropertyMeta pMeta : bm.getPropertyMetas()) { - XmlBeanPropertyMeta xmlMeta = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class); - if (xmlMeta.getXmlFormat() != ATTR) { - if (xmlMeta.getNamespace() != null) { - ClassMeta<?> ct2 = pMeta.getClassMeta(); - Namespace cNs = first(xmlMeta.getNamespace(), ct2.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs); - // Child element is in another namespace. - schemas.queueElement(cNs, pMeta.getName(), ct2); - hasOtherNsElement = true; - } - if (xmlMeta.getXmlFormat() == COLLAPSED) - hasCollapsed = true; - } - } - - if (hasOtherNsElement || hasCollapsed) { - // If this bean has any child elements in another namespace, - // we need to add an <any> element. - w.oTag(i+1, "choice").attr("maxOccurs", "unbounded").cTag().nl(i+1); - w.oTag(i+2, "any") - .attr("processContents", "skip") - .attr("minOccurs", 0) - .ceTag().nl(i+2); - w.eTag(i+1, "choice").nl(i+1); - - } else { - w.sTag(i+1, "all").nl(i+1); - for (BeanPropertyMeta pMeta : bm.getPropertyMetas()) { - XmlBeanPropertyMeta xmlMeta = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class); - if (xmlMeta.getXmlFormat() != 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.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs); - if (xmlMeta.getNamespace() == null) { - w.oTag(i+2, "element") - .attr("name", XmlUtils.encodeElementName(childName), false) - .attr("type", getXmlType(cNs, ct2)) - .attr("minOccurs", 0); - - w.ceTag().nl(i+2); - } else { - // Child element is in another namespace. - schemas.queueElement(cNs, pMeta.getName(), ct2); - hasOtherNsElement = true; - } - - } - } - w.eTag(i+1, "all").nl(i+1); - } - - } - - for (BeanPropertyMeta pMeta : bm.getExtendedMeta(XmlBeanMeta.class).getAttrProperties().values()) { - Namespace pNs = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).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(i+1); - } - - // 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(i+1); - } - } - - //----- Collection ----- - } else if (cm.isCollectionOrArray()) { - ClassMeta<?> elementType = cm.getElementType(); - if (elementType.isObject()) { - w.sTag(i+1, "sequence").nl(i+1); - w.oTag(i+2, "any") - .attr("processContents", "skip") - .attr("maxOccurs", "unbounded") - .attr("minOccurs", "0") - .ceTag().nl(i+2); - w.eTag(i+1, "sequence").nl(i+1); - } else { - Namespace cNs = first(elementType.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs); - schemas.queueType(cNs, null, elementType); - w.sTag(i+1, "sequence").nl(i+1); - w.oTag(i+2, "any") - .attr("processContents", "skip") - .attr("maxOccurs", "unbounded") - .attr("minOccurs", "0") - .ceTag().nl(i+2); - w.eTag(i+1, "sequence").nl(i+1); - } - - //----- Map ----- - } else if (cm.isMap() || cm.isAbstract() || cm.isObject()) { - w.sTag(i+1, "sequence").nl(i+1); - w.oTag(i+2, "any") - .attr("processContents", "skip") - .attr("maxOccurs", "unbounded") - .attr("minOccurs", "0") - .ceTag().nl(i+2); - w.eTag(i+1, "sequence").nl(i+1); - } - - w.oTag(i+1, "attribute") - .attr("name", session.getBeanTypePropertyName(null)) - .attr("type", "string") - .ceTag().nl(i+1); - } - - w.eTag(i, "complexType").nl(i); - schemas.processQueue(); - - return true; - } - - private String getElementName(ClassMeta<?> cm) { - cm = cm.getSerializedClassMeta(); - String name = cm.getDictionaryName(); - - if (name == null) { - if (cm.isBoolean()) - name = "boolean"; - else if (cm.isNumber()) - name = "number"; - else if (cm.isCollectionOrArray()) - name = "array"; - else if (! (cm.isMapOrBean() || cm.isCollectionOrArray() || cm.isObject() || cm.isAbstract())) - name = "string"; - else - name = "object"; - } - return name; - } - - @Override /* Object */ - public String toString() { - try { - int i = session.getIndent(); - w.eTag(i, "schema").nl(i); - } 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.getSerializedClassMeta(); - if (currentNs == targetNs) { - if (cm.isPrimitive()) { - if (cm.isBoolean()) - name = "boolean"; - else if (cm.isNumber()) { - if (cm.isDecimal()) - name = "decimal"; - else - name = "integer"; - } - } - } - if (name == null) { - name = XmlUtils.encodeElementName(cm); - schemas.queueType(currentNs, name, cm); - return currentNs.getName() + ":" + name; - } - - return name; - } - } - - private static <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"; + super(propertyStore.copy().append(XmlSerializerContext.XML_enableNamespaces, true)); } @Override /* Serializer */ - public XmlSerializerSession createSession(ObjectMap op, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType, UriContext uriContext) { - // This serializer must always have namespaces enabled. - if (op == null) - op = new ObjectMap(); - op.put(XmlSerializerContext.XML_enableNamespaces, true); - return new XmlSerializerSession(ctx, op, javaMethod, locale, timeZone, mediaType, uriContext); + public WriterSerializerSession createSession(SerializerSessionArgs args) { + return new XmlSchemaSerializerSession(ctx, args); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializerSession.java new file mode 100644 index 0000000..f3b9120 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializerSession.java @@ -0,0 +1,551 @@ +// *************************************************************************************************************************** +// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * +// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * +// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * +// * with the License. You may obtain a copy of the License at * +// * * +// * http://www.apache.org/licenses/LICENSE-2.0 * +// * * +// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * +// * specific language governing permissions and limitations under the License. * +// *************************************************************************************************************************** +package org.apache.juneau.xml; + +import static org.apache.juneau.internal.ArrayUtils.*; +import static org.apache.juneau.xml.annotation.XmlFormat.*; + +import java.io.*; +import java.util.*; +import java.util.regex.*; + +import javax.xml.*; +import javax.xml.transform.stream.*; +import javax.xml.validation.*; + +import org.apache.juneau.*; +import org.apache.juneau.serializer.*; +import org.apache.juneau.xml.annotation.*; +import org.w3c.dom.bootstrap.*; +import org.w3c.dom.ls.*; + +/** + * Session object that lives for the duration of a single use of {@link XmlSchemaSerializer}. + * + * <p> + * This class is NOT thread safe. + * It is typically discarded after one-time use although it can be reused within the same thread. + */ +public class XmlSchemaSerializerSession extends XmlSerializerSession { + + /** + * Create a new session using properties specified in the context. + * + * @param ctx + * The context creating this session object. + * The context contains all the configuration settings for this object. + * @param args + * Runtime arguments. + * These specify session-level information such as locale and URI context. + * It also include session-level properties that override the properties defined on the bean and + * serializer contexts. + * <br>If <jk>null</jk>, defaults to {@link SerializerSessionArgs#DEFAULT}. + */ + protected XmlSchemaSerializerSession(XmlSerializerContext ctx, SerializerSessionArgs args) { + super(ctx, args); + } + + @Override /* SerializerSession */ + protected void doSerialize(SerializerPipe out, Object o) throws Exception { + if (enableNamespaces && autoDetectNamespaces) + findNsfMappings(o); + + Namespace xs = xsNamespace; + Namespace[] allNs = append(new Namespace[]{defaultNamespace}, namespaces); + + Schemas schemas = new Schemas(xs, defaultNamespace, allNs); + schemas.process(o); + schemas.serializeTo(out.getWriter()); + } + + /** + * Returns an XML-Schema validator based on the output returned by {@link #doSerialize(SerializerPipe, Object)}; + * + * @param out The target writer. + * @param o The object to serialize. + * @return The new validator. + * @throws Exception If a problem was detected in the XML-Schema output produced by this serializer. + */ + public Validator getValidator(SerializerPipe out, Object o) throws Exception { + doSerialize(out, o); + String xmlSchema = out.getWriter().toString(); + + // 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 FormattedRuntimeException("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) throws IOException { + this.defaultNs = defaultNs; + for (Namespace ns : allNs) + put(ns, new Schema(this, xs, ns, defaultNs, allNs)); + } + + private Schema getSchema(Namespace ns) { + if (ns == null) + ns = defaultNs; + Schema s = get(ns); + if (s == null) + throw new FormattedRuntimeException("No schema defined for namespace ''{0}''", ns); + return s; + } + + private void process(Object o) throws IOException { + ClassMeta<?> cm = getClassMetaForObject(o); + Namespace ns = defaultNs; + if (cm == null) + queueElement(ns, "null", object()); + else { + XmlClassMeta xmlMeta = cm.getExtendedMeta(XmlClassMeta.class); + if (cm.getDictionaryName() != null && xmlMeta.getNamespace() != null) + ns = xmlMeta.getNamespace(); + queueElement(ns, cm.getDictionaryName(), 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); + 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 XmlWriter w; + 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) throws IOException { + this.schemas = schemas; + this.defaultNs = defaultNs; + this.targetNs = targetNs; + w = new XmlWriter(sw, isUseWhitespace(), getMaxIndent(), isTrimStrings(), getQuoteChar(), null, true, null); + int i = indent; + 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(i); + for (Namespace ns : allNs) { + if (ns != targetNs) { + w.oTag(i+1, "import") + .attr("namespace", ns.getUri()) + .attr("schemaLocation", ns.getName()+".xsd") + .append("/>").nl(i+1); + } + } + } + + private boolean processElement(String name, ClassMeta<?> cm) throws IOException { + if (processedElements.contains(name)) + return false; + processedElements.add(name); + + ClassMeta<?> ft = cm.getSerializedClassMeta(); + if (name == null) + name = getElementName(ft); + Namespace ns = first(ft.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs); + String type = getXmlType(ns, ft); + + w.oTag(indent+1, "element") + .attr("name", XmlUtils.encodeElementName(name)) + .attr("type", type) + .append('/').append('>').nl(indent+1); + + 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); + + String type = getXmlAttrType(cm); + + w.oTag(indent+1, "attribute") + .attr("name", name) + .attr("type", type) + .append('/').append('>').nl(indent+1); + + return true; + } + + private boolean processType(String name, ClassMeta<?> cm) throws IOException { + if (processedTypes.contains(name)) + return false; + processedTypes.add(name); + + int i = indent + 1; + + cm = cm.getSerializedClassMeta(); + XmlBeanMeta xbm = cm.isBean() ? cm.getBeanMeta().getExtendedMeta(XmlBeanMeta.class) : null; + + 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 ((xbm != null && (xbm.getContentFormat() != null && xbm.getContentFormat().isOneOf(TEXT,TEXT_PWS,MIXED,MIXED_PWS,XMLTEXT))) || ! cm.isMapOrBean()) + w.attr("mixed", "true"); + + w.cTag().nl(i); + + if (! (cm.isMapOrBean() || cm.isCollectionOrArray() || (cm.isAbstract() && ! cm.isNumber()) || cm.isObject())) { + w.oTag(i+1, "attribute").attr("name", getBeanTypePropertyName(cm)).attr("type", "string").ceTag().nl(i+1); + + } else { + + //----- Bean ----- + if (cm.isBean()) { + BeanMeta<?> bm = cm.getBeanMeta(); + + boolean hasChildElements = false; + + for (BeanPropertyMeta pMeta : bm.getPropertyMetas()) { + XmlFormat pMetaFormat = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getXmlFormat(); + if (pMetaFormat != XmlFormat.ATTR) + hasChildElements = true; + } + + XmlBeanMeta xbm2 = bm.getExtendedMeta(XmlBeanMeta.class); + if (xbm2.getContentProperty() != null && xbm2.getContentFormat() == ELEMENTS) { + w.sTag(i+1, "sequence").nl(i+1); + w.oTag(i+2, "any") + .attr("processContents", "skip") + .attr("minOccurs", 0) + .ceTag().nl(i+2); + w.eTag(i+1, "sequence").nl(i+1); + + } else if (hasChildElements) { + + boolean hasOtherNsElement = false; + boolean hasCollapsed = false; + + for (BeanPropertyMeta pMeta : bm.getPropertyMetas()) { + XmlBeanPropertyMeta xmlMeta = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class); + if (xmlMeta.getXmlFormat() != ATTR) { + if (xmlMeta.getNamespace() != null) { + ClassMeta<?> ct2 = pMeta.getClassMeta(); + Namespace cNs = first(xmlMeta.getNamespace(), ct2.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs); + // Child element is in another namespace. + schemas.queueElement(cNs, pMeta.getName(), ct2); + hasOtherNsElement = true; + } + if (xmlMeta.getXmlFormat() == COLLAPSED) + hasCollapsed = true; + } + } + + if (hasOtherNsElement || hasCollapsed) { + // If this bean has any child elements in another namespace, + // we need to add an <any> element. + w.oTag(i+1, "choice").attr("maxOccurs", "unbounded").cTag().nl(i+1); + w.oTag(i+2, "any") + .attr("processContents", "skip") + .attr("minOccurs", 0) + .ceTag().nl(i+2); + w.eTag(i+1, "choice").nl(i+1); + + } else { + w.sTag(i+1, "all").nl(i+1); + for (BeanPropertyMeta pMeta : bm.getPropertyMetas()) { + XmlBeanPropertyMeta xmlMeta = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class); + if (xmlMeta.getXmlFormat() != 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.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs); + if (xmlMeta.getNamespace() == null) { + w.oTag(i+2, "element") + .attr("name", XmlUtils.encodeElementName(childName), false) + .attr("type", getXmlType(cNs, ct2)) + .attr("minOccurs", 0); + + w.ceTag().nl(i+2); + } else { + // Child element is in another namespace. + schemas.queueElement(cNs, pMeta.getName(), ct2); + hasOtherNsElement = true; + } + + } + } + w.eTag(i+1, "all").nl(i+1); + } + + } + + for (BeanPropertyMeta pMeta : bm.getExtendedMeta(XmlBeanMeta.class).getAttrProperties().values()) { + Namespace pNs = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).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(i+1); + } + + // 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(i+1); + } + } + + //----- Collection ----- + } else if (cm.isCollectionOrArray()) { + ClassMeta<?> elementType = cm.getElementType(); + if (elementType.isObject()) { + w.sTag(i+1, "sequence").nl(i+1); + w.oTag(i+2, "any") + .attr("processContents", "skip") + .attr("maxOccurs", "unbounded") + .attr("minOccurs", "0") + .ceTag().nl(i+2); + w.eTag(i+1, "sequence").nl(i+1); + } else { + Namespace cNs = first(elementType.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs); + schemas.queueType(cNs, null, elementType); + w.sTag(i+1, "sequence").nl(i+1); + w.oTag(i+2, "any") + .attr("processContents", "skip") + .attr("maxOccurs", "unbounded") + .attr("minOccurs", "0") + .ceTag().nl(i+2); + w.eTag(i+1, "sequence").nl(i+1); + } + + //----- Map ----- + } else if (cm.isMap() || cm.isAbstract() || cm.isObject()) { + w.sTag(i+1, "sequence").nl(i+1); + w.oTag(i+2, "any") + .attr("processContents", "skip") + .attr("maxOccurs", "unbounded") + .attr("minOccurs", "0") + .ceTag().nl(i+2); + w.eTag(i+1, "sequence").nl(i+1); + } + + w.oTag(i+1, "attribute") + .attr("name", getBeanTypePropertyName(null)) + .attr("type", "string") + .ceTag().nl(i+1); + } + + w.eTag(i, "complexType").nl(i); + schemas.processQueue(); + + return true; + } + + private String getElementName(ClassMeta<?> cm) { + cm = cm.getSerializedClassMeta(); + String name = cm.getDictionaryName(); + + if (name == null) { + if (cm.isBoolean()) + name = "boolean"; + else if (cm.isNumber()) + name = "number"; + else if (cm.isCollectionOrArray()) + name = "array"; + else if (! (cm.isMapOrBean() || cm.isCollectionOrArray() || cm.isObject() || cm.isAbstract())) + name = "string"; + else + name = "object"; + } + return name; + } + + @Override /* Object */ + public String toString() { + try { + w.eTag(indent, "schema").nl(indent); + } 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.getSerializedClassMeta(); + if (currentNs == targetNs) { + if (cm.isPrimitive()) { + if (cm.isBoolean()) + name = "boolean"; + else if (cm.isNumber()) { + if (cm.isDecimal()) + name = "decimal"; + else + name = "integer"; + } + } + } + if (name == null) { + name = XmlUtils.encodeElementName(cm); + schemas.queueType(currentNs, name, cm); + return currentNs.getName() + ":" + name; + } + + return name; + } + } + + private static <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"; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializer.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializer.java index b50dfae..e836a67 100644 --- a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializer.java @@ -13,21 +13,12 @@ package org.apache.juneau.xml; import static org.apache.juneau.serializer.SerializerContext.*; -import static org.apache.juneau.xml.XmlSerializer.ContentResult.*; -import static org.apache.juneau.xml.XmlSerializer.JsonType.*; import static org.apache.juneau.xml.XmlSerializerContext.*; -import static org.apache.juneau.xml.annotation.XmlFormat.*; - -import java.lang.reflect.*; -import java.util.*; import org.apache.juneau.*; import org.apache.juneau.annotation.*; -import org.apache.juneau.http.*; import org.apache.juneau.json.*; import org.apache.juneau.serializer.*; -import org.apache.juneau.transform.*; -import org.apache.juneau.xml.annotation.*; /** * Serializes POJO models to XML. @@ -130,7 +121,6 @@ import org.apache.juneau.xml.annotation.*; * <li>{@link SqReadable} - Default serializer, single quotes, whitespace added. * </ul> */ -@SuppressWarnings({ "rawtypes", "unchecked" }) @Produces("text/xml") public class XmlSerializer extends WriterSerializer { @@ -162,12 +152,7 @@ public class XmlSerializer extends WriterSerializer { * @param propertyStore The property store containing all the settings for this object. */ public Sq(PropertyStore propertyStore) { - super(propertyStore); - } - - @Override /* CoreObject */ - protected ObjectMap getOverrideProperties() { - return super.getOverrideProperties().append(SERIALIZER_quoteChar, '\''); + super(propertyStore.copy().append(SERIALIZER_quoteChar, '\'')); } } @@ -180,12 +165,7 @@ public class XmlSerializer extends WriterSerializer { * @param propertyStore The property store containing all the settings for this object. */ public SqReadable(PropertyStore propertyStore) { - super(propertyStore); - } - - @Override /* CoreObject */ - protected ObjectMap getOverrideProperties() { - return super.getOverrideProperties().append(SERIALIZER_quoteChar, '\'').append(SERIALIZER_useWhitespace, true); + super(propertyStore.copy().append(SERIALIZER_quoteChar, '\'').append(SERIALIZER_useWhitespace, true)); } } @@ -199,12 +179,7 @@ public class XmlSerializer extends WriterSerializer { * @param propertyStore The property store containing all the settings for this object. */ public Ns(PropertyStore propertyStore) { - super(propertyStore); - } - - @Override /* CoreObject */ - protected ObjectMap getOverrideProperties() { - return super.getOverrideProperties().append(XML_enableNamespaces, true); + super(propertyStore.copy().append(XML_enableNamespaces, true)); } } @@ -217,12 +192,7 @@ public class XmlSerializer extends WriterSerializer { * @param propertyStore The property store containing all the settings for this object. */ public NsSq(PropertyStore propertyStore) { - super(propertyStore); - } - - @Override /* CoreObject */ - protected ObjectMap getOverrideProperties() { - return super.getOverrideProperties().append(XML_enableNamespaces, true).append(SERIALIZER_quoteChar, '\''); + super(propertyStore.copy().append(XML_enableNamespaces, true).append(SERIALIZER_quoteChar, '\'')); } } @@ -235,18 +205,13 @@ public class XmlSerializer extends WriterSerializer { * @param propertyStore The property store containing all the settings for this object. */ public NsSqReadable(PropertyStore propertyStore) { - super(propertyStore); - } - - @Override /* CoreObject */ - protected ObjectMap getOverrideProperties() { - return super.getOverrideProperties().append(XML_enableNamespaces, true).append(SERIALIZER_quoteChar, '\'') - .append(SERIALIZER_useWhitespace, true); + super(propertyStore.copy().append(XML_enableNamespaces, true).append(SERIALIZER_quoteChar, '\'') + .append(SERIALIZER_useWhitespace, true)); } } - private final XmlSerializerContext ctx; + final XmlSerializerContext ctx; private volatile XmlSchemaSerializer schemaSerializer; /** @@ -264,536 +229,6 @@ public class XmlSerializer extends WriterSerializer { return new XmlSerializerBuilder(propertyStore); } - /** - * Recursively searches for the XML namespaces on the specified POJO and adds them to the serializer context object. - * - * @param session The context that exists for the duration of a single serialization. - * @param o The POJO to check. - * @throws SerializeException - */ - protected void findNsfMappings(XmlSerializerSession session, Object o) throws SerializeException { - ClassMeta<?> aType = null; // The actual type - aType = session.push(null, o, null); - - if (aType != null) { - Namespace ns = aType.getExtendedMeta(XmlClassMeta.class).getNamespace(); - if (ns != null) { - if (ns.uri != null) - session.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 = session.toBeanMap(o); - } else if (aType.isDelegate()) { - ClassMeta<?> innerType = ((Delegate)o).getClassMeta(); - Namespace ns = innerType.getExtendedMeta(XmlClassMeta.class).getNamespace(); - if (ns != null) { - if (ns.uri != null) - session.addNamespace(ns); - else - ns = null; - } - - if (innerType.isBean()) { - for (BeanPropertyMeta bpm : innerType.getBeanMeta().getPropertyMetas()) { - ns = bpm.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace(); - if (ns != null && ns.uri != null) - session.addNamespace(ns); - } - - } else if (innerType.isMap()) { - for (Object o2 : ((Map)o).values()) - findNsfMappings(session, o2); - } else if (innerType.isCollection()) { - for (Object o2 : ((Collection)o)) - findNsfMappings(session, o2); - } - - } else if (aType.isMap()) { - for (Object o2 : ((Map)o).values()) - findNsfMappings(session, o2); - } else if (aType.isCollection()) { - for (Object o2 : ((Collection)o)) - findNsfMappings(session, o2); - } else if (aType.isArray() && ! aType.getElementType().isPrimitive()) { - for (Object o2 : ((Object[])o)) - findNsfMappings(session, o2); - } - if (bm != null) { - for (BeanPropertyValue p : bm.getValues(session.isTrimNulls())) { - - Namespace ns = p.getMeta().getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace(); - if (ns != null && ns.uri != null) - session.addNamespace(ns); - - try { - findNsfMappings(session, p.getValue()); - } catch (Throwable x) { - // Ignore - } - } - } - } - - session.pop(); - } - - /** - * Workhorse method. - * - * @param session The serializer context. - * @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 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 isMixed We're serializing mixed content, so don't use whitespace. - * @param preserveWhitespace - * <jk>true</jk> if we're serializing {@link XmlFormat#MIXED_PWS} or {@link XmlFormat#TEXT_PWS}. - * @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 Exception If a problem occurred trying to convert the output. - */ - protected XmlWriter serializeAnything( - XmlSerializerSession session, - XmlWriter out, - Object o, - ClassMeta eType, - String elementName, - Namespace elementNamespace, - boolean addNamespaceUris, - XmlFormat format, - boolean isMixed, - boolean preserveWhitespace, - BeanPropertyMeta pMeta) throws Exception { - - JsonType type = null; // The type string (e.g. <type> or <x x='type'> - int indent = isMixed ? 0 : session.indent; // Current indentation - ClassMeta<?> aType = null; // The actual type - ClassMeta<?> wType = null; // The wrapped type (delegate) - ClassMeta<?> sType = object(); // The serialized type - - aType = session.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; - eType = aType = ((Delegate)o).getClassMeta(); - } - - sType = aType.getSerializedClassMeta(); - - // Swap if necessary - PojoSwap swap = aType.getPojoSwap(); - if (swap != null) { - o = swap.swap(session, o); - - // If the getSwapClass() method returns Object, we need to figure out - // the actual type now. - if (sType.isObject()) - sType = session.getClassMetaForObject(o); - } - } else { - sType = eType.getSerializedClassMeta(); - } - - // Does the actual type match the expected type? - boolean isExpectedType = true; - if (o == null || ! eType.same(aType)) { - if (eType.isNumber()) - isExpectedType = aType.isNumber(); - else if (eType.isMap()) - isExpectedType = aType.isMap(); - else if (eType.isCollectionOrArray()) - isExpectedType = aType.isCollectionOrArray(); - else - isExpectedType = false; - } - - String resolvedDictionaryName = isExpectedType ? null : aType.getDictionaryName(); - - // Note that the dictionary name may be specified on the actual type or the serialized type. - // HTML templates will have them defined on the serialized type. - String dictionaryName = aType.getDictionaryName(); - if (dictionaryName == null) - dictionaryName = sType.getDictionaryName(); - - // char '\0' is interpreted as null. - if (o != null && sType.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 (o == null) { - type = NULL; - } else if (sType.isCharSequence() || sType.isChar()) { - type = STRING; - } else if (sType.isNumber()) { - type = NUMBER; - } else if (sType.isBoolean()) { - type = BOOLEAN; - } else if (sType.isMapOrBean()) { - isCollapsed = sType.getExtendedMeta(XmlClassMeta.class).getFormat() == COLLAPSED; - type = OBJECT; - } else if (sType.isCollectionOrArray()) { - isCollapsed = (format == COLLAPSED && ! addNamespaceUris); - type = ARRAY; - } else { - type = STRING; - } - - if (format.isOneOf(MIXED,MIXED_PWS,TEXT,TEXT_PWS,XMLTEXT) && type.isOneOf(NULL,STRING,NUMBER,BOOLEAN)) - isCollapsed = true; - - // Is there a name associated with this bean? - if (elementName == null && dictionaryName != null) { - elementName = dictionaryName; - isExpectedType = true; - } - - if (session.isEnableNamespaces()) { - if (elementNamespace == null) - elementNamespace = sType.getExtendedMeta(XmlClassMeta.class).getNamespace(); - if (elementNamespace == null) - elementNamespace = aType.getExtendedMeta(XmlClassMeta.class).getNamespace(); - if (elementNamespace != null && elementNamespace.uri == null) - elementNamespace = null; - if (elementNamespace == null) - elementNamespace = session.getDefaultNamespace(); - } else { - elementNamespace = null; - } - - // Do we need a carriage return after the start tag? - boolean cr = o != null && (sType.isMapOrBean() || sType.isCollectionOrArray()) && ! isMixed; - - String en = elementName; - if (en == null) { - en = type.toString(); - type = null; - } - boolean encodeEn = elementName != null; - String ns = (elementNamespace == null ? null : elementNamespace.name); - String dns = null, elementNs = null; - if (session.isEnableNamespaces()) { - dns = elementName == null && session.getDefaultNamespace() != null ? session.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", session.getDefaultNamespace().getUri()); - - for (Namespace n : session.getNamespaces()) - out.attr("xmlns", n.getName(), n.getUri()); - } - if (! isExpectedType) { - if (resolvedDictionaryName != null) - out.attr(dns, session.getBeanTypePropertyName(eType), resolvedDictionaryName); - else if (type != null && type != STRING) - out.attr(dns, session.getBeanTypePropertyName(eType), type); - } - if (o == null) { - if ((sType.isBoolean() || sType.isNumber()) && ! sType.isNullable()) - o = sType.getPrimitiveDefault(); - } - - if (o != null && ! (sType.isMapOrBean())) - out.append('>'); - - if (cr && ! (sType.isMapOrBean())) - out.nl(indent+1); - } - - ContentResult rc = CR_ELEMENTS; - - // Render the tag contents. - if (o != null) { - if (sType.isUri() || (pMeta != null && pMeta.isUri())) { - out.textUri(o); - } else if (sType.isCharSequence() || sType.isChar()) { - if (format == XMLTEXT) - out.append(o); - else - out.text(o, preserveWhitespace); - } else if (sType.isNumber() || sType.isBoolean()) { - out.append(o); - } else if (sType.isMap() || (wType != null && wType.isMap())) { - if (o instanceof BeanMap) - rc = serializeBeanMap(session, out, (BeanMap)o, elementNamespace, isCollapsed, isMixed); - else - rc = serializeMap(session, out, (Map)o, sType, eType.getKeyType(), eType.getValueType(), isMixed); - } else if (sType.isBean()) { - rc = serializeBeanMap(session, out, session.toBeanMap(o), elementNamespace, isCollapsed, isMixed); - } else if (sType.isCollection() || (wType != null && wType.isCollection())) { - if (isCollapsed) - session.indent--; - serializeCollection(session, out, o, sType, eType, pMeta, isMixed); - if (isCollapsed) - session.indent++; - } else if (sType.isArray()) { - if (isCollapsed) - session.indent--; - serializeCollection(session, out, o, sType, eType, pMeta, isMixed); - if (isCollapsed) - session.indent++; - } else { - if (format == XMLTEXT) - out.append(session.toString(o)); - else - out.text(session.toString(o)); - } - } - - session.pop(); - - // Render the end tag. - if (! isCollapsed) { - if (rc == CR_EMPTY) { - if (session.isHtmlMode()) - out.append('>').eTag(elementNs, en, encodeEn); - else - out.append('/').append('>'); - } else if (rc == CR_VOID || o == null) { - out.append('/').append('>'); - } - else - out.ie(cr && rc != CR_MIXED ? indent : 0).eTag(elementNs, en, encodeEn); - if (! isMixed) - out.nl(indent); - } - - return out; - } - - private ContentResult serializeMap(XmlSerializerSession session, XmlWriter out, Map m, ClassMeta<?> sType, - ClassMeta<?> eKeyType, ClassMeta<?> eValueType, boolean isMixed) throws Exception { - - m = session.sort(m); - - ClassMeta<?> keyType = eKeyType == null ? sType.getKeyType() : eKeyType; - ClassMeta<?> valueType = eValueType == null ? sType.getValueType() : eValueType; - - 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 = session.generalize(k, keyType); - if (session.isTrimStrings() && k instanceof String) - k = k.toString().trim(); - } - - Object value = e.getValue(); - - if (! hasChildren) { - hasChildren = true; - out.append('>').nlIf(! isMixed, session.getIndent()); - } - serializeAnything(session, out, value, valueType, session.toString(k), null, false, XmlFormat.DEFAULT, isMixed, false, null); - } - return hasChildren ? CR_ELEMENTS : CR_EMPTY; - } - - private ContentResult serializeBeanMap(XmlSerializerSession session, XmlWriter out, BeanMap<?> m, - Namespace elementNs, boolean isCollapsed, boolean isMixed) throws Exception { - boolean hasChildren = false; - BeanMeta<?> bm = m.getMeta(); - - List<BeanPropertyValue> lp = m.getValues(session.isTrimNulls()); - - XmlBeanMeta xbm = bm.getExtendedMeta(XmlBeanMeta.class); - - Set<String> - attrs = xbm.getAttrPropertyNames(), - elements = xbm.getElementPropertyNames(), - collapsedElements = xbm.getCollapsedPropertyNames(); - String - attrsProperty = xbm.getAttrsPropertyName(), - contentProperty = xbm.getContentPropertyName(); - - XmlFormat cf = null; - - Object content = null; - ClassMeta<?> contentType = null; - for (BeanPropertyValue p : lp) { - String n = p.getName(); - if (attrs.contains(n) || attrs.contains("*") || n.equals(attrsProperty)) { - BeanPropertyMeta pMeta = p.getMeta(); - ClassMeta<?> cMeta = p.getClassMeta(); - - String key = p.getName(); - Object value = p.getValue(); - Throwable t = p.getThrown(); - if (t != null) - session.onBeanGetterException(pMeta, t); - - if (session.canIgnoreValue(cMeta, key, value)) - continue; - - Namespace ns = (session.isEnableNamespaces() && pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace() != elementNs ? pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace() : null); - - if (pMeta.isUri() ) { - out.attrUri(ns, key, value); - } else if (n.equals(attrsProperty)) { - if (value instanceof BeanMap) { - BeanMap<?> bm2 = (BeanMap)value; - for (BeanPropertyValue p2 : bm2.getValues(true)) { - String key2 = p2.getName(); - Object value2 = p2.getValue(); - Throwable t2 = p2.getThrown(); - if (t2 != null) - session.onBeanGetterException(pMeta, t); - out.attr(ns, key2, value2); - } - } else /* Map */ { - Map m2 = (Map)value; - for (Map.Entry e : (Set<Map.Entry>)(m2.entrySet())) { - out.attr(ns, session.toString(e.getKey()), e.getValue()); - } - } - } else { - out.attr(ns, key, value); - } - } - } - - boolean - hasContent = false, - preserveWhitespace = false, - isVoidElement = xbm.getContentFormat() == VOID; - - for (BeanPropertyValue p : lp) { - BeanPropertyMeta pMeta = p.getMeta(); - ClassMeta<?> cMeta = p.getClassMeta(); - - String n = p.getName(); - if (n.equals(contentProperty)) { - content = p.getValue(); - contentType = p.getClassMeta(); - hasContent = true; - cf = xbm.getContentFormat(); - if (cf.isOneOf(MIXED,MIXED_PWS,TEXT,TEXT_PWS,XMLTEXT)) - isMixed = true; - if (cf.isOneOf(MIXED_PWS, TEXT_PWS)) - preserveWhitespace = true; - if (contentType.isCollection() && ((Collection)content).isEmpty()) - hasContent = false; - else if (contentType.isArray() && Array.getLength(content) == 0) - hasContent = false; - } else if (elements.contains(n) || collapsedElements.contains(n) || elements.contains("*") || collapsedElements.contains("*") ) { - String key = p.getName(); - Object value = p.getValue(); - Throwable t = p.getThrown(); - if (t != null) - session.onBeanGetterException(pMeta, t); - - if (session.canIgnoreValue(cMeta, key, value)) - continue; - - if (! hasChildren) { - hasChildren = true; - out.appendIf(! isCollapsed, '>').nlIf(! isMixed, session.getIndent()); - } - - XmlBeanPropertyMeta xbpm = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class); - serializeAnything(session, out, value, cMeta, key, xbpm.getNamespace(), false, xbpm.getXmlFormat(), isMixed, false, pMeta); - } - } - if (! hasContent) - return (hasChildren ? CR_ELEMENTS : isVoidElement ? CR_VOID : CR_EMPTY); - out.append('>').nlIf(! isMixed, session.getIndent()); - - // Serialize XML content. - if (content != null) { - if (contentType == null) { - } else if (contentType.isCollection()) { - Collection c = (Collection)content; - for (Iterator i = c.iterator(); i.hasNext();) { - Object value = i.next(); - serializeAnything(session, out, value, contentType.getElementType(), null, null, false, cf, isMixed, preserveWhitespace, null); - } - } else if (contentType.isArray()) { - Collection c = toList(Object[].class, content); - for (Iterator i = c.iterator(); i.hasNext();) { - Object value = i.next(); - serializeAnything(session, out, value, contentType.getElementType(), null, null, false, cf, isMixed, preserveWhitespace, null); - } - } else { - serializeAnything(session, out, content, contentType, null, null, false, cf, isMixed, preserveWhitespace, null); - } - } else { - if (! session.isTrimNulls()) { - if (! isMixed) - out.i(session.indent); - out.text(content); - if (! isMixed) - out.nl(session.indent); - } - } - return isMixed ? CR_MIXED : CR_ELEMENTS; - } - - private XmlWriter serializeCollection(XmlSerializerSession session, XmlWriter out, Object in, ClassMeta<?> sType, - ClassMeta<?> eType, BeanPropertyMeta ppMeta, boolean isMixed) throws Exception { - - ClassMeta<?> seType = sType.getElementType(); - if (seType == null) - seType = session.object(); - ClassMeta<?> eeType = eType.getElementType(); - - Collection c = (sType.isCollection() ? (Collection)in : toList(sType.getInnerClass(), in)); - - c = session.sort(c); - - String type2 = null; - if (sType != eType) - type2 = sType.getDictionaryName(); - - String eName = type2; - Namespace eNs = null; - - if (ppMeta != null) { - XmlBeanPropertyMeta xbpm = ppMeta.getExtendedMeta(XmlBeanPropertyMeta.class); - eName = xbpm.getChildName(); - eNs = xbpm.getNamespace(); - } - - for (Iterator i = c.iterator(); i.hasNext();) { - Object value = i.next(); - serializeAnything(session, out, value, eeType, eName, eNs, false, XmlFormat.DEFAULT, isMixed, false, null); - } - return out; - } /** * Returns the schema serializer based on the settings of this serializer. @@ -801,57 +236,12 @@ public class XmlSerializer extends WriterSerializer { */ public XmlSerializer getSchemaSerializer() { if (schemaSerializer == null) - schemaSerializer = new XmlSchemaSerializer(propertyStore, getOverrideProperties()); + schemaSerializer = new XmlSchemaSerializer(propertyStore); return schemaSerializer; } - static enum JsonType { - STRING("string"),BOOLEAN("boolean"),NUMBER("number"),ARRAY("array"),OBJECT("object"),NULL("null"); - - private final String value; - private JsonType(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } - - boolean isOneOf(JsonType...types) { - for (JsonType type : types) - if (type == this) - return true; - return false; - } - } - - /** - * Identifies what the contents were of a serialized bean. - */ - static enum ContentResult { - CR_VOID, // No content...append "/>" to the start tag. - CR_EMPTY, // No content...append "/>" to the start tag if XML, "/></end>" if HTML. - CR_MIXED, // Mixed content...don't add whitespace. - CR_ELEMENTS // Elements...use normal whitespace rules. - } - - - //-------------------------------------------------------------------------------- - // Entry point methods - //-------------------------------------------------------------------------------- - - @Override /* Serializer */ - protected void doSerialize(SerializerSession session, SerializerOutput out, Object o) throws Exception { - XmlSerializerSession s = (XmlSerializerSession)session; - if (s.isEnableNamespaces() && s.isAutoDetectNamespaces()) - findNsfMappings(s, o); - serializeAnything(s, s.getXmlWriter(out), o, s.getExpectedRootType(o), null, null, s.isEnableNamespaces() && s.isAddNamespaceUrlsToRoot(), XmlFormat.DEFAULT, false, false, null); - } - @Override /* Serializer */ - public XmlSerializerSession createSession(ObjectMap op, Method javaMethod, Locale locale, - TimeZone timeZone, MediaType mediaType, UriContext uriContext) { - return new XmlSerializerSession(ctx, op, javaMethod, locale, timeZone, mediaType, uriContext); + public WriterSerializerSession createSession(SerializerSessionArgs args) { + return new XmlSerializerSession(ctx, args); } }
