/**
* 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.jackson;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator.Feature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
import com.fasterxml.jackson.dataformat.csv.CsvFactory;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import com.fasterxml.jackson.dataformat.smile.SmileFactory;
import com.fasterxml.jackson.dataformat.xml.XmlFactory;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.restlet.data.MediaType;
import org.restlet.ext.jackson.internal.XmlFactoryProvider;
import org.restlet.representation.OutputRepresentation;
import org.restlet.representation.Representation;
import java.io.IOException;
import java.io.OutputStream;
/**
* Representation based on the Jackson library. It can serialize and deserialize
* automatically in JSON, JSON binary (Smile), CBOR, XML, YAML and CSV. <br>
* <br>
* SECURITY WARNING: Using XML parsers configured to not prevent nor limit
* document type definition (DTD) entity resolution can expose the parser to an
* XML Entity Expansion injection attack.
*
* @see <a href="http://jackson.codehaus.org/">Jackson project</a>
* @see <a
* href="https://github.com/restlet/restlet-framework-java/wiki/XEE-security-enhancements">XML
* Entity Expansion injection attack</a>
* @author Jerome Louvel
* @param <T>
* The type to wrap.
*/
public class JacksonRepresentation<T> extends OutputRepresentation {
// [ifndef android] member
/**
* True for expanding entity references when parsing XML representations.
* Default value provided by system property
* "org.restlet.ext.xml.expandingEntityRefs", false by default.
*/
public static boolean XML_EXPANDING_ENTITY_REFS = Boolean
.getBoolean("org.restlet.ext.xml.expandingEntityRefs");
// [ifndef android] member
/**
* True for validating DTD documents when parsing XML representations.
* Default value provided by system property
* "org.restlet.ext.xml.validatingDtd", false by default.
*/
public static boolean XML_VALIDATING_DTD = Boolean
.getBoolean("org.restlet.ext.xml.validatingDtd");
/** The modifiable Jackson CSV schema. */
private CsvSchema csvSchema;
// [ifndef android] member
/**
* Specifies that the parser will expand entity reference nodes. By default
* the value of this is set to false.
*/
private volatile boolean expandingEntityRefs;
/** The (parsed) object to format. */
private volatile T object;
/** The object class to instantiate. */
private volatile Class<T> objectClass;
/** The modifiable Jackson object mapper. */
private volatile ObjectMapper objectMapper;
/** The modifiable Jackson object reader. */
private volatile ObjectReader objectReader;
/** The modifiable Jackson object writer. */
private volatile ObjectWriter objectWriter;
/** The representation to parse. */
private volatile Representation representation;
// [ifndef android] member
/**
* 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 javax.xml.parsers.DocumentBuilderFactory#setValidating(boolean)
*/
private volatile boolean validatingDtd;
/**
* Constructor.
*
* @param mediaType
* The target media type.
* @param object
* The object to format.
*/
@SuppressWarnings("unchecked")
public JacksonRepresentation(MediaType mediaType, T object) {
super(mediaType);
this.object = object;
this.objectClass = (Class<T>) ((object == null) ? null : object
.getClass());
this.representation = null;
this.objectMapper = null;
this.objectReader = null;
this.objectWriter = null;
this.csvSchema = null;
// [ifndef android] instruction
this.expandingEntityRefs = XML_EXPANDING_ENTITY_REFS;
// [ifndef android] instruction
this.validatingDtd = XML_VALIDATING_DTD;
}
/**
* Constructor.
*
* @param representation
* The representation to parse.
* @param objectClass
* The object class to instantiate.
*/
public JacksonRepresentation(Representation representation,
Class<T> objectClass) {
super(representation.getMediaType());
this.object = null;
this.objectClass = objectClass;
this.representation = representation;
this.objectMapper = null;
this.objectReader = null;
this.objectWriter = null;
this.csvSchema = null;
// [ifndef android] instruction
this.expandingEntityRefs = XML_EXPANDING_ENTITY_REFS;
// [ifndef android] instruction
this.validatingDtd = XML_VALIDATING_DTD;
}
/**
* Constructor for the JSON media type.
*
* @param object
* The object to format.
*/
public JacksonRepresentation(T object) {
this(MediaType.APPLICATION_JSON, object);
}
/**
* Creates a Jackson CSV schema based on a mapper and the current object
* class.
*
* @param csvMapper
* The source CSV mapper.
* @return A Jackson CSV schema
*/
protected CsvSchema createCsvSchema(CsvMapper csvMapper) {
return csvMapper.schemaFor(getObjectClass());
}
/**
* Creates a Jackson object mapper based on a media type. It supports JSON,
* JSON Smile, XML, YAML and CSV.
*
* @return The Jackson object mapper.
*/
protected ObjectMapper createObjectMapper() {
ObjectMapper result = null;
if (MediaType.APPLICATION_JSON.isCompatible(getMediaType())) {
JsonFactory jsonFactory = new JsonFactory();
jsonFactory.configure(Feature.AUTO_CLOSE_TARGET, false);
result = new ObjectMapper(jsonFactory);
} else if (MediaType.APPLICATION_JSON_SMILE
.isCompatible(getMediaType())) {
SmileFactory smileFactory = new SmileFactory();
smileFactory.configure(Feature.AUTO_CLOSE_TARGET, false);
result = new ObjectMapper(smileFactory);
} else if (MediaType.APPLICATION_CBOR
.isCompatible(getMediaType())) {
CBORFactory cborFactory = new CBORFactory();
cborFactory.configure(Feature.AUTO_CLOSE_TARGET, false);
result = new ObjectMapper(cborFactory);
// [ifndef android]
} else if (MediaType.APPLICATION_XML.isCompatible(getMediaType())
|| MediaType.TEXT_XML.isCompatible(getMediaType())) {
javax.xml.stream.XMLInputFactory xif = XmlFactoryProvider.newInputFactory();
xif.setProperty(
javax.xml.stream.XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES,
isExpandingEntityRefs());
xif.setProperty(javax.xml.stream.XMLInputFactory.SUPPORT_DTD,
isExpandingEntityRefs());
xif.setProperty(javax.xml.stream.XMLInputFactory.IS_VALIDATING,
isValidatingDtd());
javax.xml.stream.XMLOutputFactory xof = XmlFactoryProvider.newOutputFactory();
XmlFactory xmlFactory = new XmlFactory(xif, xof);
xmlFactory.configure(Feature.AUTO_CLOSE_TARGET, false);
result = new XmlMapper(xmlFactory);
// [enddef]
} else if (MediaType.APPLICATION_YAML.isCompatible(getMediaType())
|| MediaType.TEXT_YAML.isCompatible(getMediaType())) {
YAMLFactory yamlFactory = new YAMLFactory();
yamlFactory.configure(Feature.AUTO_CLOSE_TARGET, false);
result = new ObjectMapper(yamlFactory);
} else if (MediaType.TEXT_CSV.isCompatible(getMediaType())) {
CsvFactory csvFactory = new CsvFactory();
csvFactory.configure(Feature.AUTO_CLOSE_TARGET, false);
result = new CsvMapper(csvFactory);
} else {
JsonFactory jsonFactory = new JsonFactory();
jsonFactory.configure(Feature.AUTO_CLOSE_TARGET, false);
result = new ObjectMapper(jsonFactory);
}
return result;
}
/**
* Creates a Jackson object reader based on a mapper. Has a special handling
* for CSV media types.
*
* @return The Jackson object reader.
*/
protected ObjectReader createObjectReader() {
ObjectReader result = null;
if (MediaType.TEXT_CSV.isCompatible(getMediaType())) {
CsvMapper csvMapper = (CsvMapper) getObjectMapper();
CsvSchema csvSchema = createCsvSchema(csvMapper);
result = csvMapper.reader(getObjectClass()).with(csvSchema);
} else {
result = getObjectMapper().reader(getObjectClass());
}
return result;
}
/**
* Creates a Jackson object writer based on a mapper. Has a special handling
* for CSV media types.
*
* @return The Jackson object writer.
*/
protected ObjectWriter createObjectWriter() {
ObjectWriter result = null;
if (MediaType.TEXT_CSV.isCompatible(getMediaType())) {
CsvMapper csvMapper = (CsvMapper) getObjectMapper();
CsvSchema csvSchema = createCsvSchema(csvMapper);
result = csvMapper.writer(csvSchema);
} else {
result = getObjectMapper().writerWithType(getObjectClass());
}
return result;
}
/**
* Returns the modifiable Jackson CSV schema.
*
* @return The modifiable Jackson CSV schema.
*/
public CsvSchema getCsvSchema() {
if (this.csvSchema == null) {
this.csvSchema = createCsvSchema((CsvMapper) getObjectMapper());
}
return this.csvSchema;
}
/**
* Returns the wrapped object, deserializing the representation with Jackson
* if necessary.
*
* @return The wrapped object.
* @throws IOException
*/
public T getObject() throws IOException {
T result = null;
if (this.object != null) {
result = this.object;
} else if (this.representation != null) {
result = getObjectReader().readValue(
this.representation.getStream());
}
return result;
}
/**
* Returns the object class to instantiate.
*
* @return The object class to instantiate.
*/
public Class<T> getObjectClass() {
return objectClass;
}
/**
* Returns the modifiable Jackson object mapper. Useful to customize
* mappings.
*
* @return The modifiable Jackson object mapper.
*/
public ObjectMapper getObjectMapper() {
if (this.objectMapper == null) {
this.objectMapper = createObjectMapper();
}
return this.objectMapper;
}
/**
* Returns the modifiable Jackson object reader. Useful to customize
* deserialization.
*
* @return The modifiable Jackson object reader.
*/
public ObjectReader getObjectReader() {
if (this.objectReader == null) {
this.objectReader = createObjectReader();
}
return this.objectReader;
}
/**
* Returns the modifiable Jackson object writer. Useful to customize
* serialization.
*
* @return The modifiable Jackson object writer.
*/
public ObjectWriter getObjectWriter() {
if (this.objectWriter == null) {
this.objectWriter = createObjectWriter();
}
return this.objectWriter;
}
// [ifndef android] method
/**
* 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;
}
// [ifndef android] method
/**
* 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;
}
/**
* Sets the Jackson CSV schema.
*
* @param csvSchema
* The Jackson CSV schema.
*/
public void setCsvSchema(CsvSchema csvSchema) {
this.csvSchema = csvSchema;
}
// [ifndef android] method
/**
* 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;
}
/**
* Sets the object to format.
*
* @param object
* The object to format.
*/
public void setObject(T object) {
this.object = object;
}
/**
* Sets the object class to instantiate.
*
* @param objectClass
* The object class to instantiate.
*/
public void setObjectClass(Class<T> objectClass) {
this.objectClass = objectClass;
}
/**
* Sets the Jackson object mapper.
*
* @param objectMapper
* The Jackson object mapper.
*/
public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
/**
* Sets the Jackson object reader.
*
* @param objectReader
* The Jackson object reader.
*/
public void setObjectReader(ObjectReader objectReader) {
this.objectReader = objectReader;
}
/**
* Sets the Jackson object writer.
*
* @param objectWriter
* The Jackson object writer.
*/
public void setObjectWriter(ObjectWriter objectWriter) {
this.objectWriter = objectWriter;
}
// [ifndef android] method
/**
* 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;
}
@Override
public void write(OutputStream outputStream) throws IOException {
if (representation != null) {
representation.write(outputStream);
} else if (object != null) {
getObjectWriter().writeValue(outputStream, object);
}
}
}