From 799a98cf4120d86d46a00966565adf6800786411 Mon Sep 17 00:00:00 2001 From: Manuel Boillod Date: Sun, 7 Dec 2014 14:03:38 +0100 Subject: [PATCH 1/2] Extract default jackson object mappers in ObjectMappers class for allowing customization of jackson mapper in a global way --- .../ext/jackson/JacksonRepresentation.java | 75 ++++---- .../restlet/ext/jackson/ObjectMappers.java | 176 ++++++++++++++++++ .../test/service/StatusServiceTestCase.java | 3 + 3 files changed, 212 insertions(+), 42 deletions(-) create mode 100644 modules/org.restlet.ext.jackson/src/org/restlet/ext/jackson/ObjectMappers.java diff --git a/modules/org.restlet.ext.jackson/src/org/restlet/ext/jackson/JacksonRepresentation.java b/modules/org.restlet.ext.jackson/src/org/restlet/ext/jackson/JacksonRepresentation.java index b144360df7..a935c0c2c3 100644 --- a/modules/org.restlet.ext.jackson/src/org/restlet/ext/jackson/JacksonRepresentation.java +++ b/modules/org.restlet.ext.jackson/src/org/restlet/ext/jackson/JacksonRepresentation.java @@ -38,25 +38,17 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLOutputFactory; import org.restlet.data.MediaType; import org.restlet.representation.OutputRepresentation; import org.restlet.representation.Representation; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -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.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; /** * Representation based on the Jackson library. It can serialize and deserialize @@ -79,17 +71,19 @@ public class JacksonRepresentation extends OutputRepresentation { * True for expanding entity references when parsing XML representations; * default value provided by system property * "org.restlet.ext.xml.expandingEntityRefs", false by default. + * @deprecated Use {@link org.restlet.ext.jackson.ObjectMappers.XmlMapperFactory#XML_EXPANDING_ENTITY_REFS} */ - public static boolean XML_EXPANDING_ENTITY_REFS = Boolean - .getBoolean("org.restlet.ext.xml.expandingEntityRefs"); + @Deprecated + public static boolean XML_EXPANDING_ENTITY_REFS = ObjectMappers.XmlMapperFactory.XML_EXPANDING_ENTITY_REFS; /** * True for validating DTD documents when parsing XML representations; * default value provided by system property * "org.restlet.ext.xml.validatingDtd", false by default. + * @deprecated Use {@link org.restlet.ext.jackson.ObjectMappers.XmlMapperFactory#XML_VALIDATING_DTD} */ - public static boolean XML_VALIDATING_DTD = Boolean - .getBoolean("org.restlet.ext.xml.validatingDtd"); + @Deprecated + public static boolean XML_VALIDATING_DTD = ObjectMappers.XmlMapperFactory.XML_VALIDATING_DTD; /** The modifiable Jackson CSV schema. */ private CsvSchema csvSchema; @@ -201,46 +195,31 @@ protected CsvSchema createCsvSchema(CsvMapper csvMapper) { * @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); + return ObjectMappers.getDefaultJsonMapperFactory().newObjectMapper(); } else if (MediaType.APPLICATION_JSON_SMILE .isCompatible(getMediaType())) { - SmileFactory smileFactory = new SmileFactory(); - smileFactory.configure(Feature.AUTO_CLOSE_TARGET, false); - result = new ObjectMapper(smileFactory); + return ObjectMappers.getDefaultJsonSmileMapperFactory().newObjectMapper(); } else if (MediaType.APPLICATION_XML.isCompatible(getMediaType()) || MediaType.TEXT_XML.isCompatible(getMediaType())) { - XMLInputFactory xif = XMLInputFactory.newFactory(); - xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, - isExpandingEntityRefs()); - xif.setProperty(XMLInputFactory.SUPPORT_DTD, - isExpandingEntityRefs()); - xif.setProperty(XMLInputFactory.IS_VALIDATING, isValidatingDtd()); - XMLOutputFactory xof = XMLOutputFactory.newFactory(); - XmlFactory xmlFactory = new XmlFactory(xif, xof); - xmlFactory.configure(Feature.AUTO_CLOSE_TARGET, false); - result = new XmlMapper(xmlFactory); + ObjectMapper objectMapper = ObjectMappers.getDefaultXmlMapperFactory().newObjectMapper(); + //for backward compatibility (deprecated) + if (objectMapper instanceof XmlMapper) { + XmlMapper xmlMapper = (XmlMapper) objectMapper; + XMLInputFactory xif = xmlMapper.getFactory().getXMLInputFactory(); + xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, isExpandingEntityRefs()); + xif.setProperty(XMLInputFactory.SUPPORT_DTD, isExpandingEntityRefs()); + xif.setProperty(XMLInputFactory.IS_VALIDATING, isValidatingDtd()); + } + return objectMapper; } 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); + return ObjectMappers.getDefaultYamlMapperFactory().newObjectMapper(); } else if (MediaType.TEXT_CSV.isCompatible(getMediaType())) { - CsvFactory csvFactory = new CsvFactory(); - csvFactory.configure(Feature.AUTO_CLOSE_TARGET, false); - result = new CsvMapper(csvFactory); + return ObjectMappers.getDefaultCsvMapperFactory().newObjectMapper(); } else { - JsonFactory jsonFactory = new JsonFactory(); - jsonFactory.configure(Feature.AUTO_CLOSE_TARGET, false); - result = new ObjectMapper(jsonFactory); + return ObjectMappers.getDefaultJsonMapperFactory().newObjectMapper(); } - - result.setSerializationInclusion(Include.NON_NULL); - return result; } /** @@ -372,7 +351,10 @@ public ObjectWriter getObjectWriter() { * the value of this is set to true. * * @return True if the parser will expand entity reference nodes. + * @deprecated configure it globally in {@link ObjectMappers.XmlMapperFactory} + * or override {@link #createObjectMapper()} to configure it for an instance only. */ + @Deprecated public boolean isExpandingEntityRefs() { return expandingEntityRefs; } @@ -382,7 +364,10 @@ public boolean isExpandingEntityRefs() { * against an XML schema if one is referenced within the contents. * * @return True if the schema-based validation is enabled. + * @deprecated configure it globally in {@link ObjectMappers.XmlMapperFactory} + * or override {@link #createObjectMapper()} to configure it for an instance only. */ + @Deprecated public boolean isValidatingDtd() { return validatingDtd; } @@ -403,7 +388,10 @@ public void setCsvSchema(CsvSchema csvSchema) { * * @param expandEntityRefs * True if the parser will expand entity reference nodes. + * @deprecated configure it globally in {@link ObjectMappers.XmlMapperFactory} + * or override {@link #createObjectMapper()} to configure it for an instance only. */ + @Deprecated public void setExpandingEntityRefs(boolean expandEntityRefs) { this.expandingEntityRefs = expandEntityRefs; } @@ -464,7 +452,10 @@ public void setObjectWriter(ObjectWriter objectWriter) { * * @param validating * The new validation flag to set. + * @deprecated configure it globally in {@link ObjectMappers.XmlMapperFactory} + * or override {@link #createObjectMapper()} to configure it for an instance only. */ + @Deprecated public void setValidatingDtd(boolean validating) { this.validatingDtd = validating; } diff --git a/modules/org.restlet.ext.jackson/src/org/restlet/ext/jackson/ObjectMappers.java b/modules/org.restlet.ext.jackson/src/org/restlet/ext/jackson/ObjectMappers.java new file mode 100644 index 0000000000..1f905d448c --- /dev/null +++ b/modules/org.restlet.ext.jackson/src/org/restlet/ext/jackson/ObjectMappers.java @@ -0,0 +1,176 @@ +package org.restlet.ext.jackson; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.csv.CsvFactory; +import com.fasterxml.jackson.dataformat.csv.CsvMapper; +import com.fasterxml.jackson.dataformat.xml.XmlFactory; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; + +/** + * List of default object mappers for each supported media type. + * + * Each default object mappers could be overridden for customize the default behaviour. + * + * @author Manuel Boillod + */ +public class ObjectMappers { + + /** + * Simple {@link ObjectMapper} factory + */ + public static interface ObjectMapperFactory { + /** + * Returns a new instance of {@link ObjectMapper}. + */ + ObjectMapper newObjectMapper(); + } + + /** Default object mapper for JSON media type */ + private static ObjectMapperFactory DEFAULT_JSON_MAPPER_FACTORY = new JsonMapperFactory(); + /** Default object mapper for JSON SMILE media type */ + private static ObjectMapperFactory DEFAULT_JSON_SMILE_MAPPER_FACTORY = new JsonSmileMapperFactory(); + /** Default object mapper for XML media type */ + private static ObjectMapperFactory DEFAULT_XML_MAPPER_FACTORY = new XmlMapperFactory(); + /** Default object mapper for YAML media type */ + private static ObjectMapperFactory DEFAULT_YAML_MAPPER_FACTORY = new YamlMapperFactory(); + /** Default object mapper for CSV media type */ + private static ObjectMapperFactory DEFAULT_CSV_MAPPER_FACTORY = new CsvMapperFactory(); + + /** Returns the default object mapper for JSON media type */ + public static ObjectMapperFactory getDefaultJsonMapperFactory() { + return DEFAULT_JSON_MAPPER_FACTORY; + } + + /** Define the default object mapper for JSON media type */ + public static void setDefaultJsonMapperFactory(ObjectMapperFactory DEFAULT_JSON_MAPPER_FACTORY) { + ObjectMappers.DEFAULT_JSON_MAPPER_FACTORY = DEFAULT_JSON_MAPPER_FACTORY; + } + + /** Returns the default object mapper for JSON SMILE media type */ + public static ObjectMapperFactory getDefaultJsonSmileMapperFactory() { + return DEFAULT_JSON_SMILE_MAPPER_FACTORY; + } + + /** Define the default object mapper for JSON SMILE media type */ + public static void setDefaultJsonSmileMapperFactory(ObjectMapperFactory DEFAULT_JSON_SMILE_MAPPER_FACTORY) { + ObjectMappers.DEFAULT_JSON_SMILE_MAPPER_FACTORY = DEFAULT_JSON_SMILE_MAPPER_FACTORY; + } + + /** Returns the default object mapper for XML media type */ + public static ObjectMapperFactory getDefaultXmlMapperFactory() { + return DEFAULT_XML_MAPPER_FACTORY; + } + + /** Define the default object mapper for XML media type */ + public static void setDefaultXmlMapperFactory(ObjectMapperFactory DEFAULT_XML_MAPPER_FACTORY) { + ObjectMappers.DEFAULT_XML_MAPPER_FACTORY = DEFAULT_XML_MAPPER_FACTORY; + } + + /** Returns the default object mapper for YAML media type */ + public static ObjectMapperFactory getDefaultYamlMapperFactory() { + return DEFAULT_YAML_MAPPER_FACTORY; + } + + /** Define the default object mapper for YAML media type */ + public static void setDefaultYamlMapperFactory(ObjectMapperFactory DEFAULT_YAML_MAPPER_FACTORY) { + ObjectMappers.DEFAULT_YAML_MAPPER_FACTORY = DEFAULT_YAML_MAPPER_FACTORY; + } + + /** Returns the default object mapper for CSV media type */ + public static ObjectMapperFactory getDefaultCsvMapperFactory() { + return DEFAULT_CSV_MAPPER_FACTORY; + } + + /** Define the default object mapper for CSV media type */ + public static void setDefaultCsvMapperFactory(ObjectMapperFactory DEFAULT_CSV_MAPPER_FACTORY) { + ObjectMappers.DEFAULT_CSV_MAPPER_FACTORY = DEFAULT_CSV_MAPPER_FACTORY; + } + + /** Default {@link ObjectMapperFactory} for JSON media type */ + public static class JsonMapperFactory implements ObjectMapperFactory { + @Override + public ObjectMapper newObjectMapper() { + JsonFactory jsonFactory = new JsonFactory(); + jsonFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); + ObjectMapper objectMapper = new ObjectMapper(jsonFactory); + objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + return objectMapper; + } + } + + /** Default {@link ObjectMapperFactory} for JSON SMILE media type */ + public static class JsonSmileMapperFactory implements ObjectMapperFactory { + @Override + public ObjectMapper newObjectMapper() { + JsonFactory jsonFactory = new JsonFactory(); + jsonFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); + ObjectMapper objectMapper = new ObjectMapper(jsonFactory); + objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + return objectMapper; + } + } + + /** Default {@link ObjectMapperFactory} for XML media type */ + public static class XmlMapperFactory implements ObjectMapperFactory { + /** + * 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"); + + /** + * 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"); + + @Override + public ObjectMapper newObjectMapper() { + XMLInputFactory xif = XMLInputFactory.newFactory(); + xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, XML_EXPANDING_ENTITY_REFS); + xif.setProperty(XMLInputFactory.SUPPORT_DTD, XML_EXPANDING_ENTITY_REFS); + xif.setProperty(XMLInputFactory.IS_VALIDATING, XML_VALIDATING_DTD); + XMLOutputFactory xof = XMLOutputFactory.newFactory(); + XmlFactory xmlFactory = new XmlFactory(xif, xof); + xmlFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); + ObjectMapper objectMapper = new XmlMapper(xmlFactory); + objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + return objectMapper; + } + } + + /** Default {@link ObjectMapperFactory} for YAML media type */ + public static class YamlMapperFactory implements ObjectMapperFactory { + @Override + public ObjectMapper newObjectMapper() { + YAMLFactory yamlFactory = new YAMLFactory(); + yamlFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); + ObjectMapper objectMapper = new ObjectMapper(yamlFactory); + objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + return objectMapper; + } + } + + /** Default {@link ObjectMapperFactory} for CSV media type */ + public static class CsvMapperFactory implements ObjectMapperFactory { + @Override + public ObjectMapper newObjectMapper() { + CsvFactory csvFactory = new CsvFactory(); + csvFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); + ObjectMapper objectMapper = new CsvMapper(csvFactory); + objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + return objectMapper; + } + } +} diff --git a/modules/org.restlet.test/src/org/restlet/test/service/StatusServiceTestCase.java b/modules/org.restlet.test/src/org/restlet/test/service/StatusServiceTestCase.java index 90e8e4df32..7ee9bf6d4d 100644 --- a/modules/org.restlet.test/src/org/restlet/test/service/StatusServiceTestCase.java +++ b/modules/org.restlet.test/src/org/restlet/test/service/StatusServiceTestCase.java @@ -96,8 +96,10 @@ public void testStatusSerialization() throws IOException { Status expectedStatus = Status.CLIENT_ERROR_BAD_REQUEST; HashMap expectedRepresentationMap = new LinkedHashMap<>(); expectedRepresentationMap.put("code", expectedStatus.getCode()); + expectedRepresentationMap.put("contactEmail", null); expectedRepresentationMap.put("description", expectedStatus.getDescription()); + expectedRepresentationMap.put("homeRef", null); expectedRepresentationMap.put("reasonPhrase", expectedStatus.getReasonPhrase()); expectedRepresentationMap.put("uri", @@ -122,6 +124,7 @@ public void testSerializedException() throws IOException { // verify HashMap expectedRepresentationMap = new LinkedHashMap<>(); + expectedRepresentationMap.put("cause", null); expectedRepresentationMap.put("stackTrace", exception.getStackTrace()); expectedRepresentationMap.put("value", 50); expectedRepresentationMap.put("message", "test message"); From c079d60dd41e48e5aa160977f59a3db543b918da Mon Sep 17 00:00:00 2001 From: Manuel Boillod Date: Sun, 21 Dec 2014 11:56:41 +0100 Subject: [PATCH 2/2] Add global setting for default Inclusion mode --- .../src/org/restlet/ext/jackson/ObjectMappers.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/modules/org.restlet.ext.jackson/src/org/restlet/ext/jackson/ObjectMappers.java b/modules/org.restlet.ext.jackson/src/org/restlet/ext/jackson/ObjectMappers.java index 1f905d448c..7c7bca7e43 100644 --- a/modules/org.restlet.ext.jackson/src/org/restlet/ext/jackson/ObjectMappers.java +++ b/modules/org.restlet.ext.jackson/src/org/restlet/ext/jackson/ObjectMappers.java @@ -22,6 +22,8 @@ */ public class ObjectMappers { + public static JsonInclude.Include DEFAULT_SERIALIZATION_INCLUSION = JsonInclude.Include.ALWAYS; + /** * Simple {@link ObjectMapper} factory */ @@ -100,7 +102,7 @@ public ObjectMapper newObjectMapper() { JsonFactory jsonFactory = new JsonFactory(); jsonFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); ObjectMapper objectMapper = new ObjectMapper(jsonFactory); - objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + objectMapper.setSerializationInclusion(DEFAULT_SERIALIZATION_INCLUSION); return objectMapper; } } @@ -112,7 +114,7 @@ public ObjectMapper newObjectMapper() { JsonFactory jsonFactory = new JsonFactory(); jsonFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); ObjectMapper objectMapper = new ObjectMapper(jsonFactory); - objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + objectMapper.setSerializationInclusion(DEFAULT_SERIALIZATION_INCLUSION); return objectMapper; } } @@ -145,7 +147,7 @@ public ObjectMapper newObjectMapper() { XmlFactory xmlFactory = new XmlFactory(xif, xof); xmlFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); ObjectMapper objectMapper = new XmlMapper(xmlFactory); - objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + objectMapper.setSerializationInclusion(DEFAULT_SERIALIZATION_INCLUSION); return objectMapper; } } @@ -157,7 +159,7 @@ public ObjectMapper newObjectMapper() { YAMLFactory yamlFactory = new YAMLFactory(); yamlFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); ObjectMapper objectMapper = new ObjectMapper(yamlFactory); - objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + objectMapper.setSerializationInclusion(DEFAULT_SERIALIZATION_INCLUSION); return objectMapper; } } @@ -169,7 +171,7 @@ public ObjectMapper newObjectMapper() { CsvFactory csvFactory = new CsvFactory(); csvFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); ObjectMapper objectMapper = new CsvMapper(csvFactory); - objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + objectMapper.setSerializationInclusion(DEFAULT_SERIALIZATION_INCLUSION); return objectMapper; } }