diff --git a/java/XFlat/lib/XStream/xmlpull-1.1.3.1.jar b/java/XFlat/lib/XStream/xmlpull-1.1.3.1.jar new file mode 100644 index 0000000..cbc149d Binary files /dev/null and b/java/XFlat/lib/XStream/xmlpull-1.1.3.1.jar differ diff --git a/java/XFlat/lib/XStream/xstream-1.4.4.jar b/java/XFlat/lib/XStream/xstream-1.4.4.jar new file mode 100644 index 0000000..dcedd5a Binary files /dev/null and b/java/XFlat/lib/XStream/xstream-1.4.4.jar differ diff --git a/java/XFlat/lib/nblibraries.properties b/java/XFlat/lib/nblibraries.properties index 4e6848a..c7471c4 100644 --- a/java/XFlat/lib/nblibraries.properties +++ b/java/XFlat/lib/nblibraries.properties @@ -54,3 +54,7 @@ libs.mockito.classpath=\ ${base}/mockito/mockito-all-1.9.5-rc1.jar libs.mockito.src=\ ${base}/mockito/mockito-all-1.9.5-rc1-2.jar +libs.XStream.classpath=\ + ${base}/XStream/xstream-1.4.4.jar;\ + ${base}/XStream/xmlpull-1.1.3.1.jar +libs.XStream.displayName=XStream diff --git a/java/XFlat/nbproject/project.properties b/java/XFlat/nbproject/project.properties index 6cef4ee..02a1e91 100644 --- a/java/XFlat/nbproject/project.properties +++ b/java/XFlat/nbproject/project.properties @@ -35,7 +35,8 @@ javac.classpath=\ ${libs.Apache_Commons_Logging.classpath}:\ ${libs.Hamcrest_Matchers.classpath}:\ ${libs.JDOM-2.0.4.classpath}:\ - ${libs.JAXB-2.2.6.classpath} + ${libs.JAXB-2.2.6.classpath}:\ + ${libs.XStream.classpath} # Space-separated list of extra javac options javac.compilerargs= javac.deprecation=false diff --git a/java/XFlat/src/com/thoughtworks/xstream/io/xml/JDom2Reader.java b/java/XFlat/src/com/thoughtworks/xstream/io/xml/JDom2Reader.java new file mode 100644 index 0000000..1e6d3a0 --- /dev/null +++ b/java/XFlat/src/com/thoughtworks/xstream/io/xml/JDom2Reader.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2004, 2005, 2006 Joe Walnes. + * Copyright (C) 2006, 2007, 2009, 2011 XStream Committers. + * All rights reserved. + * + * The software in this package is published under the terms of the BSD + * style license a copy of which has been included with this distribution in + * the LICENSE.txt file. + * + * Created on 03. September 2004 by Joe Walnes + */ +package com.thoughtworks.xstream.io.xml; + +import com.thoughtworks.xstream.io.naming.NameCoder; +import java.util.List; +import org.jdom2.Attribute; +import org.jdom2.Document; +import org.jdom2.Element; + +/** + * @author Laurent Bihanic + */ +public class JDom2Reader extends AbstractDocumentReader { + + private Element currentElement; + + public JDom2Reader(Element root) { + super(root); + } + + public JDom2Reader(Document document) { + super(document.getRootElement()); + } + + /** + * @since 1.4 + */ + public JDom2Reader(Element root, NameCoder nameCoder) { + super(root, nameCoder); + } + + /** + * @since 1.4 + */ + public JDom2Reader(Document document, NameCoder nameCoder) { + super(document.getRootElement(), nameCoder); + } + + @Override + protected void reassignCurrentElement(Object current) { + currentElement = (Element) current; + } + + @Override + protected Object getParent() { + // JDOM 1.0: + return currentElement.getParentElement(); + } + + @Override + protected Object getChild(int index) { + return currentElement.getChildren().get(index); + } + + @Override + protected int getChildCount() { + return currentElement.getChildren().size(); + } + + @Override + public String getNodeName() { + return decodeNode(currentElement.getName()); + } + + @Override + public String getValue() { + return currentElement.getText(); + } + + @Override + public String getAttribute(String name) { + return currentElement.getAttributeValue(encodeAttribute(name)); + } + + @Override + public String getAttribute(int index) { + return ((Attribute) currentElement.getAttributes().get(index)).getValue(); + } + + @Override + public int getAttributeCount() { + return currentElement.getAttributes().size(); + } + + @Override + public String getAttributeName(int index) { + return decodeAttribute(((Attribute) currentElement.getAttributes().get(index)).getQualifiedName()); + } + + @Override + public String peekNextChild() { + List list = currentElement.getChildren(); + if (null == list || list.isEmpty()) { + return null; + } + return decodeNode(((Element) list.get(0)).getName()); + } + +} diff --git a/java/XFlat/src/com/thoughtworks/xstream/io/xml/JDom2Writer.java b/java/XFlat/src/com/thoughtworks/xstream/io/xml/JDom2Writer.java new file mode 100644 index 0000000..6b41d97 --- /dev/null +++ b/java/XFlat/src/com/thoughtworks/xstream/io/xml/JDom2Writer.java @@ -0,0 +1,96 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.thoughtworks.xstream.io.xml; + +import com.thoughtworks.xstream.io.naming.NameCoder; +import java.util.List; +import org.jdom2.DefaultJDOMFactory; +import org.jdom2.Element; +import org.jdom2.JDOMFactory; + +/** + * + * @author Gordon + */ +public class JDom2Writer extends AbstractDocumentWriter { + private final JDOMFactory documentFactory; + + public JDom2Writer( + final Element container, final JDOMFactory factory, + final NameCoder nameCoder) { + super(container, nameCoder); + documentFactory = factory; + } + + public JDom2Writer(final Element container, final JDOMFactory factory) { + this(container, factory, new XmlFriendlyNameCoder()); + } + public JDom2Writer(final JDOMFactory factory, final NameCoder nameCoder) { + this(null, factory, nameCoder); + } + + public JDom2Writer(final JDOMFactory factory) { + this(null, factory); + } + + public JDom2Writer(final Element container, final NameCoder nameCoder) { + this(container, new DefaultJDOMFactory(), nameCoder); + } + + + public JDom2Writer(final Element container) { + this(container, new DefaultJDOMFactory()); + } + + public JDom2Writer() { + this(new DefaultJDOMFactory()); + } + + @Override + protected Object createNode(final String name) { + final Element element = documentFactory.element(encodeNode(name)); + final Element parent = top(); + if (parent != null) { + parent.addContent(element); + } + return element; + } + + @Override + public void setValue(final String text) { + top().addContent(documentFactory.text(text)); + } + + @Override + public void addAttribute(final String key, final String value) { + top().setAttribute(documentFactory.attribute(encodeAttribute(key), value)); + } + + private Element top() { + return (Element)getCurrent(); + } + + /** + * Casts the top level nodes as a list of JDOM2 elements. + * @return + */ + @Override + public List getTopLevelNodes(){ + return (List)super.getTopLevelNodes(); + } + + /** + * Gets the first top level node, or null if none exists. + * Useful if you expect only one top level node. + * @return The first top level node, or null. + */ + public Element getTopLevelNode(){ + List top = super.getTopLevelNodes(); + if(top.isEmpty()) + return null; + + return top.get(0); + } +} diff --git a/java/XFlat/src/org/xflatdb/xflat/convert/converters/JavaBeansPojoConverter.java b/java/XFlat/src/org/xflatdb/xflat/convert/converters/JavaBeansPojoConverter.java new file mode 100644 index 0000000..610a6e5 --- /dev/null +++ b/java/XFlat/src/org/xflatdb/xflat/convert/converters/JavaBeansPojoConverter.java @@ -0,0 +1,176 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.xflatdb.xflat.convert.converters; + +import java.beans.XMLDecoder; +import java.beans.XMLEncoder; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.input.SAXBuilder; +import org.jdom2.output.XMLOutputter; +import org.xflatdb.xflat.convert.ConversionException; +import org.xflatdb.xflat.convert.ConversionNotSupportedException; +import org.xflatdb.xflat.convert.ConversionService; +import org.xflatdb.xflat.convert.Converter; +import org.xflatdb.xflat.convert.PojoConverter; + +/** + * This PojoConverter maps Java beans to XML using {@link java.beans.XMLEncoder}. + * @author Gordon + */ +public class JavaBeansPojoConverter implements PojoConverter { + + @Override + public ConversionService extend(ConversionService service) { + return new JavaBeansConversionService(service); + } + + private static class JavaBeansConversionService implements ConversionService { + + ConversionService base; + + public JavaBeansConversionService(ConversionService base){ + this.base = base; + } + + @Override + public boolean canConvert(Class source, Class target) { + if(!base.canConvert(source, target)){ + + if(Element.class.equals(target)) + makeConverters(source); + else if(Element.class.equals(source)){ + makeConverters(target); + } + else{ + //can't convert + return false; + } + } + //else the base can convert + + return true; + } + + @Override + public T convert(Object source, Class target) throws ConversionException { + try{ + return base.convert(source, target); + } + catch(ConversionNotSupportedException ex){ + if(source == null){ + throw ex; + } + + //the base class does not support the conversion - try to make converters + if(Element.class.equals(target)) + makeConverters(source.getClass()); + else if(Element.class.equals(source.getClass())){ + makeConverters(target); + } + else{ + //can't convert + throw ex; + } + + //try again now that we successfully made converters + return base.convert(source, target); + } + } + + @Override + public void addConverter(Class sourceType, Class targetType, Converter converter) { + base.addConverter(sourceType, targetType, converter); + } + + @Override + public void removeConverter(Class sourceType, Class targetType) { + base.removeConverter(sourceType, targetType); + } + + private void makeConverters(Class clazz){ + base.addConverter(clazz, Element.class, XMLEncoderConverter); + base.addConverter(Element.class, clazz, getDecoder(clazz)); + } + } + + + + private static Converter XMLEncoderConverter = new Converter() { + + @Override + public Element convert(Object source) throws ConversionException { + + byte[] bytes; + try(ByteArrayOutputStream os = new ByteArrayOutputStream()){ + + try(XMLEncoder encoder = new XMLEncoder(os)){ + encoder.writeObject(source); + } + + bytes = os.toByteArray(); + } + catch(IOException ex){ + throw new ConversionException("Error converting object of class " + source.getClass(), ex); + } + + try(ByteArrayInputStream is = new ByteArrayInputStream(bytes)){ + + Document doc = new SAXBuilder().build(is); + + return doc.getRootElement().getChild("object").detach(); + } + catch(JDOMException | IOException ex){ + throw new ConversionException("Error converting object of class " + source.getClass(), ex); + } + } + }; + + private static Converter getDecoder(Class clazz){ + return new Converter(){ + + final String version = System.getProperty("java.version"); + + @Override + public U convert(Element source) throws ConversionException { + byte[] bytes; + try(ByteArrayOutputStream os = new ByteArrayOutputStream()){ + + Document doc = new Document(); + doc.setRootElement(new Element("java") + .setAttribute("version", version) + .setAttribute("class", "java.beans.XMLDecoder")); + + doc.getRootElement().addContent(source.detach()); + + XMLOutputter outputter = new XMLOutputter(); + outputter.output(doc, os); + + bytes = os.toByteArray(); + } + catch(IOException ex){ + throw new ConversionException("Error reading object of class " + source.getClass(), ex); + } + + try(ByteArrayInputStream is = new ByteArrayInputStream(bytes)){ + + try(XMLDecoder decoder = new XMLDecoder(is)){ + return (U)decoder.readObject(); + } + } + catch(IOException ex){ + throw new ConversionException("Error reading object of class " + source.getClass(), ex); + } + } + }; + } + + +} diff --git a/java/XFlat/src/org/xflatdb/xflat/convert/converters/XStreamPojoConverter.java b/java/XFlat/src/org/xflatdb/xflat/convert/converters/XStreamPojoConverter.java new file mode 100644 index 0000000..be2bb53 --- /dev/null +++ b/java/XFlat/src/org/xflatdb/xflat/convert/converters/XStreamPojoConverter.java @@ -0,0 +1,288 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.xflatdb.xflat.convert.converters; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.JDom2Reader; +import com.thoughtworks.xstream.io.xml.JDom2Writer; +import com.thoughtworks.xstream.mapper.CannotResolveClassException; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.jdom2.Element; +import org.xflatdb.xflat.convert.ConversionException; +import org.xflatdb.xflat.convert.ConversionNotSupportedException; +import org.xflatdb.xflat.convert.ConversionService; +import org.xflatdb.xflat.convert.Converter; +import org.xflatdb.xflat.convert.PojoConverter; + +/** + * A PojoConverter that uses XStream for marshalling and unmarshalling. + * @author Gordon + */ +public class XStreamPojoConverter implements PojoConverter { + + + private final XStream xstream; + /** + * Gets the XStream instance that performs the POJO mapping. + * @return + */ + public XStream getXStream(){ + return xstream; + } + + private final boolean isThreadSafe; + + private final ReadWriteLock lock; + + /** + * Creates a new XStream instance with the default options. + * This instance is not thread safe on serializing new objects, + * since it relies on annotation processing. + */ + public XStreamPojoConverter(){ + this.xstream = new XStream(); + this.isThreadSafe = false; + lock = new ReentrantReadWriteLock(); + } + + /** + * Creates a pojo converter that uses the given configured XStream instance. + *

