/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.ext.jaxb;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.util.JAXBSource;
import javax.xml.parsers.DocumentBuilderFactory;
import org.restlet.Context;
import org.restlet.data.MediaType;
import org.restlet.ext.jaxb.internal.Marshaller;
import org.restlet.ext.jaxb.internal.Unmarshaller;
import org.restlet.representation.Representation;
import org.restlet.representation.WriterRepresentation;
import com.sun.xml.bind.marshaller.NamespacePrefixMapper;
/**
* An XML representation based on JAXB that provides easy translation between
* XML and JAXB element class trees.
*
* @author Overstock.com
* @author Jerome Louvel
* @param <T>
* The type to wrap.
*/
public class JaxbRepresentation<T> extends WriterRepresentation {
/** Improves performance by caching contexts which are expensive to create. */
private final static Map<String, JAXBContext> contexts = new ConcurrentHashMap<String, JAXBContext>();
/**
* Returns the JAXB context, if possible from the cached contexts.
*
* @param contextPath
* The JAXB context path.
*
* @return The JAXB context.
* @throws JAXBException
*/
public static synchronized JAXBContext getContext(String contextPath)
throws JAXBException {
return getContext(contextPath, null);
}
/**
* Returns the JAXB context, if possible from the cached contexts.
*
* @param contextPath
* The JAXB context path.
*
* @param classLoader
* The JAXB classloader to use for annotated JAXB classes.
*
* @return The JAXB context.
* @throws JAXBException
*/
public static synchronized JAXBContext getContext(String contextPath,
ClassLoader classLoader) throws JAXBException {
// Contexts are thread-safe so reuse those.
JAXBContext result = contexts.get(contextPath);
if (result == null) {
result = (classLoader == null) ? JAXBContext
.newInstance(contextPath) : JAXBContext.newInstance(
contextPath, classLoader);
contexts.put(contextPath, result);
}
return result;
}
/**
* The classloader to use for JAXB annotated classes.
*/
private volatile ClassLoader classLoader;
/**
* The list of Java package names that contain schema derived class and/or
* Java to schema (JAXB-annotated) mapped classes.
*/
private volatile String contextPath;
/**
* Specifies that the parser will expand entity reference nodes. By default
* the value of this is set to true.
*/
private volatile boolean expandingEntityRefs;
/**
* Indicates if the resulting XML data should be formatted with line breaks
* and indentation. Defaults to false.
*/
private volatile boolean formattedOutput;
/**
* Indicates whether or not document level events will be generated by the
* Marshaller.
*/
private volatile boolean fragment;
/** An optional namespace prefix mapper for marshalling. */
private volatile NamespacePrefixMapper namespacePrefixMapper;
/** The "xsi:noNamespaceSchemaLocation" attribute in the generated XML data. */
private volatile String noNamespaceSchemaLocation;
/** The wrapped Java object. */
private volatile T object;
/** The "xsi:schemaLocation" attribute in the generated XML data */
private volatile String schemaLocation;
/** Limits potential XML overflow attacks. */
private boolean secureProcessing;
/**
* Indicates the desire for validating this type of XML representations
* against a DTD. Note that for XML schema or Relax NG validation, use the
* "schema" property instead.
*
* @see DocumentBuilderFactory#setValidating(boolean)
*/
private volatile boolean validatingDtd;
/** The JAXB validation event handler. */
private volatile ValidationEventHandler validationEventHandler;
/**
* Indicates the desire for processing <em>XInclude</em> if found in this
* type of XML representations. By default the value of this is set to
* false.
*
* @see DocumentBuilderFactory#setXIncludeAware(boolean)
*/
private volatile boolean xIncludeAware;
/** The source XML representation. */
private volatile Representation xmlRepresentation;
/**
* Creates a JAXB representation from an existing JAXB content tree.
*
* @param mediaType
* The representation's media type.
* @param object
* The Java object.
*/
public JaxbRepresentation(MediaType mediaType, T object) {
this(mediaType, object, (object != null) ? object.getClass()
.getClassLoader() : null);
}
/**
* Creates a JAXB representation from an existing JAXB content tree.
*
* @param mediaType
* The representation's media type.
* @param object
* The Java object.
* @param classloader
* The classloader to use for JAXB annotated classes.
*/
public JaxbRepresentation(MediaType mediaType, T object,
ClassLoader classloader) {
super(mediaType);
this.classLoader = classloader;
this.contextPath = (object != null) ? object.getClass().getPackage()
.getName() : null;
this.object = object;
this.validationEventHandler = null;
this.xmlRepresentation = null;
this.expandingEntityRefs = false;
this.formattedOutput = false;
this.fragment = false;
this.namespacePrefixMapper = null;
this.noNamespaceSchemaLocation = null;
this.schemaLocation = null;
this.secureProcessing = true;
this.validatingDtd = false;
this.xIncludeAware = false;
}
/**
* Creates a new JAXB representation, converting the input XML into a Java
* content tree. The XML is validated.
*
* @param xmlRepresentation
* The XML wrapped in a representation.
* @param type
* The type to convert to.
*
* @throws JAXBException
* If the incoming XML does not validate against the schema.
* @throws IOException
* If unmarshalling XML fails.
*/
public JaxbRepresentation(Representation xmlRepresentation, Class<T> type) {
this(xmlRepresentation, type.getPackage().getName(), null, type
.getClassLoader());
}
/**
* Creates a new JAXB representation, converting the input XML into a Java
* content tree. The XML is validated.
*
* @param xmlRepresentation
* The XML wrapped in a representation.
* @param type
* The type to convert to.
* @param validationHandler
* A handler for dealing with validation failures.
*
* @throws JAXBException
* If the incoming XML does not validate against the schema.
* @throws IOException
* If unmarshalling XML fails.
*/
public JaxbRepresentation(Representation xmlRepresentation, Class<T> type,
ValidationEventHandler validationHandler) {
this(xmlRepresentation, type.getPackage().getName(), validationHandler,
type.getClassLoader());
}
/**
* Creates a new JAXB representation, converting the input XML into a Java
* content tree. The XML is validated.
*
* @param xmlRepresentation
* The XML wrapped in a representation.
* @param contextPath
* The list of Java package names for JAXB.
*
* @throws JAXBException
* If the incoming XML does not validate against the schema.
* @throws IOException
* If unmarshalling XML fails.
*/
public JaxbRepresentation(Representation xmlRepresentation,
String contextPath) {
this(xmlRepresentation, contextPath, null, null);
}
/**
* Creates a new JAXB representation, converting the input XML into a Java
* content tree. The XML is validated.
*
* @param xmlRepresentation
* The XML wrapped in a representation.
* @param contextPath
* The list of Java package names for JAXB.
* @param validationHandler
* A handler for dealing with validation failures.
*
* @throws JAXBException
* If the incoming XML does not validate against the schema.
* @throws IOException
* If unmarshalling XML fails.
*/
public JaxbRepresentation(Representation xmlRepresentation,
String contextPath, ValidationEventHandler validationHandler) {
this(xmlRepresentation, contextPath, validationHandler, null);
}
/**
* Creates a new JAXB representation, converting the input XML into a Java
* content tree. The XML is validated.
*
* @param xmlRepresentation
* The XML wrapped in a representation.
* @param contextPath
* The list of Java package names for JAXB.
* @param validationHandler
* A handler for dealing with validation failures.
* @param classLoader
* The classloader to use for JAXB annotated classes.
* @throws JAXBException
* If the incoming XML does not validate against the schema.
* @throws IOException
* If unmarshalling XML fails.
*/
public JaxbRepresentation(Representation xmlRepresentation,
String contextPath, ValidationEventHandler validationHandler,
ClassLoader classLoader) {
super((xmlRepresentation == null) ? null : xmlRepresentation
.getMediaType());
this.classLoader = classLoader;
this.contextPath = contextPath;
this.object = null;
this.secureProcessing = true;
this.validationEventHandler = validationHandler;
this.xmlRepresentation = xmlRepresentation;
}
/**
* Creates a JAXB representation from an existing JAXB content tree with
* {@link MediaType#APPLICATION_XML}.
*
* @param object
* The Java object.
*/
public JaxbRepresentation(T object) {
this(MediaType.APPLICATION_XML, object);
}
/**
* Returns the classloader to use for JAXB annotated classes.
*
* @return The classloader to use for JAXB annotated classes.
*/
public ClassLoader getClassLoader() {
return this.classLoader;
}
/**
* Returns the JAXB context.
*
* @return The JAXB context.
* @throws JAXBException
*/
public JAXBContext getContext() throws JAXBException {
return getContext(getContextPath(), getClassLoader());
}
/**
* Returns the list of Java package names that contain schema derived class
* and/or Java to schema (JAXB-annotated) mapped classes
*
* @return The list of Java package names.
*/
public String getContextPath() {
return this.contextPath;
}
/**
* Returns a JAXB SAX source.
*
* @return A JAXB SAX source.
*/
public JAXBSource getJaxbSource() throws IOException {
try {
return new JAXBSource(getContext(), getObject());
} catch (JAXBException e) {
throw new IOException(
"JAXBException while creating the JAXBSource: "
+ e.getMessage());
}
}
/**
* Returns the optional namespace prefix mapper for marshalling.
*
* @return The optional namespace prefix mapper for marshalling.
*/
public NamespacePrefixMapper getNamespacePrefixMapper() {
return namespacePrefixMapper;
}
/**
* Returns the "xsi:noNamespaceSchemaLocation" attribute in the generated
* XML data.
*
* @return The "xsi:noNamespaceSchemaLocation" attribute in the generated
* XML data.
*/
public String getNoNamespaceSchemaLocation() {
return noNamespaceSchemaLocation;
}
/**
* Returns the wrapped Java object.
*
* @return The wrapped Java object.
* @throws IOException
*/
@SuppressWarnings("unchecked")
public T getObject() throws IOException {
if ((this.object == null) && (this.xmlRepresentation != null)) {
// Try to unmarshal the wrapped XML representation
final Unmarshaller<T> u = new Unmarshaller<T>(this.contextPath,
this.classLoader);
if (getValidationEventHandler() != null) {
try {
u.setEventHandler(getValidationEventHandler());
} catch (JAXBException e) {
Context.getCurrentLogger().log(Level.WARNING,
"Unable to set the event handler", e);
throw new IOException("Unable to set the event handler."
+ e.getMessage());
}
}
try {
this.object = (T) u.unmarshal(this,
this.xmlRepresentation.getReader());
} catch (JAXBException e) {
Context.getCurrentLogger().log(Level.WARNING,
"Unable to unmarshal the XML representation", e);
throw new IOException(
"Unable to unmarshal the XML representation."
+ e.getMessage());
}
}
return this.object;
}
/**
* Returns the "xsi:schemaLocation" attribute in the generated XML data.
*
* @return The "xsi:schemaLocation" attribute in the generated XML data.
*/
public String getSchemaLocation() {
return schemaLocation;
}
/**
* Returns the optional validation event handler.
*
* @return The optional validation event handler.
*/
public ValidationEventHandler getValidationEventHandler() {
return this.validationEventHandler;
}
/**
* Indicates if the parser will expand entity reference nodes. By default
* the value of this is set to true.
*
* @return True if the parser will expand entity reference nodes.
*/
public boolean isExpandingEntityRefs() {
return expandingEntityRefs;
}
/**
* Indicates if the resulting XML data should be formatted with line breaks
* and indentation. Defaults to false.
*
* @return the formattedOutput
*/
public boolean isFormattedOutput() {
return this.formattedOutput;
}
/**
* Indicates whether or not document level events will be generated by the
* Marshaller.
*
* @return True if the document level events will be generated by the
* Marshaller.
*/
public boolean isFragment() {
return fragment;
}
/**
* Indicates if it limits potential XML overflow attacks.
*
* @return True if it limits potential XML overflow attacks.
*/
public boolean isSecureProcessing() {
return secureProcessing;
}
/**
* Indicates the desire for validating this type of XML representations
* against an XML schema if one is referenced within the contents.
*
* @return True if the schema-based validation is enabled.
*/
public boolean isValidatingDtd() {
return validatingDtd;
}
/**
* Indicates the desire for processing <em>XInclude</em> if found in this
* type of XML representations. By default the value of this is set to
* false.
*
* @return The current value of the xIncludeAware flag.
*/
public boolean isXIncludeAware() {
return xIncludeAware;
}
/**
* Sets the classloader to use for JAXB annotated classes.
*
* @param classLoader
* The classloader to use for JAXB annotated classes.
*/
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Sets the list of Java package names that contain schema derived class
* and/or Java to schema (JAXB-annotated) mapped classes.
*
* @param contextPath
* The JAXB context path.
*/
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
/**
* Indicates if the parser will expand entity reference nodes. By default
* the value of this is set to true.
*
* @param expandEntityRefs
* True if the parser will expand entity reference nodes.
*/
public void setExpandingEntityRefs(boolean expandEntityRefs) {
this.expandingEntityRefs = expandEntityRefs;
}
/**
* Indicates if the resulting XML data should be formatted with line breaks
* and indentation.
*
* @param formattedOutput
* True if the resulting XML data should be formatted.
*/
public void setFormattedOutput(boolean formattedOutput) {
this.formattedOutput = formattedOutput;
}
/**
* Indicates whether or not document level events will be generated by the
* Marshaller.
*
* @param fragment
* True if the document level events will be generated by the
* Marshaller.
*/
public void setFragment(boolean fragment) {
this.fragment = fragment;
}
/**
* Sets the optional namespace prefix mapper for marshalling.
*
* @param namespacePrefixMapper
* The optional namespace prefix mapper for marshalling.
*/
public void setNamespacePrefixMapper(
NamespacePrefixMapper namespacePrefixMapper) {
this.namespacePrefixMapper = namespacePrefixMapper;
}
/**
* Sets the "xsi:noNamespaceSchemaLocation" attribute in the generated XML
* data.
*
* @param noNamespaceSchemaLocation
* The "xsi:noNamespaceSchemaLocation" attribute in the generated
* XML data.
*/
public void setNoNamespaceSchemaLocation(String noNamespaceSchemaLocation) {
this.noNamespaceSchemaLocation = noNamespaceSchemaLocation;
}
/**
* Sets the wrapped Java object.
*
* @param object
* The Java object to set.
*/
public void setObject(T object) {
this.object = object;
}
/**
* Sets the "xsi:schemaLocation" attribute in the generated XML data.
*
* @param schemaLocation
* The "xsi:noNamespaceSchemaLocation" attribute in the generated
* XML data.
*/
public void setSchemaLocation(String schemaLocation) {
this.schemaLocation = schemaLocation;
}
/**
* Indicates if it limits potential XML overflow attacks.
*
* @param secureProcessing
* True if it limits potential XML overflow attacks.
*/
public void setSecureProcessing(boolean secureProcessing) {
this.secureProcessing = secureProcessing;
}
/**
* Indicates the desire for validating this type of XML representations
* against an XML schema if one is referenced within the contents.
*
* @param validating
* The new validation flag to set.
*/
public void setValidatingDtd(boolean validating) {
this.validatingDtd = validating;
}
/**
* Sets the validation event handler.
*
* @param validationEventHandler
* The optional validation event handler.
*/
public void setValidationEventHandler(
ValidationEventHandler validationEventHandler) {
this.validationEventHandler = validationEventHandler;
}
/**
* Indicates the desire for processing <em>XInclude</em> if found in this
* type of XML representations. By default the value of this is set to
* false.
*
* @param includeAware
* The new value of the xIncludeAware flag.
*/
public void setXIncludeAware(boolean includeAware) {
xIncludeAware = includeAware;
}
/**
* Writes the representation to a stream of characters.
*
* @param writer
* The writer to use when writing.
*
* @throws IOException
* If any error occurs attempting to write the stream.
*/
@Override
public void write(Writer writer) throws IOException {
try {
new Marshaller<T>(this, this.contextPath, getClassLoader())
.marshal(getObject(), writer);
} catch (JAXBException e) {
Context.getCurrentLogger().log(Level.WARNING,
"JAXB marshalling error caught.", e);
// Maybe the tree represents a failure, try that.
try {
new Marshaller<T>(this, "failure", getClassLoader()).marshal(
getObject(), writer);
} catch (JAXBException e2) {
// We don't know what package this tree is from.
throw new IOException(e.getMessage());
}
}
}
}