package me.prettyprint.cassandra.serializers;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import me.prettyprint.cassandra.serializers.AbstractSerializer;
import me.prettyprint.hector.api.exceptions.HectorSerializationException;
/**
* Serializes Objects using Jaxb. An instance of this class may only serialize
* JAXB compatible objects of classes known to its configured context.
*
* @author shuzhang0@gmail.com
*/
public class JaxbSerializer extends AbstractSerializer<Object> {
/** The cached per-thread marshaller. */
private ThreadLocal<Marshaller> marshaller;
/** The cached per-thread unmarshaller. */
private ThreadLocal<Unmarshaller> unmarshaller;
/**
* Lazily initialized singleton factory for producing default
* XMLStreamWriters.
*/
private static XMLOutputFactory outputFactory;
/**
* Lazily initialized singleton factory for producing default
* XMLStreamReaders.
*/
private static XMLInputFactory inputFactory;
/**
* Constructor.
*
* @param serializableClasses
* List of classes which can be serialized by this instance. Note
* that concrete classes directly referenced by any class in the list
* will also be serializable through this instance.
*/
public JaxbSerializer(final Class<?>... serializableClasses) {
marshaller = new ThreadLocal<Marshaller>() {
@Override
protected Marshaller initialValue() {
try {
return JAXBContext.newInstance(serializableClasses)
.createMarshaller();
} catch (JAXBException e) {
throw new IllegalArgumentException(
"Classes to serialize are not JAXB compatible.", e);
}
}
};
unmarshaller = new ThreadLocal<Unmarshaller>() {
@Override
protected Unmarshaller initialValue() {
try {
return JAXBContext.newInstance(serializableClasses)
.createUnmarshaller();
} catch (JAXBException e) {
throw new IllegalArgumentException(
"Classes to serialize are not JAXB compatible.", e);
}
}
};
}
/** {@inheritDoc} */
@Override
public ByteBuffer toByteBuffer(Object obj) {
if (obj == null) {
return null;
}
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try {
XMLStreamWriter writer = createStreamWriter(buffer);
marshaller.get().marshal(obj, writer);
writer.flush();
writer.close();
} catch (JAXBException e) {
throw new HectorSerializationException("Object to serialize " + obj
+ " does not seem compatible with the configured JaxbContext;"
+ " note this Serializer works only with JAXBable objects.", e);
} catch (XMLStreamException e) {
throw new HectorSerializationException(
"Exception occurred writing XML stream.", e);
}
return ByteBuffer.wrap(buffer.toByteArray());
}
/** {@inheritDoc} */
@Override
public Object fromByteBuffer(ByteBuffer bytes) {
if (bytes == null || !bytes.hasRemaining()) {
return null;
}
int l = bytes.remaining();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes.array(),
bytes.arrayOffset() + bytes.position(), l);
try {
XMLStreamReader reader = createStreamReader(bais);
Object ret = unmarshaller.get().unmarshal(reader);
reader.close();
return ret;
} catch (JAXBException e) {
throw new HectorSerializationException(
"Jaxb exception occurred during deserialization.", e);
} catch (XMLStreamException e) {
throw new HectorSerializationException("Exception reading XML stream.", e);
}
}
/**
* Get a new XML stream writer.
*
* @param output
* An underlying OutputStream to write to.
* @return a new {@link XMLStreamWriter} which writes to the specified
* OutputStream. The output written by this XMLStreamWriter is
* understandable by XMLStreamReaders produced by
* {@link #createStreamReader(InputStream)}.
* @throws XMLStreamException
*/
// Provides hook for subclasses to override how marshalling results are
// serialized (ex. encoding).
protected XMLStreamWriter createStreamWriter(OutputStream output)
throws XMLStreamException {
if (outputFactory == null) {
outputFactory = XMLOutputFactory.newInstance();
}
return outputFactory.createXMLStreamWriter(output);
}
/**
* Get a new XML stream reader.
*
* @param input
* the underlying InputStream to read from.
* @return a new {@link XmlStreamReader} which reads from the specified
* InputStream. The reader can read anything written by
* {@link #createStreamWriter(OutputStream)}.
* @throws XMLStreamException
*/
protected XMLStreamReader createStreamReader(InputStream input)
throws XMLStreamException {
if (inputFactory == null) {
inputFactory = XMLInputFactory.newInstance();
}
return inputFactory.createXMLStreamReader(input);
}
}