You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2016/08/09 19:53:45 UTC
[12/51] [partial] incubator-juneau git commit: Rename project
directories.
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/xml/XmlDocSerializer.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlDocSerializer.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlDocSerializer.java
new file mode 100644
index 0000000..5c567e8
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlDocSerializer.java
@@ -0,0 +1,64 @@
+/***************************************************************************************************************************
+ * 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.xml.XmlSerializerContext.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.serializer.*;
+
+/**
+ * Serializes POJOs to HTTP responses as 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>
+ * Same as {@link XmlSerializer}, 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 (james.bognar@salesforce.com)
+ */
+public class XmlDocSerializer extends XmlSerializer {
+
+ /** Default serializer without namespaces. */
+ @Produces(value="text/xml+simple",contentType="text/xml")
+ public static class Simple extends XmlDocSerializer {
+ /** Constructor */
+ public Simple() {
+ setProperty(XML_enableNamespaces, false);
+ }
+ }
+
+ //--------------------------------------------------------------------------------
+ // Overridden methods
+ //--------------------------------------------------------------------------------
+
+ @Override /* Serializer */
+ protected void doSerialize(SerializerSession session, Object o) throws Exception {
+ XmlSerializerSession s = (XmlSerializerSession)session;
+ XmlWriter w = s.getWriter();
+ w.append("<?xml")
+ .attr("version", "1.0")
+ .attr("encoding", "UTF-8")
+ .appendln("?>");
+ super.doSerialize(s, o);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/xml/XmlParser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlParser.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlParser.java
new file mode 100644
index 0000000..0ed36b9
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlParser.java
@@ -0,0 +1,523 @@
+/***************************************************************************************************************************
+ * 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 javax.xml.stream.XMLStreamConstants.*;
+import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.xml.annotation.XmlFormat.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import javax.xml.stream.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.transform.*;
+import org.apache.juneau.xml.annotation.*;
+
+/**
+ * Parses text generated by the {@link XmlSerializer} class back into a POJO model.
+ *
+ *
+ * <h6 class='topic'>Media types</h6>
+ * <p>
+ * Handles <code>Content-Type</code> types: <code>text/xml</code>
+ *
+ *
+ * <h6 class='topic'>Description</h6>
+ * <p>
+ * See the {@link XmlSerializer} class for a description of Juneau-generated XML.
+ *
+ *
+ * <h6 class='topic'>Configurable properties</h6>
+ * <p>
+ * This class has the following properties associated with it:
+ * <ul>
+ * <li>{@link XmlParserContext}
+ * <li>{@link BeanContext}
+ * </ul>
+ *
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+@SuppressWarnings({ "rawtypes", "unchecked" })
+@Consumes({"text/xml","application/xml"})
+public class XmlParser extends ReaderParser {
+
+ /** Default parser, all default settings.*/
+ public static final XmlParser DEFAULT = new XmlParser().lock();
+
+ private static final int UNKNOWN=0, OBJECT=1, ARRAY=2, STRING=3, NUMBER=4, BOOLEAN=5, NULL=6;
+
+
+ private <T> T parseAnything(XmlParserSession session, ClassMeta<T> nt, String currAttr, XMLStreamReader r, Object outer, boolean isRoot) throws Exception {
+
+ BeanContext bc = session.getBeanContext();
+ if (nt == null)
+ nt = (ClassMeta<T>)object();
+ PojoTransform<T,Object> transform = (PojoTransform<T,Object>)nt.getPojoTransform();
+ ClassMeta<?> ft = nt.getTransformedClassMeta();
+ session.setCurrentClass(ft);
+
+ String wrapperAttr = (isRoot && session.isPreserveRootElement()) ? r.getName().getLocalPart() : null;
+ String typeAttr = r.getAttributeValue(null, "type");
+ int jsonType = getJsonType(typeAttr);
+ String b = r.getAttributeValue(session.getXsiNs(), "nil");
+ if (b == null)
+ b = r.getAttributeValue(null, "nil");
+ boolean isNull = b != null && b.equals("true");
+ if (jsonType == 0) {
+ String elementName = session.decodeString(r.getLocalName());
+ if (elementName == null || elementName.equals(currAttr))
+ jsonType = UNKNOWN;
+ else
+ jsonType = getJsonType(elementName);
+ }
+ if (! ft.canCreateNewInstance(outer)) {
+ String c = r.getAttributeValue(null, "_class");
+ if (c != null) {
+ ft = nt = (ClassMeta<T>)bc.getClassMetaFromString(c);
+ }
+ }
+ Object o = null;
+
+ if (jsonType == NULL) {
+ r.nextTag(); // Discard end tag
+ return null;
+ }
+ if (isNull) {
+ while (true) {
+ int e = r.next();
+ if (e == END_ELEMENT)
+ return null;
+ }
+ }
+
+ if (ft.isObject()) {
+ if (jsonType == OBJECT) {
+ ObjectMap m = new ObjectMap(bc);
+ parseIntoMap(session, r, m, string(), object());
+ if (wrapperAttr != null)
+ m = new ObjectMap(bc).append(wrapperAttr, m);
+ o = m.cast();
+ } else if (jsonType == ARRAY)
+ o = parseIntoCollection(session, r, new ObjectList(bc), object());
+ else if (jsonType == STRING) {
+ o = session.decodeString(r.getElementText());
+ if (ft.isChar())
+ o = o.toString().charAt(0);
+ }
+ else if (jsonType == NUMBER)
+ o = parseNumber(session.decodeLiteral(r.getElementText()), null);
+ else if (jsonType == BOOLEAN)
+ o = Boolean.parseBoolean(session.decodeLiteral(r.getElementText()));
+ else if (jsonType == UNKNOWN)
+ o = getUnknown(session, r);
+ } else if (ft.isBoolean()) {
+ o = Boolean.parseBoolean(session.decodeLiteral(r.getElementText()));
+ } else if (ft.isCharSequence()) {
+ o = session.decodeString(r.getElementText());
+ } else if (ft.isChar()) {
+ o = session.decodeString(r.getElementText()).charAt(0);
+ } else if (ft.isMap()) {
+ Map m = (ft.canCreateNewInstance(outer) ? (Map)ft.newInstance(outer) : new ObjectMap(bc));
+ o = parseIntoMap(session, r, m, ft.getKeyType(), ft.getValueType());
+ if (wrapperAttr != null)
+ o = new ObjectMap(bc).append(wrapperAttr, m);
+ } else if (ft.isCollection()) {
+ Collection l = (ft.canCreateNewInstance(outer) ? (Collection)ft.newInstance(outer) : new ObjectList(bc));
+ o = parseIntoCollection(session, r, l, ft.getElementType());
+ } else if (ft.isNumber()) {
+ o = parseNumber(session.decodeLiteral(r.getElementText()), (Class<? extends Number>)ft.getInnerClass());
+ } else if (ft.canCreateNewInstanceFromObjectMap(outer)) {
+ ObjectMap m = new ObjectMap(bc);
+ parseIntoMap(session, r, m, string(), object());
+ o = ft.newInstanceFromObjectMap(outer, m);
+ } else if (ft.canCreateNewBean(outer)) {
+ if (ft.getXmlMeta().getFormat() == XmlFormat.COLLAPSED) {
+ String fieldName = r.getLocalName();
+ BeanMap m = bc.newBeanMap(outer, ft.getInnerClass());
+ BeanPropertyMeta bpm = m.getMeta().getXmlMeta().getPropertyMeta(fieldName);
+ ClassMeta<?> cm = m.getMeta().getClassMeta();
+ Object value = parseAnything(session, cm, currAttr, r, m.getBean(false), false);
+ setName(cm, value, currAttr);
+ bpm.set(m, value);
+ o = m.getBean();
+ } else {
+ BeanMap m = bc.newBeanMap(outer, ft.getInnerClass());
+ o = parseIntoBean(session, r, m).getBean();
+ }
+ } else if (ft.isArray()) {
+ ArrayList l = (ArrayList)parseIntoCollection(session, r, new ArrayList(), ft.getElementType());
+ o = bc.toArray(ft, l);
+ } else if (ft.canCreateNewInstanceFromString(outer)) {
+ o = ft.newInstanceFromString(outer, session.decodeString(r.getElementText()));
+ } else if (ft.canCreateNewInstanceFromNumber(outer)) {
+ o = ft.newInstanceFromNumber(outer, parseNumber(session.decodeLiteral(r.getElementText()), ft.getNewInstanceFromNumberClass()));
+ } else {
+ throw new ParseException(session, "Class ''{0}'' could not be instantiated. Reason: ''{1}''", ft.getInnerClass().getName(), ft.getNotABeanReason());
+ }
+
+ if (transform != null && o != null)
+ o = transform.normalize(o, nt);
+
+ if (outer != null)
+ setParent(nt, o, outer);
+
+ return (T)o;
+ }
+
+ private <K,V> Map<K,V> parseIntoMap(XmlParserSession session, XMLStreamReader r, Map<K,V> m, ClassMeta<K> keyType, ClassMeta<V> valueType) throws Exception {
+ BeanContext bc = session.getBeanContext();
+ int depth = 0;
+ for (int i = 0; i < r.getAttributeCount(); i++) {
+ String a = r.getAttributeLocalName(i);
+ // TODO - Need better handling of namespaces here.
+ if (! (a.equals("type"))) {
+ K key = session.trim(convertAttrToType(session, m, a, keyType));
+ V value = session.trim(convertAttrToType(session, m, r.getAttributeValue(i), valueType));
+ setName(valueType, value, key);
+ m.put(key, value);
+ }
+ }
+ do {
+ int event = r.nextTag();
+ String currAttr;
+ if (event == START_ELEMENT) {
+ depth++;
+ currAttr = session.decodeString(r.getLocalName());
+ K key = convertAttrToType(session, m, currAttr, keyType);
+ V value = parseAnything(session, valueType, currAttr, r, m, false);
+ setName(valueType, value, currAttr);
+ if (valueType.isObject() && m.containsKey(key)) {
+ Object o = m.get(key);
+ if (o instanceof List)
+ ((List)o).add(value);
+ else
+ m.put(key, (V)new ObjectList(o, value).setBeanContext(bc));
+ } else {
+ m.put(key, value);
+ }
+ } else if (event == END_ELEMENT) {
+ depth--;
+ return m;
+ }
+ } while (depth > 0);
+ return m;
+ }
+
+ private <E> Collection<E> parseIntoCollection(XmlParserSession session, XMLStreamReader r, Collection<E> l, ClassMeta<E> elementType) throws Exception {
+ int depth = 0;
+ do {
+ int event = r.nextTag();
+ if (event == START_ELEMENT) {
+ depth++;
+ E value = parseAnything(session, elementType, null, r, l, false);
+ l.add(value);
+ } else if (event == END_ELEMENT) {
+ depth--;
+ return l;
+ }
+ } while (depth > 0);
+ return l;
+ }
+
+ private Object[] doParseArgs(XmlParserSession session, XMLStreamReader r, ClassMeta<?>[] argTypes) throws Exception {
+ int depth = 0;
+ Object[] o = new Object[argTypes.length];
+ int i = 0;
+ do {
+ int event = r.nextTag();
+ if (event == START_ELEMENT) {
+ depth++;
+ o[i] = parseAnything(session, argTypes[i], null, r, null, false);
+ i++;
+ } else if (event == END_ELEMENT) {
+ depth--;
+ return o;
+ }
+ } while (depth > 0);
+ return o;
+ }
+
+ private int getJsonType(String s) {
+ if (s == null)
+ return UNKNOWN;
+ char c = s.charAt(0);
+ switch(c) {
+ case 'o': return (s.equals("object") ? OBJECT : UNKNOWN);
+ case 'a': return (s.equals("array") ? ARRAY : UNKNOWN);
+ case 's': return (s.equals("string") ? STRING : UNKNOWN);
+ case 'b': return (s.equals("boolean") ? BOOLEAN : UNKNOWN);
+ case 'n': {
+ c = s.charAt(2);
+ switch(c) {
+ case 'm': return (s.equals("number") ? NUMBER : UNKNOWN);
+ case 'l': return (s.equals("null") ? NULL : UNKNOWN);
+ }
+ //return NUMBER;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ private <T> BeanMap<T> parseIntoBean(XmlParserSession session, XMLStreamReader r, BeanMap<T> m) throws Exception {
+ BeanMeta bMeta = m.getMeta();
+ XmlBeanMeta xmlMeta = bMeta.getXmlMeta();
+
+ for (int i = 0; i < r.getAttributeCount(); i++) {
+ String key = session.decodeString(r.getAttributeLocalName(i));
+ String val = r.getAttributeValue(i);
+ BeanPropertyMeta bpm = xmlMeta.getPropertyMeta(key);
+ if (bpm == null) {
+ if (m.getMeta().isSubTyped()) {
+ m.put(key, val);
+ } else {
+ Location l = r.getLocation();
+ onUnknownProperty(session, key, m, l.getLineNumber(), l.getColumnNumber());
+ }
+ } else {
+ bpm.set(m, val);
+ }
+ }
+
+ BeanPropertyMeta cp = xmlMeta.getXmlContentProperty();
+ if (cp != null) {
+ XmlContentHandler h = xmlMeta.getXmlContentHandler();
+ if (h != null) {
+ h.parse(r, m.getBean());
+ } else {
+ String text = r.getElementText();
+ cp.set(m, session.decodeString(text));
+ }
+ return m;
+ }
+
+ int depth = 0;
+ do {
+ int event = r.nextTag();
+ String currAttr;
+ if (event == START_ELEMENT) {
+ depth++;
+ currAttr = session.decodeString(r.getLocalName());
+ BeanPropertyMeta pMeta = xmlMeta.getPropertyMeta(currAttr);
+ if (pMeta == null) {
+ if (m.getMeta().isSubTyped()) {
+ Object value = parseAnything(session, string(), currAttr, r, m.getBean(false), false);
+ m.put(currAttr, value);
+ } else {
+ Location l = r.getLocation();
+ onUnknownProperty(session, currAttr, m, l.getLineNumber(), l.getColumnNumber());
+ skipCurrentTag(r);
+ }
+ } else {
+ session.setCurrentProperty(pMeta);
+ XmlFormat xf = pMeta.getXmlMeta().getXmlFormat();
+ if (xf == COLLAPSED) {
+ ClassMeta<?> et = pMeta.getClassMeta().getElementType();
+ Object value = parseAnything(session, et, currAttr, r, m.getBean(false), false);
+ setName(et, value, currAttr);
+ pMeta.add(m, value);
+ } else if (xf == ATTR) {
+ pMeta.set(m, session.decodeString(r.getAttributeValue(0)));
+ r.nextTag();
+ } else {
+ ClassMeta<?> cm = pMeta.getClassMeta();
+ Object value = parseAnything(session, cm, currAttr, r, m.getBean(false), false);
+ setName(cm, value, currAttr);
+ pMeta.set(m, value);
+ }
+ session.setCurrentProperty(null);
+ }
+ } else if (event == END_ELEMENT) {
+ depth--;
+ return m;
+ }
+ } while (depth > 0);
+ return m;
+ }
+
+ private void skipCurrentTag(XMLStreamReader r) throws XMLStreamException {
+ int depth = 1;
+ do {
+ int event = r.next();
+ if (event == START_ELEMENT)
+ depth++;
+ else if (event == END_ELEMENT)
+ depth--;
+ } while (depth > 0);
+ }
+
+ private Object getUnknown(XmlParserSession session, XMLStreamReader r) throws Exception {
+ BeanContext bc = session.getBeanContext();
+ if (r.getEventType() != XMLStreamConstants.START_ELEMENT) {
+ throw new XMLStreamException("parser must be on START_ELEMENT to read next text", r.getLocation());
+ }
+ ObjectMap m = null;
+
+ // If this element has attributes, then it's always an ObjectMap.
+ if (r.getAttributeCount() > 0) {
+ m = new ObjectMap(bc);
+ for (int i = 0; i < r.getAttributeCount(); i++) {
+ String key = session.decodeString(r.getAttributeLocalName(i));
+ String val = r.getAttributeValue(i);
+ if (! key.equals("type"))
+ m.put(key, val);
+ }
+ }
+ int eventType = r.next();
+ StringBuilder sb = new StringBuilder();
+ while (eventType != XMLStreamConstants.END_ELEMENT) {
+ if (eventType == XMLStreamConstants.CHARACTERS || eventType == XMLStreamConstants.CDATA || eventType == XMLStreamConstants.SPACE || eventType == XMLStreamConstants.ENTITY_REFERENCE) {
+ sb.append(r.getText());
+ } else if (eventType == XMLStreamConstants.PROCESSING_INSTRUCTION || eventType == XMLStreamConstants.COMMENT) {
+ // skipping
+ } else if (eventType == XMLStreamConstants.END_DOCUMENT) {
+ throw new XMLStreamException("Unexpected end of document when reading element text content", r.getLocation());
+ } else if (eventType == XMLStreamConstants.START_ELEMENT) {
+ // Oops...this has an element in it.
+ // Parse it as a map.
+ if (m == null)
+ m = new ObjectMap(bc);
+ int depth = 0;
+ do {
+ int event = (eventType == -1 ? r.nextTag() : eventType);
+ String currAttr;
+ if (event == START_ELEMENT) {
+ depth++;
+ currAttr = session.decodeString(r.getLocalName());
+ String key = convertAttrToType(session, null, currAttr, string());
+ Object value = parseAnything(session, object(), currAttr, r, null, false);
+ if (m.containsKey(key)) {
+ Object o = m.get(key);
+ if (o instanceof ObjectList)
+ ((ObjectList)o).add(value);
+ else
+ m.put(key, new ObjectList(o, value).setBeanContext(bc));
+ } else {
+ m.put(key, value);
+ }
+
+ } else if (event == END_ELEMENT) {
+ depth--;
+ break;
+ }
+ eventType = -1;
+ } while (depth > 0);
+ break;
+ } else {
+ throw new XMLStreamException("Unexpected event type " + eventType, r.getLocation());
+ }
+ eventType = r.next();
+ }
+ String s = sb.toString();
+ s = session.decodeString(s);
+ if (m != null) {
+ if (! s.isEmpty())
+ m.put("contents", s);
+ return m;
+ }
+ return s;
+ }
+
+
+ //--------------------------------------------------------------------------------
+ // Overridden methods
+ //--------------------------------------------------------------------------------
+
+ @Override /* Parser */
+ public XmlParserSession createSession(Object input, ObjectMap properties, Method javaMethod, Object outer) {
+ return new XmlParserSession(getContext(XmlParserContext.class), getBeanContext(), input, properties, javaMethod, outer);
+ }
+
+ @Override /* Parser */
+ protected <T> T doParse(ParserSession session, ClassMeta<T> type) throws Exception {
+ XmlParserSession s = (XmlParserSession)session;
+ type = s.getBeanContext().normalizeClassMeta(type);
+ return parseAnything(s, type, null, s.getXmlStreamReader(), s.getOuter(), true);
+ }
+
+ @Override /* ReaderParser */
+ protected <K,V> Map<K,V> doParseIntoMap(ParserSession session, Map<K,V> m, Type keyType, Type valueType) throws Exception {
+ XmlParserSession s = (XmlParserSession)session;
+ ClassMeta cm = s.getBeanContext().getMapClassMeta(m.getClass(), keyType, valueType);
+ return parseIntoMap(s, m, cm.getKeyType(), cm.getValueType());
+ }
+
+ @Override /* ReaderParser */
+ protected <E> Collection<E> doParseIntoCollection(ParserSession session, Collection<E> c, Type elementType) throws Exception {
+ XmlParserSession s = (XmlParserSession)session;
+ ClassMeta cm = s.getBeanContext().getCollectionClassMeta(c.getClass(), elementType);
+ return parseIntoCollection(s,c, cm.getElementType());
+ }
+
+ @Override /* ReaderParser */
+ protected Object[] doParseArgs(ParserSession session, ClassMeta<?>[] argTypes) throws Exception {
+ XmlParserSession s = (XmlParserSession)session;
+ return doParseArgs(s, s.getXmlStreamReader(), argTypes);
+ }
+
+ @Override /* CoreApi */
+ public XmlParser setProperty(String property, Object value) throws LockedException {
+ super.setProperty(property, value);
+ return this;
+ }
+
+ @Override /* CoreApi */
+ public XmlParser setProperties(ObjectMap properties) throws LockedException {
+ super.setProperties(properties);
+ return this;
+ }
+
+ @Override /* CoreApi */
+ public XmlParser addNotBeanClasses(Class<?>...classes) throws LockedException {
+ super.addNotBeanClasses(classes);
+ return this;
+ }
+
+ @Override /* CoreApi */
+ public XmlParser addTransforms(Class<?>...classes) throws LockedException {
+ super.addTransforms(classes);
+ return this;
+ }
+
+ @Override /* CoreApi */
+ public <T> XmlParser addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException {
+ super.addImplClass(interfaceClass, implClass);
+ return this;
+ }
+
+ @Override /* CoreApi */
+ public XmlParser setClassLoader(ClassLoader classLoader) throws LockedException {
+ super.setClassLoader(classLoader);
+ return this;
+ }
+
+ @Override /* Lockable */
+ public XmlParser lock() {
+ super.lock();
+ return this;
+ }
+
+ @Override /* Lockable */
+ public XmlParser clone() {
+ try {
+ XmlParser c = (XmlParser)super.clone();
+ return c;
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e); // Shouldn't happen.
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/xml/XmlParserContext.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlParserContext.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlParserContext.java
new file mode 100644
index 0000000..1957943
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlParserContext.java
@@ -0,0 +1,156 @@
+/***************************************************************************************************************************
+ * 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 javax.xml.stream.*;
+import javax.xml.stream.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.parser.*;
+
+/**
+ * Configurable properties on the {@link XmlParser} class.
+ * <p>
+ * Context properties are set by calling {@link ContextFactory#setProperty(String, Object)} on the context factory
+ * returned {@link CoreApi#getContextFactory()}.
+ * <p>
+ * The following convenience methods are also provided for setting context properties:
+ * <ul>
+ * <li>{@link XmlParser#setProperty(String,Object)}
+ * <li>{@link XmlParser#setProperties(ObjectMap)}
+ * <li>{@link XmlParser#addNotBeanClasses(Class[])}
+ * <li>{@link XmlParser#addTransforms(Class[])}
+ * <li>{@link XmlParser#addImplClass(Class,Class)}
+ * </ul>
+ * <p>
+ * See {@link ContextFactory} for more information about context properties.
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public final class XmlParserContext extends ParserContext {
+
+ /**
+ * 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>true</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";
+
+ final String xsiNs;
+ final boolean
+ trimWhitespace,
+ validating,
+ coalescing,
+ replaceEntityReferences,
+ preserveRootElement;
+ final XMLReporter reporter;
+ final XMLResolver resolver;
+ final XMLEventAllocator eventAllocator;
+
+ /**
+ * Constructor.
+ * <p>
+ * Typically only called from {@link ContextFactory#getContext(Class)}.
+ *
+ * @param cf The factory that created this context.
+ */
+ public XmlParserContext(ContextFactory cf) {
+ super(cf);
+ xsiNs = cf.getProperty(XML_xsiNs, String.class, "http://www.w3.org/2001/XMLSchema-instance");
+ trimWhitespace = cf.getProperty(XML_trimWhitespace, boolean.class, true);
+ validating = cf.getProperty(XML_validating, boolean.class, false);
+ coalescing = cf.getProperty(XML_coalescing, boolean.class, false);
+ replaceEntityReferences = cf.getProperty(XML_replaceEntityReferences, boolean.class, true);
+ preserveRootElement = cf.getProperty(XML_preserveRootElement, boolean.class, false);
+ reporter = cf.getProperty(XML_reporter, XMLReporter.class, null);
+ resolver = cf.getProperty(XML_resolver, XMLResolver.class, null);
+ eventAllocator = cf.getProperty(XML_eventAllocator, XMLEventAllocator.class, null);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/xml/XmlParserSession.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlParserSession.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlParserSession.java
new file mode 100644
index 0000000..bddd1fd
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlParserSession.java
@@ -0,0 +1,189 @@
+/***************************************************************************************************************************
+ * 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.xml.XmlParserContext.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+
+import javax.xml.stream.*;
+import javax.xml.stream.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.parser.*;
+
+/**
+ * Session object that lives for the duration of a single use of {@link XmlParser}.
+ * <p>
+ * This class is NOT thread safe. It is meant to be discarded after one-time use.
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public final class XmlParserSession extends ParserSession {
+
+ 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 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 beanContext The bean context being used.
+ * @param input The input. Can be any of the following types:
+ * <ul>
+ * <li><jk>null</jk>
+ * <li>{@link Reader}
+ * <li>{@link CharSequence}
+ * <li>{@link InputStream} containing UTF-8 encoded text.
+ * <li>{@link File} containing system encoded text.
+ * </ul>
+ * @param op The override properties.
+ * These override any context properties defined in the context.
+ * @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 XmlParserSession(XmlParserContext ctx, BeanContext beanContext, Object input, ObjectMap op, Method javaMethod, Object outer) {
+ super(ctx, beanContext, input, op, javaMethod, outer);
+ if (op == null || op.isEmpty()) {
+ xsiNs = ctx.xsiNs;
+ trimWhitespace = ctx.trimWhitespace;
+ validating = ctx.validating;
+ coalescing = ctx.coalescing;
+ replaceEntityReferences = ctx.replaceEntityReferences;
+ reporter = ctx.reporter;
+ resolver = ctx.resolver;
+ eventAllocator = ctx.eventAllocator;
+ preserveRootElement = ctx.preserveRootElement;
+ } else {
+ xsiNs = op.getString(XML_xsiNs, ctx.xsiNs);
+ trimWhitespace = op.getBoolean(XML_trimWhitespace, ctx.trimWhitespace);
+ validating = op.getBoolean(XML_validating, ctx.validating);
+ coalescing = op.getBoolean(XML_coalescing, ctx.coalescing);
+ replaceEntityReferences = op.getBoolean(XML_replaceEntityReferences, ctx.replaceEntityReferences);
+ reporter = (XMLReporter)op.get(XML_reporter, ctx.reporter);
+ resolver = (XMLResolver)op.get(XML_resolver, ctx.resolver);
+ eventAllocator = (XMLEventAllocator)op.get(XML_eventAllocator, ctx.eventAllocator);
+ preserveRootElement = op.getBoolean(XML_preserveRootElement, ctx.preserveRootElement);
+ }
+ }
+
+ /**
+ * Returns the {@link XmlParserContext#XML_xsiNs} setting value for this session.
+ *
+ * @return The {@link XmlParserContext#XML_xsiNs} setting value for this session.
+ */
+ public final String getXsiNs() {
+ return xsiNs;
+ }
+
+ /**
+ * Returns the {@link XmlParserContext#XML_preserveRootElement} setting value for this session.
+ *
+ * @return The {@link XmlParserContext#XML_preserveRootElement} setting value for this session.
+ */
+ public final boolean isPreserveRootElement() {
+ return preserveRootElement;
+ }
+
+ /**
+ * Wrap the specified reader in a STAX reader based on settings in this context.
+ *
+ * @return The new STAX reader.
+ * @throws Exception If problem occurred trying to create reader.
+ */
+ public final XMLStreamReader getXmlStreamReader() throws Exception {
+ try {
+ Reader r = IOUtils.getBufferedReader(getReader());
+ 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;
+ }
+
+ /**
+ * Decodes and trims the specified string.
+ *
+ * @param s The string to be decoded.
+ * @return The decoded string.
+ */
+ public final String decodeString(String s) {
+ if (s == null || s.isEmpty())
+ return s;
+ if (trimWhitespace)
+ s = s.trim();
+ s = XmlUtils.decode(s);
+ if (isTrimStrings())
+ s = s.trim();
+ return s;
+ }
+
+ /**
+ * Decodes the specified literal (e.g. <js>"true"</js>, <js>"123"</js>).
+ * <p>
+ * Unlike <code>decodeString(String)</code>, the input string is ALWAYS trimmed before decoding, and
+ * NEVER trimmed after decoding.
+ *
+ * @param s The string to trim.
+ * @return The trimmed string, or <jk>null</jk> if the string was <jk>null</jk>.
+ */
+ public final String decodeLiteral(String s) {
+ if (s == null || s.isEmpty())
+ return s;
+ s = s.trim();
+ s = XmlUtils.decode(s);
+ return s;
+ }
+
+ /**
+ * Silently closes the XML stream.
+ */
+ @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/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaDocSerializer.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaDocSerializer.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaDocSerializer.java
new file mode 100644
index 0000000..640fbb7
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaDocSerializer.java
@@ -0,0 +1,67 @@
+/***************************************************************************************************************************
+ * 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.*;
+import org.apache.juneau.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 (james.bognar@salesforce.com)
+ */
+public class XmlSchemaDocSerializer extends XmlSchemaSerializer {
+
+ //--------------------------------------------------------------------------------
+ // Overridden methods
+ //--------------------------------------------------------------------------------
+
+ /**
+ * Constructor.
+ */
+ public XmlSchemaDocSerializer() {}
+
+ /**
+ * Constructor.
+ *
+ * @param cf The context factory to use for creating the context for this serializer.
+ */
+ protected XmlSchemaDocSerializer(ContextFactory cf) {
+ super(cf);
+ }
+
+ @Override /* Serializer */
+ protected void doSerialize(SerializerSession session, Object o) throws Exception {
+ XmlSerializerSession s = (XmlSerializerSession)session;
+ XmlWriter w = s.getWriter();
+ w.append("<?xml")
+ .attr("version", "1.0")
+ .attr("encoding", "UTF-8")
+ .appendln("?>");
+ super.doSerialize(s, o);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/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
new file mode 100644
index 0000000..7a2c0f4
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializer.java
@@ -0,0 +1,605 @@
+/***************************************************************************************************************************
+ * 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.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.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+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.
+ *
+ *
+ * <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 XmlSerializerContext}
+ * <li>{@link BeanContext}
+ * </ul>
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+@Produces(value="text/xml+schema",contentType="text/xml")
+public class XmlSchemaSerializer extends XmlSerializer {
+
+ /**
+ * Constructor
+ */
+ public XmlSchemaSerializer() {
+ super();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param config Initialize with the specified config property store.
+ */
+ protected XmlSchemaSerializer(ContextFactory config) {
+ getContextFactory().copyFrom(config);
+ }
+
+ @Override /* XmlSerializer */
+ protected void doSerialize(SerializerSession session, Object o) throws Exception {
+ XmlSerializerSession s = (XmlSerializerSession)session;
+
+ if (s.isEnableNamespaces() && s.isAutoDetectNamespaces())
+ findNsfMappings(s, o);
+
+ Namespace xs = s.getXsNamespace();
+ Namespace[] allNs = ArrayUtils.append(new Namespace[]{s.getDefaultNamespace()}, s.getNamespaces());
+
+ Schemas schemas = new Schemas(s, xs, s.getDefaultNamespace(), allNs);
+ schemas.process(s, o);
+ schemas.serializeTo(session.getWriter());
+ }
+
+ /**
+ * Returns an XML-Schema validator based on the output returned by {@link #doSerialize(SerializerSession, Object)};
+ *
+ * @param session The serializer session object return by {@link #createSession(Object, ObjectMap, Method)}.<br>
+ * Can be <jk>null</jk>.
+ * @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, Object o) throws Exception {
+ doSerialize(session, o);
+ String xmlSchema = session.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 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(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 RuntimeException("No schema defined for namespace '"+ns+"'");
+ return s;
+ }
+
+ private void process(SerializerSession session, Object o) throws IOException {
+ ClassMeta<?> cm = session.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);
+ 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.isUseIndentation(), session.isTrimStrings(), session.getQuoteChar(), null, 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();
+ 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.getTransformedClassMeta();
+ int i = session.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 = session.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 = session.getIndent() + 1;
+
+ cm = cm.getTransformedClassMeta();
+
+ 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 (session.isAddJsonTypeAttrs() || (session.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 (! session.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 (session.isAddClassAttrs()) {
+ w.oTag(i+1, "attribute")
+ .attr("name", "_class")
+ .attr("type", "string")
+ .ceTag().nl();
+ }
+ if (session.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.getTransformedClassMeta();
+ 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(session.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.getTransformedClassMeta();
+ if (currentNs == targetNs && ! session.isAddJsonTypeAttrs()) {
+ if (cm.isBoolean())
+ name = "boolean";
+ else if (cm.isNumber()) {
+ if (cm.isDecimal())
+ name = "decimal";
+ else
+ name = "integer";
+ }
+ if (name == null && ! session.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);
+ 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 XmlSerializerSession createSession(Object output, ObjectMap properties, Method javaMethod) {
+ // This serializer must always have namespaces enabled.
+ if (properties == null)
+ properties = new ObjectMap();
+ properties.put(XmlSerializerContext.XML_enableNamespaces, true);
+ return new XmlSerializerSession(getContext(XmlSerializerContext.class), getBeanContext(), output, properties, javaMethod);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/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
new file mode 100644
index 0000000..a686412
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializer.java
@@ -0,0 +1,709 @@
+/***************************************************************************************************************************
+ * 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.serializer.SerializerContext.*;
+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.json.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.transform.*;
+import org.apache.juneau.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 XmlSerializerContext}
+ * <li>{@link BeanContext}
+ * </ul>
+ *
+ *
+ * <h6 class='topic'>Behavior-specific subclasses</h6>
+ * <p>
+ * The following direct subclasses are provided for convenience:
+ * <ul class='spaced-list'>
+ * <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 (james.bognar@salesforce.com)
+ */
+@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);
+ }
+ }
+
+ /**
+ * 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 {
+ BeanContext bc = session.getBeanContext();
+ ClassMeta<?> aType = null; // The actual type
+ aType = session.push(null, o, null);
+
+ if (aType != null) {
+ Namespace ns = aType.getXmlMeta().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 = bc.forBean(o);
+ } else if (aType.isDelegate()) {
+ ClassMeta innerType = ((Delegate)o).getClassMeta();
+ Namespace ns = innerType.getXmlMeta().getNamespace();
+ if (ns != null) {
+ if (ns.uri != null)
+ session.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)
+ 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(false, session.isTrimNulls())) {
+
+ Namespace ns = p.getMeta().getXmlMeta().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 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, BeanPropertyMeta<?> pMeta) throws Exception {
+
+ BeanContext bc = session.getBeanContext();
+ String ts = null; // The type string (e.g. <type> or <x x='type'>
+ int indent = session.indent; // Current indentation
+ ClassMeta<?> aType = null; // The actual type
+ ClassMeta<?> wType = null; // The wrapped type
+ ClassMeta<?> gType = object(); // The generic 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;
+ aType = ((Delegate)o).getClassMeta();
+ }
+
+ gType = aType.getTransformedClassMeta();
+
+ // Transform if necessary
+ PojoTransform transform = aType.getPojoTransform();
+ if (transform != null) {
+ o = transform.transform(o);
+
+ // If the transform's getTransformedClass() method returns Object, we need to figure out
+ // the actual type now.
+ if (gType.isObject())
+ gType = bc.getClassMetaForObject(o);
+ }
+ } else {
+ gType = eType.getTransformedClassMeta();
+ }
+
+ String classAttr = null;
+ if (session.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 (session.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 = session.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 (session.isEnableNamespaces()) {
+ xsi = session.getXsiNamespace().name;
+ 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());
+
+ Namespace xsiNs = session.getXsiNamespace();
+ if (xsiNs != null)
+ out.attr("xmlns", xsiNs.name, xsiNs.uri);
+ }
+ if (elementName != null && session.isAddJsonTypeAttrs() && (session.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(session.trim(o));
+ else if (gType.isNumber() || gType.isBoolean())
+ out.append(o);
+ else if (gType.isMap() || (wType != null && wType.isMap())) {
+ if (o instanceof BeanMap)
+ hasChildren = serializeBeanMap(session, out, (BeanMap)o, elementNamespace, isCollapsed);
+ else
+ hasChildren = serializeMap(session, out, (Map)o, gType);
+ }
+ else if (gType.hasToObjectMapMethod())
+ hasChildren = serializeMap(session, out, gType.toObjectMap(o), gType);
+ else if (gType.isBean())
+ hasChildren = serializeBeanMap(session, out, bc.forBean(o), elementNamespace, isCollapsed);
+ else if (gType.isCollection() || (wType != null && wType.isCollection())) {
+ if (isCollapsed)
+ session.indent--;
+ serializeCollection(session, out, (Collection)o, gType, pMeta);
+ if (isCollapsed)
+ session.indent++;
+ }
+ else if (gType.isArray()) {
+ if (isCollapsed)
+ session.indent--;
+ serializeCollection(session, out, toList(gType.getInnerClass(), o), gType, pMeta);
+ if (isCollapsed)
+ session.indent++;
+ }
+ else
+ out.encodeText(session.toString(o));
+ }
+
+ session.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(XmlSerializerSession session, XmlWriter out, Map m, ClassMeta<?> type) throws Exception {
+
+ m = session.sort(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 = session.generalize(k, keyType);
+ if (session.isTrimStrings() && k instanceof String)
+ k = k.toString().trim();
+ }
+
+ Object value = e.getValue();
+
+ if (! hasChildren) {
+ hasChildren = true;
+ out.append('>').nl();
+ }
+ serializeAnything(session, out, value, valueType, session.toString(k), null, false, NORMAL, null);
+ }
+ return hasChildren;
+ }
+
+ private boolean serializeBeanMap(XmlSerializerSession session, XmlWriter out, BeanMap<?> m, Namespace elementNs, boolean isCollapsed) throws Exception {
+ boolean hasChildren = false;
+ BeanMeta bm = m.getMeta();
+
+ List<BeanPropertyValue> lp = m.getValues(false, session.isTrimNulls());
+
+ Map<String,BeanPropertyMeta> xmlAttrs = bm.getXmlMeta().getXmlAttrProperties();
+ Object content = null;
+ for (BeanPropertyValue p : lp) {
+ if (xmlAttrs.containsKey(p.getName())) {
+ BeanPropertyMeta pMeta = p.getMeta();
+ String key = p.getName();
+ Object value = p.getValue();
+ Throwable t = p.getThrown();
+ if (t != null)
+ session.addBeanGetterWarning(pMeta, t);
+
+ if (session.canIgnoreValue(pMeta.getClassMeta(), key, value))
+ continue;
+
+ Namespace ns = (session.isEnableNamespaces() && pMeta.getXmlMeta().getNamespace() != elementNs ? pMeta.getXmlMeta().getNamespace() : null);
+
+ if (pMeta.isBeanUri() || pMeta.isUri())
+ out.attrUri(ns, key, value);
+ else
+ out.attr(ns, key, value);
+ }
+ }
+
+ boolean hasContent = false;
+
+ for (BeanPropertyValue p : lp) {
+ 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.getName();
+ Object value = p.getValue();
+ Throwable t = p.getThrown();
+ if (t != null)
+ session.addBeanGetterWarning(pMeta, t);
+
+ if (session.canIgnoreValue(pMeta.getClassMeta(), key, value))
+ continue;
+
+ if (! hasChildren) {
+ hasChildren = true;
+ out.appendIf(! isCollapsed, '>').nl();
+ }
+ serializeAnything(session, out, value, pMeta.getClassMeta(), key, pMeta.getXmlMeta().getNamespace(), false, pMeta.getXmlMeta().getXmlFormat(), pMeta);
+ }
+ }
+ if ((! hasContent) || session.canIgnoreValue(string(), null, content))
+ return hasChildren;
+ out.append('>').cr(session.indent);
+
+ // Serialize XML content.
+ XmlContentHandler h = bm.getXmlMeta().getXmlContentHandler();
+ if (h != null)
+ h.serialize(out, m.getBean());
+ else
+ out.encodeText(content);
+ out.nl();
+ return true;
+ }
+
+ private XmlWriter serializeCollection(XmlSerializerSession session, XmlWriter out, Collection c, ClassMeta<?> type, BeanPropertyMeta<?> ppMeta) throws Exception {
+
+ c = session.sort(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(session, out, value, elementType, 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(getContextFactory());
+ return s;
+ }
+
+
+ //--------------------------------------------------------------------------------
+ // Overridden methods
+ //--------------------------------------------------------------------------------
+
+ @Override /* Serializer */
+ protected void doSerialize(SerializerSession session, Object o) throws Exception {
+ XmlSerializerSession s = (XmlSerializerSession)session;
+ if (s.isEnableNamespaces() && s.isAutoDetectNamespaces())
+ findNsfMappings(s, o);
+ serializeAnything(s, s.getWriter(), o, null, null, null, s.isEnableNamespaces() && s.isAddNamespaceUrlsToRoot(), NORMAL, null);
+ }
+
+ @Override /* Serializer */
+ public XmlSerializerSession createSession(Object output, ObjectMap properties, Method javaMethod) {
+ return new XmlSerializerSession(getContext(XmlSerializerContext.class), getBeanContext(), output, properties, javaMethod);
+ }
+
+ @Override /* CoreApi */
+ public XmlSerializer setProperty(String property, Object value) throws LockedException {
+ super.setProperty(property, value);
+ return this;
+ }
+
+ @Override /* CoreApi */
+ public XmlSerializer setProperties(ObjectMap properties) throws LockedException {
+ super.setProperties(properties);
+ return this;
+ }
+
+ @Override /* CoreApi */
+ public XmlSerializer addNotBeanClasses(Class<?>...classes) throws LockedException {
+ super.addNotBeanClasses(classes);
+ return this;
+ }
+
+ @Override /* CoreApi */
+ public XmlSerializer addTransforms(Class<?>...classes) throws LockedException {
+ super.addTransforms(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();
+ return c;
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e); // Shouldn't happen.
+ }
+ }
+}