/*=============================================================================*
* Copyright 2006 The Apache Software Foundation
*
* Licensed 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.muse.core.serializer.xstream;
import javax.xml.namespace.QName;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.apache.muse.core.serializer.Serializer;
import org.apache.muse.util.ReflectUtils;
import org.apache.muse.util.xml.XmlUtils;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.alias.ClassMapper;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.DomReader;
import com.thoughtworks.xstream.io.xml.DomWriter;
import com.thoughtworks.xstream.mapper.MapperWrapper;
/**
*
* XStreamSerializer is a generic Serializer that relies on the XStream
* library to serialize and deserialize objects to/from XML without any
* configuration files or schemas. To use this class within Muse
* applications, simply sub-class it an override the getSerializableType()
* method to return the class or interface that is being serialized. That's it!
* <br><br>
* This serializer has some additional code that helps XStream create "pure"
* XML by adding better support for XML namespaces and prefixes, as well as
* being more flexible in the Java field name conventions that can be read
* and converted into proper XML element names.
* <br><br>
* For more information about how XStream serializes your objects, and to get
* the JAR file needed to execute this code, please visit XStream's web site:
* <br><br>
* <a href="http://xstream.codehaus.org">http://xstream.codehaus.org</a>
*
* @author Dan Jemiolo (danj)
*
* @see org.apache.muse.core.serializer.Serializer
*
*/
public abstract class XStreamSerializer implements Serializer
{
//
// The XStream facade that aggregates all of the core XStream features.
// Note we initialize XStream with the DOM driver so that sub-classes
// that use the XStream.toXML and XStream.fromXML convenience methods
// will not have to configure this themselves (the default driver is
// XML Pull Parser, which we do not wish to include for legal reasons).
//
// This version of the XStream facade is an extension that calls
// on this Serializer class to get the name/type that is being
// serialized. This, in combination with some prefix/namespace support
// code, allows us to generate "pure" XML that has no Java connotations.
//
private XStream _xstream = new MuseSerializerXStream();
/**
*
* This method takes the non-namespace-aware DOM (DOM Level 1) that
* is generated by XStream and converts it into an equivalent DOM
* tree with the proper namespaces and prefixes. The namespace and
* prefix values should be taken from the QName that is provided to
* the Serializer.toXML() method.
*
*/
protected Element copySubTree(Element[] children,
Element root,
String namespace,
String prefix)
{
Document doc = root.getOwnerDocument();
for (int n = 0; n < children.length; ++n)
{
//
// create a QName using the user's URI/prefix and the
// local name created by XStream
//
QName qname = new QName(namespace, children[n].getLocalName(), prefix);
//
// now copy over the text and sub-child elements
//
Element childCopy = XmlUtils.createElement(doc, qname);
root.appendChild(childCopy);
String text = XmlUtils.extractText(children[n]);
if (text != null)
XmlUtils.setElementText(childCopy, text);
Element[] subChildren = XmlUtils.getAllElements(children[n]);
copySubTree(subChildren, childCopy, namespace, prefix);
}
return root;
}
/**
*
* @return A POJO representation of the given XML fragment.
*
*/
public Object fromXML(Element xml)
{
return getXStream().unmarshal(new DomReader(xml));
}
/**
*
* @return The XStream API, initialized with the DOM parser/driver.
*
*/
public final XStream getXStream()
{
return _xstream;
}
/**
*
* @return The XML representation of the object, with the root element
* having the given QName.
*
*/
public Element toXML(Object result, QName qname)
{
Document doc = XmlUtils.createDocument();
//
// use marshal rather than toXML so we can serialize the object
// directly to DOM (toXML returns a string of XML)
//
getXStream().marshal(result, new DomWriter(doc));
//
// XStream creates DOM Level 1 - argh! we need qualified names
// to be schema-compliant, so we have to copy the whole DOM tree
// in a Level 2 way. DOM (both levels) does not permit the
// changing of the name(space) of nodes after their creation.
//
Element root = XmlUtils.getFirstElement(doc);
Element[] children = XmlUtils.getAllElements(root);
String namespace = qname.getNamespaceURI();
String prefix = qname.getPrefix();
Element qualifiedRoot = XmlUtils.createElement(doc, qname);
return copySubTree(children, qualifiedRoot, namespace, prefix);
}
/**
*
* JavaFieldConverter is a pluggable XStream component that allows us
* more control over the naming conventions XStream uses/accepts when
* serializing and deserializing XML. We use this component to achieve
* two goals:
* <ol>
* <li>Tolerate namespaces and prefixes in XML elements when XStream
* is deserializing XML into POJOs. Normally, it would balk at the
* prefixed-name of the element because it doesn't match the field
* name or any of its aliases.
* </li>
* <br>
* <li>Tolerate Java field naming conventions - camel casing (first
* letter lowercase) and underscore prefixes. We drop underscore prefixes
* from the name and convert the first letter to uppercase before using
* the name to create an XML element. This makes it impossible to tell
* that the XML came from a Java class.
* </li>
* <br>
* </ol>
*
* @author Dan Jemiolo (danj)
*
*/
private class JavaFieldConverter extends MapperWrapper
{
public JavaFieldConverter(ClassMapper wrapper)
{
super(wrapper);
}
/**
*
* This method returns Serializer.getSerializableType(). Because
* each XStreamSerializer-based serializer will have its own
* instance of XStream, we can assume it is serializing this type.
*
*/
public Class realClass(String className)
{
return getSerializableType();
}
/**
*
* This method converts the XML element name to the Java field name.
*
*/
public String realMember(Class theClass, String elementName)
{
//
// strip prefix from DOM element name
//
int colonIndex = elementName.indexOf(':');
if (colonIndex >= 0)
elementName = elementName.substring(colonIndex + 1);
//
// make two options - just camel case, or camel case with an
// underscore prefix. try 'em both
//
String withLowerCase = Character.toLowerCase(elementName.charAt(0)) + elementName.substring(1);
String withPrefix = '_' + withLowerCase;
try
{
theClass.getDeclaredField(withPrefix);
return withPrefix; // has the underscore - return it!
}
catch (NoSuchFieldException error)
{
//
// no underscore - return regular name
//
return withLowerCase;
}
}
/**
*
* This method returns the unqualified (no package) name of the
* Java class (com.example.myapp.MyClass becomes "MyClass").
*
*/
public String serializedClass(Class theClass)
{
return ReflectUtils.getShortName(theClass);
}
/**
*
* This method strips an camel casing or underscore prefix from
* the given field name so it can be used as an XML element name.
*
*/
public String serializedMember(Class theClass, String fieldName)
{
if (fieldName.charAt(0) == '_')
fieldName = fieldName.substring(1);
fieldName = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
return super.serializedMember(theClass, fieldName);
}
}
/**
*
* MuseSerializerXStream is a version of XStream that uses our
* JavaFieldConverter class to map Java field names to XML elements.
*
* @author Dan Jemiolo (danj)
*
*/
private class MuseSerializerXStream extends XStream
{
public MuseSerializerXStream()
{
super(new DomDriver());
}
protected MapperWrapper wrapMapper(MapperWrapper next)
{
return new JavaFieldConverter(next);
}
}
}