+ * From the XStream documentation: + *

+     * The XStream instance is thread-safe. That is, once the XStream instance has been created and configured,
+     * it may be shared across multiple threads allowing objects to be serialized/deserialized concurrently. 
+     * Note, that this only applies if annotations are not auto-detected on -the-fly.
+     * 
+ * Therefore, if you are using annotations to control serialization, ensure + * that you set "isThreadSafe" to false. + * @param xstream + */ + public XStreamPojoConverter(XStream xstream, boolean isThreadSafe){ + this.xstream = xstream; + this.isThreadSafe = isThreadSafe; + if(!isThreadSafe){ + lock = new ReentrantReadWriteLock(); + } + else{ + lock = null; + } + } + + @Override + public ConversionService extend(ConversionService service) { + return new XStreamConversionService(service); + } + + private class XStreamConversionService implements ConversionService { + + ConversionService base; + + public XStreamConversionService(ConversionService base){ + this.base = base; + } + + @Override + public boolean canConvert(Class source, Class target) { + if(!base.canConvert(source, target)){ + + if(Element.class.equals(target)) + return true; + else if(Element.class.equals(source)){ + return true; + } + else{ + //can't convert + return false; + } + } + //else the base can convert + + return true; + } + + @Override + public T convert(Object source, Class target) throws ConversionException { + try{ + return base.convert(source, target); + } + catch(ConversionNotSupportedException ex){ + if(source == null){ + throw ex; + } + + //the base class does not support the conversion - try to make converters + if(Element.class.equals(target)) + return makeConvertersAndRetry(source.getClass(), source, target); + else if(Element.class.equals(source.getClass())){ + return makeConvertersAndRetry(target, source, target); + } + else{ + //can't convert + throw ex; + } + } + } + + @Override + public void addConverter(Class sourceType, Class targetType, Converter converter) { + base.addConverter(sourceType, targetType, converter); + } + + @Override + public void removeConverter(Class sourceType, Class targetType) { + base.removeConverter(sourceType, targetType); + } + + private T makeConvertersAndRetry(Class clazz, Object source, Class target) throws ConversionException { + if(!isThreadSafe){ + //need to synchronize + lock.writeLock().lock(); + try{ + //doublecheck if another thread got here first + if(!base.canConvert(clazz, Element.class)){ + //any other threads will stall on the readlock + base.addConverter(clazz, Element.class, threadSafeMarshallingConverter); + XStreamThreadSafeUnmarshallingConverter unmarshaller = new XStreamThreadSafeUnmarshallingConverter<>(clazz); + base.addConverter(Element.class, clazz, unmarshaller); + + //process the annotations + xstream.processAnnotations(clazz); + } + } + finally{ + lock.writeLock().unlock(); + } + + //invoke the converters via the base + return base.convert(source, target); + } + else{ + //no need to synchronize, the XStream class is thread safe. + //Since we've said thread-safe, we also have no need to process annotations. + + base.addConverter(clazz, Element.class, marshallingConverter); + XStreamUnmarshallingConverter unmarshaller = new XStreamUnmarshallingConverter<>(clazz); + base.addConverter(Element.class, clazz, unmarshaller); + + if(Element.class.equals(target)) + return (T)marshallingConverter.convert(source); + else { + return (T)unmarshaller.convert((Element)source); + } + } + } + + + } + + //marshalling singletons + private final Converter marshallingConverter = new XStreamMarshallingConverter(); + + private final Converter threadSafeMarshallingConverter = new XStreamThreadSafeMarshallingConverter(); + + /** + * A converter that performs marshalling to XML + */ + private class XStreamMarshallingConverter implements Converter{ + + @Override + public Element convert(Object source) throws ConversionException { + + JDom2Writer writer = new JDom2Writer(); + try{ + xstream.marshal(source, writer); + } + catch(Exception ex){ + throw new ConversionException("Unable to marshal " + source.getClass() + " to xml", ex); + } + + return writer.getTopLevelNode(); + } + } + + /** + * Wraps the marshalling converter in a global readlock for the XStream instance, + * since it is not thread safe when reading annotations. + */ + private class XStreamThreadSafeMarshallingConverter implements Converter{ + + @Override + public Element convert(Object source) throws ConversionException { + + lock.readLock().lock(); + try{ + return marshallingConverter.convert(source); + } + finally{ + lock.readLock().unlock(); + } + } + } + + /** + * A converter that performs unmarshalling from XML + * @param + */ + private class XStreamUnmarshallingConverter implements Converter{ + + Class clazz; + + public XStreamUnmarshallingConverter(Class clazz){ + this.clazz = clazz; + } + + @Override + public T convert(Element source) throws ConversionException { + + JDom2Reader reader = new JDom2Reader(source); + try{ + T ret = (T)xstream.unmarshal(reader); + return ret; + + } + catch(Exception ex){ + throw new ConversionException("Unable to unmarshal " + clazz + " from xml", ex); + } + } + + } + + /** + * Wraps the unmarshalling converter in a global readlock for the XStream instance, + * since it is not thread safe when reading annotations. + * @param + */ + private class XStreamThreadSafeUnmarshallingConverter implements Converter{ + + Class clazz; + + XStreamUnmarshallingConverter innerConverter; + + public XStreamThreadSafeUnmarshallingConverter(Class clazz){ + this.clazz = clazz; + this.innerConverter = new XStreamUnmarshallingConverter<>(clazz); + } + + @Override + public T convert(Element source) throws ConversionException { + + lock.readLock().lock(); + try{ + return innerConverter.convert(source); + } + finally{ + lock.readLock().unlock(); + } + } + + } + + + + + +} diff --git a/java/XFlat/src/org/xflatdb/xflat/db/XFlatDatabase.java b/java/XFlat/src/org/xflatdb/xflat/db/XFlatDatabase.java index cbc721f..df3832e 100644 --- a/java/XFlat/src/org/xflatdb/xflat/db/XFlatDatabase.java +++ b/java/XFlat/src/org/xflatdb/xflat/db/XFlatDatabase.java @@ -172,6 +172,29 @@ public void configureTable(String tableName, TableConfig config){ private Log log = LogFactory.getLog(getClass()); + private AtomicBoolean pojoConverterLoaded = new AtomicBoolean(false); + private volatile PojoConverter pojoConverter; + /** + * Gets the PojoConverter that has been used to extend the database's conversion service. + * This overrides any PojoConverter that was defined in the Database Configuration. + * @param converter The converter that should extend the database's conversion service. + */ + public PojoConverter getPojoConverter(){ + return pojoConverter; + } + + /** + * Extends the database's conversion service with the given PojoConverter. + * This overrides any PojoConverter that was defined in the Database Configuration. + * @param converter The converter that should extend the database's conversion service. + */ + public void setPojoConverter(PojoConverter converter){ + synchronized(this){ + this.conversionService = converter.extend(conversionService); + this.pojoConverter = converter; + } + } + /** * Creates a new database in the given directory. * @param directory The flat-file directory in which tables should be stored. @@ -421,17 +444,7 @@ private void update(){ } - /** - * Extends the database's conversion service with the given PojoConverter. - * It does this by invoking {@link PojoConverter#extend(org.xflatdb.xflat.convert.ConversionService) } - * using the database's current conversion service, in a synchronized context. - * @param extender The extender that should extend the database's conversion service. - */ - public void extendConversionService(PojoConverter extender){ - synchronized(this){ - this.conversionService = extender.extend(conversionService); - } - } + @Override @@ -520,16 +533,16 @@ private TableMetadata getMetadata(Class type, String name){ return table; } - private AtomicBoolean pojoConverterLoaded = new AtomicBoolean(false); - private volatile PojoConverter pojoConverter; - public PojoConverter getPojoConverter(){ - return pojoConverter; - } private void loadPojoConverter() throws ClassNotFoundException, InstantiationException, IllegalAccessException{ if(!pojoConverterLoaded.compareAndSet(false, true)){ return; } + if(this.pojoConverter != null){ + //the user set a pojo converter via the Set method. + return; + } + Class converter; converter = this.getClass().getClassLoader().loadClass(this.config.getPojoConverterClass()); @@ -542,9 +555,9 @@ private void loadPojoConverter() throws ClassNotFoundException, InstantiationExc if(log.isTraceEnabled()) log.trace(String.format("Activating Pojo Converter %s", converter.getName())); - this.pojoConverter = (PojoConverter)converter.newInstance(); + PojoConverter instance = (PojoConverter)converter.newInstance(); - this.extendConversionService(this.pojoConverter); + this.setPojoConverter(instance); } /** diff --git a/java/XFlat/src/org/xflatdb/xflat/query/XPathQuery.java b/java/XFlat/src/org/xflatdb/xflat/query/XPathQuery.java index 606f37d..b41cc53 100644 --- a/java/XFlat/src/org/xflatdb/xflat/query/XPathQuery.java +++ b/java/XFlat/src/org/xflatdb/xflat/query/XPathQuery.java @@ -44,7 +44,10 @@ * are convertible to the query's value type, and then the converted value * type is compared to the given value to see if the row matches the query. *

- * The row is matched to the query if any one + * The row is matched to the query if any one of the nodes selected by + * the XPath expression match the query. This means that an Equals query + * which selects the individual item elements of a list will match if any item + * in the list matches the query. * @author Gordon */ public class XPathQuery { diff --git a/java/XFlat/test/org/xflatdb/xflat/convert/converters/JavaBeansPojoMapperTest.java b/java/XFlat/test/org/xflatdb/xflat/convert/converters/JavaBeansPojoMapperTest.java new file mode 100644 index 0000000..7d171dc --- /dev/null +++ b/java/XFlat/test/org/xflatdb/xflat/convert/converters/JavaBeansPojoMapperTest.java @@ -0,0 +1,140 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.xflatdb.xflat.convert.converters; + +import java.beans.XMLDecoder; +import java.beans.XMLEncoder; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import org.hamcrest.Matchers; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.input.SAXBuilder; +import org.jdom2.output.XMLOutputter; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import test.Foo; +import static org.junit.Assert.*; +import org.xflatdb.xflat.convert.ConversionService; +import org.xflatdb.xflat.convert.DefaultConversionService; + +/** + * + * @author Gordon + */ +public class JavaBeansPojoMapperTest { + + public JavaBeansPojoMapperTest() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + @Test + public void testXmlEncoder_CanEncodeObject_EncodesAsXml() throws Exception { + System.out.println("testXmlEncoder_CanEncodeObject_EncodesAsXml"); + + + Foo foo = new Foo(); + foo.setId("junk"); + foo.fooInt = 17; + + byte[] bytes; + try(ByteArrayOutputStream os = new ByteArrayOutputStream()){ + + XMLEncoder encoder = new XMLEncoder(os); + encoder.writeObject(foo); + + + encoder.close(); + + bytes = os.toByteArray(); + } + + System.out.write(bytes); + + SAXBuilder builder = new SAXBuilder(); + XMLOutputter outputter = new XMLOutputter(); + + Document doc; + try(ByteArrayInputStream is = new ByteArrayInputStream(bytes)){ + doc = builder.build(is); + } + + assertNotNull(doc); + + outputter.output(doc, System.out); + + } + + @Test + public void testXmlEncoderDecoder_RoundTrip() throws Exception { + System.out.println("testXmlEncoderDecoder_RoundTrip"); + + Foo foo = new Foo(); + foo.setId("junk"); + foo.fooInt = 17; + + byte[] bytes; + try(ByteArrayOutputStream os = new ByteArrayOutputStream()){ + + XMLEncoder encoder = new XMLEncoder(os); + encoder.writeObject(foo); + + + encoder.close(); + + bytes = os.toByteArray(); + } + + System.out.write(bytes); + + Foo foo2; + try(ByteArrayInputStream is = new ByteArrayInputStream(bytes)){ + + XMLDecoder decoder = new XMLDecoder(is); + + foo2 = (Foo)decoder.readObject(); + + } + + assertNotNull(foo2); + + assertThat(foo, Matchers.equalTo(foo2)); + } + + @Test + public void testExtendConversionService_ConversionServiceCanNowConvertToJDOM() throws Exception { + System.out.println("testExtendConversionService_ConversionServiceCanNowConvertToJDOM"); + + + ConversionService service = new DefaultConversionService(); + + JavaBeansPojoConverter instance = new JavaBeansPojoConverter(); + //act + service = instance.extend(service); + + + assertTrue(service.canConvert(Foo.class, Element.class)); + assertTrue(service.canConvert(Element.class, Foo.class)); + + Foo foo = new Foo(); + foo.fooInt = 12; + foo.setId("junk some more"); + + Element element = service.convert(foo, Element.class); + new XMLOutputter().output(element, System.out); + + Foo foo2 = service.convert(element, Foo.class); + + assertThat(foo2, Matchers.equalTo(foo)); + } +} diff --git a/java/XFlat/test/org/xflatdb/xflat/convert/converters/XStreamPojoConverterTest.java b/java/XFlat/test/org/xflatdb/xflat/convert/converters/XStreamPojoConverterTest.java new file mode 100644 index 0000000..03eaf4a --- /dev/null +++ b/java/XFlat/test/org/xflatdb/xflat/convert/converters/XStreamPojoConverterTest.java @@ -0,0 +1,165 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.xflatdb.xflat.convert.converters; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.JDom2Reader; +import com.thoughtworks.xstream.io.xml.JDom2Writer; +import java.util.List; +import org.jdom2.Element; +import org.jdom2.output.XMLOutputter; +import org.junit.Test; +import org.xflatdb.xflat.convert.ConversionException; +import org.xflatdb.xflat.convert.ConversionService; +import org.xflatdb.xflat.convert.DefaultConversionService; +import test.Baz; +import test.Foo; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * + * @author Gordon + */ +public class XStreamPojoConverterTest { + + @Test + public void testXStream_PojoToXml_MarshalsCorrectly() throws Exception { + System.out.println("testXStream_PojoToXml_MarshalsCorrectly"); + + JDom2Writer writer = new JDom2Writer(); + + Foo foo = new Foo(); + foo.fooInt = 17; + foo.setId("blah"); + + XStream xstream = new XStream(); + + xstream.marshal(foo, writer); + + Element element = writer.getTopLevelNode(); + + XMLOutputter outputter = new XMLOutputter(); + System.out.println(outputter.outputString(element)); + + assertNotNull(element); + assertEquals("test.Foo", element.getName()); + assertEquals("17", element.getChildText("fooInt")); + } + + @Test + public void testXStream_XmlToPojo_UnmarshalsCorrectly() throws Exception { + System.out.println("testXStream_XmlToPojo_UnmarshalsCorrectly"); + + Element element = new Element("test.Foo"); + element.addContent(new Element("id").setText("blah")); + element.addContent(new Element("fooInt").setText("23")); + + JDom2Reader reader = new JDom2Reader(element); + + XStream xstream = new XStream(); + + Foo foo = (Foo)xstream.unmarshal(reader); + + assertNotNull(foo); + assertEquals("blah", foo.getId()); + assertEquals(23, foo.fooInt); + } + + @Test + public void testCanConvert_AnnotatedClass_CanConvert() throws Exception { + System.out.println("testCanConvert_AnnotatedClass_CanConvert"); + + ConversionService mockConversion = mock(ConversionService.class); + when(mockConversion.canConvert(any(Class.class), any(Class.class))) + .thenReturn(false); + + ConversionService instance = new XStreamPojoConverter().extend(mockConversion); + + boolean canConvertToElement = instance.canConvert(Baz.class, Element.class); + boolean canConvertFromElement = instance.canConvert(Element.class, Baz.class); + + assertTrue("Should be able to convert annotated class", canConvertFromElement); + assertTrue("Should be able to convert annotated class", canConvertToElement); + }//end testCanConvert_AnnotatedClass_CanConvert + + @Test + public void testCanConvert_ConvertingToOtherType_ReturnsFalse() throws Exception { + System.out.println("testCanConvert_ConvertingToOtherType_ReturnsFalse"); + + + ConversionService mockConversion = mock(ConversionService.class); + when(mockConversion.canConvert(any(Class.class), any(Class.class))) + .thenReturn(false); + + ConversionService instance = new XStreamPojoConverter().extend(mockConversion); + + boolean convertToFoo = instance.canConvert(Baz.class, Foo.class); + boolean convertFromFoo = instance.canConvert(Foo.class, Baz.class); + + assertFalse("Should not convert to foo", convertToFoo); + assertFalse("Should not convert from foo", convertFromFoo); + }//end testCanConvert_ConvertingToOtherType_ReturnsFalse + + @Test + public void testConvert_AnnotatedClass_Marshals() throws Exception { + System.out.println("testConvert_AnnotatedClass_Marshals"); + + ConversionService base = new DefaultConversionService(); + + ConversionService instance = new XStreamPojoConverter().extend(base); + + Baz baz = new Baz(); + baz.setAttrInt(32); + baz.getTestData().add("test 1"); + baz.getTestData().add("test 2"); + baz.setId("test id"); + + //ACT + Element converted = instance.convert(baz, Element.class); + + System.out.println(new XMLOutputter().outputString(converted)); + + assertEquals("should have correct name", "baz", converted.getName()); + assertEquals("should have correct attr value", "32", converted.getAttributeValue("attrInt")); + + List children = converted.getChildren("testData"); + assertEquals("should have list data", 2, children.size()); + assertEquals("should have list data", "test 1", children.get(0).getText()); + assertEquals("should have list data", "test 2", children.get(1).getText()); + + assertNull("should not have ID element", converted.getChild("id")); + + }//end testConvert_AnnotatedClass_Marshals + + @Test + public void testConvert_AnnotatedClass_Unmarshals() throws Exception { + System.out.println("testConvert_AnnotatedClass_Unmarshals"); + + ConversionService base = new DefaultConversionService(); + + ConversionService instance = new XStreamPojoConverter().extend(base); + + Element baz = new Element("baz"); + baz.setAttribute("attrInt", "56"); + Element listData = new Element("testData"); + listData.setText("data 1"); + baz.addContent(listData); + listData = new Element("testData"); + listData.setText("data 2"); + baz.addContent(listData); + + //ACT + Baz converted = instance.convert(baz, Baz.class); + + assertEquals("should have correct attr value", 56, converted.getAttrInt()); + + assertEquals("should have list data", 2, converted.getTestData().size()); + assertEquals("should have list data", "data 1", converted.getTestData().get(0)); + assertEquals("should have list data", "data 2", converted.getTestData().get(1)); + + assertNull("should not have ID element", converted.getId()); + }//end testConvert_AnnotatedClass_Unmarshals +} diff --git a/java/XFlat/test/org/xflatdb/xflat/engine/IdShardedEngineTest.java b/java/XFlat/test/org/xflatdb/xflat/engine/IdShardedEngineTest.java index 7f4835f..9f246a7 100644 --- a/java/XFlat/test/org/xflatdb/xflat/engine/IdShardedEngineTest.java +++ b/java/XFlat/test/org/xflatdb/xflat/engine/IdShardedEngineTest.java @@ -89,7 +89,7 @@ public DatabaseState getState(){ } }; - db.extendConversionService(new PojoConverter(){ + db.setPojoConverter(new PojoConverter(){ @Override public ConversionService extend(ConversionService service) { return conversionService; diff --git a/java/XFlat/test/test/Baz.java b/java/XFlat/test/test/Baz.java index 092fe58..92259a6 100644 --- a/java/XFlat/test/test/Baz.java +++ b/java/XFlat/test/test/Baz.java @@ -15,6 +15,10 @@ */ package test; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamAsAttribute; +import com.thoughtworks.xstream.annotations.XStreamImplicit; +import com.thoughtworks.xstream.annotations.XStreamOmitField; import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.XmlAttribute; @@ -28,12 +32,14 @@ * @author gordon */ @XmlRootElement +@XStreamAlias("baz") public class Baz { + @XStreamOmitField private String id; @Id - @XmlTransient + @XmlTransient public String getId() { return this.id; } @@ -42,6 +48,7 @@ public void setId(String id) { this.id = id; } + @XStreamImplicit(itemFieldName="testData") private List testData = new ArrayList<>(); @XmlElement @@ -49,8 +56,10 @@ public List getTestData() { return this.testData; } + @XStreamAsAttribute private int attrInt; - @XmlAttribute + + @XmlAttribute public int getAttrInt() { return this.attrInt; }