/**********************************************************************************
*
* $Id: VersionedExternalizable.java 105077 2012-02-24 22:54:29Z ottenhoff@longsight.com $
*
***********************************************************************************
*
* Copyright (c) 2007, 2008 The Sakai Foundation
*
* Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.component.gradebook;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.mapper.Mapper;
/**
* Generic helper class for serializing Java objects to and from simply-formatted XML
* for archival and reconstitution across data definition versions.
* <p>
* XStream is used to handle the marshalling and unmarshalling work. The main
* addition is an "externalizableVersion" attribute on the POJO's top-level
* element. That attribute can then be checked for incompatibilities before
* reconstitution, and used to convert old data into its new form. (Currently,
* if there's a version mismatch and nothing is done about it, this class throws
* a ConversionException.)
* <p>
* Translation to and from XML can be handled either with the static "toXML"
* and "fromXML" methods, or through the Externalizable interface. The chief
* benefit of the static methods is that they (theoretically) give subclasses
* the ability to translate across versions using XSLT, and possibly even return
* an object of a different class than the original.
* <p>
* TODO For the functionality being checked in (site-to-site migration), this class
* is not strictly necessary. It's here on a speculative basis for upcoming
* import/archive/merge development.
*/
public abstract class VersionedExternalizable implements Externalizable {
public static String VERSION_ATTRIBUTE = "externalizableVersion";
/**
* @return non-null archivable version identifier for the object definition
*/
public abstract String getExternalizableVersion();
/**
* This XStream converter stores the externalizable version of the
* class as a Document-level attribute for easy access by translators.
* Right now, though, since we don't have any version translators, we
* don't try to reconstitute XML corresponding to anything but the current
* version.
*/
public static class Converter extends AbstractReflectionConverter {
public Converter(Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
}
public boolean canConvert(Class type) {
return VersionedExternalizable.class.isAssignableFrom(type);
}
public void marshal(Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
writer.addAttribute(VERSION_ATTRIBUTE, ((VersionedExternalizable)source).getExternalizableVersion());
super.marshal(source, writer, context);
}
public Object doUnmarshal(Object result, HierarchicalStreamReader reader, UnmarshallingContext context) {
String currentVersion = ((VersionedExternalizable)result).getExternalizableVersion();
String oldVersion = reader.getAttribute(VERSION_ATTRIBUTE);
if ((oldVersion == null) || !currentVersion.equals(oldVersion)) {
// This is one place we might put a version translation method in the future....
throw new ConversionException("Cannot convert " + result + " from version " + oldVersion + " to version " + currentVersion);
}
return super.doUnmarshal(result, reader, context);
}
}
protected static XStream getXStream() {
XStream xstream = new XStream(new DomDriver()); // does not require XPP3 library
xstream.registerConverter(new Converter(xstream.getMapper(), xstream.getReflectionProvider()));
return xstream;
}
public void readExternal(ObjectInput inputStream) throws IOException, ClassNotFoundException {
getXStream().fromXML(inputStream.readUTF(), this);
}
public void writeExternal(ObjectOutput outputStream) throws IOException {
outputStream.writeUTF(getXStream().toXML(this));
}
/**
* @param obj the Java object (usually a subclass of VersionedExternalizable) to describe
* as XML
* @return XML describing the object
*/
public static String toXml(Object obj) {
return getXStream().toXML(obj);
}
/**
* @param xmlString XML string (presumably created by this class) describing a Java object
* @return the Java object it describes
*/
public static Object fromXml(String xmlString) {
return getXStream().fromXML(xmlString);
}
}