001: /**********************************************************************************
002: *
003: * $Id: VersionedExternalizable.java 20758 2007-01-29 18:07:03Z ray@media.berkeley.edu $
004: *
005: ***********************************************************************************
006: *
007: * Copyright (c) 2007 The Regents of the University of California
008: *
009: * Licensed under the Educational Community License, Version 1.0 (the "License");
010: * you may not use this file except in compliance with the License.
011: * You may obtain a copy of the License at
012: *
013: * http://www.opensource.org/licenses/ecl1.php
014: *
015: * Unless required by applicable law or agreed to in writing, software
016: * distributed under the License is distributed on an "AS IS" BASIS,
017: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018: * See the License for the specific language governing permissions and
019: * limitations under the License.
020: *
021: **********************************************************************************/package org.sakaiproject.component.gradebook;
022:
023: import java.io.Externalizable;
024: import java.io.IOException;
025: import java.io.ObjectInput;
026: import java.io.ObjectOutput;
027:
028: import com.thoughtworks.xstream.XStream;
029: import com.thoughtworks.xstream.converters.ConversionException;
030: import com.thoughtworks.xstream.converters.MarshallingContext;
031: import com.thoughtworks.xstream.converters.UnmarshallingContext;
032: import com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter;
033: import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
034: import com.thoughtworks.xstream.io.HierarchicalStreamReader;
035: import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
036: import com.thoughtworks.xstream.io.xml.DomDriver;
037: import com.thoughtworks.xstream.mapper.Mapper;
038:
039: /**
040: * Generic helper class for serializing Java objects to and from simply-formatted XML
041: * for archival and reconstitution across data definition versions.
042: * <p>
043: * XStream is used to handle the marshalling and unmarshalling work. The main
044: * addition is an "externalizableVersion" attribute on the POJO's top-level
045: * element. That attribute can then be checked for incompatibilities before
046: * reconstitution, and used to convert old data into its new form. (Currently,
047: * if there's a version mismatch and nothing is done about it, this class throws
048: * a ConversionException.)
049: * <p>
050: * Translation to and from XML can be handled either with the static "toXML"
051: * and "fromXML" methods, or through the Externalizable interface. The chief
052: * benefit of the static methods is that they (theoretically) give subclasses
053: * the ability to translate across versions using XSLT, and possibly even return
054: * an object of a different class than the original.
055: * <p>
056: * TODO For the functionality being checked in (site-to-site migration), this class
057: * is not strictly necessary. It's here on a speculative basis for upcoming
058: * import/archive/merge development.
059: */
060: public abstract class VersionedExternalizable implements Externalizable {
061: public static String VERSION_ATTRIBUTE = "externalizableVersion";
062:
063: /**
064: * @return non-null archivable version identifier for the object definition
065: */
066: public abstract String getExternalizableVersion();
067:
068: /**
069: * This XStream converter stores the externalizable version of the
070: * class as a Document-level attribute for easy access by translators.
071: * Right now, though, since we don't have any version translators, we
072: * don't try to reconstitute XML corresponding to anything but the current
073: * version.
074: */
075: public static class Converter extends AbstractReflectionConverter {
076: public Converter(Mapper mapper,
077: ReflectionProvider reflectionProvider) {
078: super (mapper, reflectionProvider);
079: }
080:
081: public boolean canConvert(Class type) {
082: return VersionedExternalizable.class.isAssignableFrom(type);
083: }
084:
085: public void marshal(Object source,
086: final HierarchicalStreamWriter writer,
087: final MarshallingContext context) {
088: writer.addAttribute(VERSION_ATTRIBUTE,
089: ((VersionedExternalizable) source)
090: .getExternalizableVersion());
091: super .marshal(source, writer, context);
092: }
093:
094: public Object doUnmarshal(Object result,
095: HierarchicalStreamReader reader,
096: UnmarshallingContext context) {
097: String currentVersion = ((VersionedExternalizable) result)
098: .getExternalizableVersion();
099: String oldVersion = reader.getAttribute(VERSION_ATTRIBUTE);
100: if ((oldVersion == null)
101: || !currentVersion.equals(oldVersion)) {
102: // This is one place we might put a version translation method in the future....
103: throw new ConversionException("Cannot convert "
104: + result + " from version " + oldVersion
105: + " to version " + currentVersion);
106: }
107: return super .doUnmarshal(result, reader, context);
108: }
109: }
110:
111: protected static XStream getXStream() {
112: XStream xstream = new XStream(new DomDriver()); // does not require XPP3 library
113: xstream.registerConverter(new Converter(xstream.getMapper(),
114: xstream.getReflectionProvider()));
115: return xstream;
116: }
117:
118: public void readExternal(ObjectInput inputStream)
119: throws IOException, ClassNotFoundException {
120: getXStream().fromXML(inputStream.readUTF(), this );
121: }
122:
123: public void writeExternal(ObjectOutput outputStream)
124: throws IOException {
125: outputStream.writeUTF(getXStream().toXML(this ));
126: }
127:
128: /**
129: * @param obj the Java object (usually a subclass of VersionedExternalizable) to describe
130: * as XML
131: * @return XML describing the object
132: */
133: public static String toXml(Object obj) {
134: return getXStream().toXML(obj);
135: }
136:
137: /**
138: * @param xmlString XML string (presumably created by this class) describing a Java object
139: * @return the Java object it describes
140: */
141: public static Object fromXml(String xmlString) {
142: return getXStream().fromXML(xmlString);
143: }
144:
145: }
|