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>&lt;?xml</xt> <xa>version</xa>=<xs>'1.0'</xs> <xa>encoding</xa>=<xs>'UTF-8'</xs><xt>?&gt;</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>&lt;root&gt;&lt;a&gt;</xt>foobar<xt>&lt;/a&gt;&lt;/root&gt;</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>&lt;?xml</xt> <xa>version</xa>=<xs>'1.0'</xs> <xa>encoding</xa>=<xs>'UTF-8'</xs><xt>?&gt;</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>' &gt; 640'</js>
+ * 	}
+ * <p>
+ * 	...maps to the following XML...
+ * <p class='bcode'>
+ * 	<xt>&lt;object&gt;</xt>
+ * 		<xt>&lt;name</xt> <xa>type</xa>=<xs>'string'</xs><xt>&gt;</xt>John Smith<xt>&lt;/name&gt;</xt>
+ * 		<xt>&lt;address</xt> <xa>type</xa>=<xs>'object'</xs><xt>&gt;</xt>
+ * 			<xt>&lt;streetAddress</xt> <xa>type</xa>=<xs>'string'</xs><xt>&gt;</xt>21 2nd Street<xt>&lt;/streetAddress&gt;</xt>
+ * 			<xt>&lt;city</xt> <xa>type</xa>=<xs>'string'</xs><xt>&gt;</xt>New York<xt>&lt;/city&gt;</xt>
+ * 			<xt>&lt;state</xt> <xa>type</xa>=<xs>'string'</xs><xt>&gt;</xt>NY<xt>&lt;/state&gt;</xt>
+ * 			<xt>&lt;postalCode</xt> <xa>type</xa>=<xs>'number'</xs><xt>&gt;</xt>10021<xt>&lt;/postalCode&gt;</xt>
+ * 		<xt>&lt;/address&gt;</xt>
+ * 		<xt>&lt;phoneNumbers</xt> <xa>type</xa>=<xs>'array'</xs><xt>&gt;</xt>
+ * 			<xt>&lt;string&gt;</xt>212 555-1111<xt>&lt;/string&gt;</xt>
+ * 			<xt>&lt;string&gt;</xt>212 555-2222<xt>&lt;/string&gt;</xt>
+ * 		<xt>&lt;/phoneNumbers&gt;</xt>
+ * 		<xt>&lt;additionalInfo</xt> <xa>type</xa>=<xs>'null'</xs><xt>&gt;&lt;/additionalInfo&gt;</xt>
+ * 		<xt>&lt;remote</xt> <xa>type</xa>=<xs>'boolean'</xs><xt>&gt;</xt>false<xt>&lt;/remote&gt;</xt>
+ * 		<xt>&lt;height</xt> <xa>type</xa>=<xs>'number'</xs><xt>&gt;</xt>62.4<xt>&lt;/height&gt;</xt>
+ * 		<xt>&lt;fico_x0020_score</xt> <xa>type</xa>=<xs>'string'</xs><xt>&gt;</xt> &amp;gt; 640<xt>&lt;/fico_x0020_score&gt;</xt>
+ * 	<xt>&lt;/object&gt;</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.
+		}
+	}
+}