From 874837a6f17d87323d7ac0bdada53c635ff2293a Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Sat, 19 Apr 2014 17:14:35 +0200 Subject: [PATCH] Fix #28 commit d8e267e8938327d4358d2a9743dd5997c6888517 Author: Clement Escoffier Date: Sat Apr 19 17:13:52 2014 +0200 Implement the XML service (#28) The XML service implementation is merge with the JSON service implementation as both are based on Jackson. It also simplifies the Jackson Module management Signed-off-by: Clement Escoffier commit 0e41b3c44b61ae2e465c2c3cb384a15da48178ee Author: Clement Escoffier Date: Sat Apr 19 17:11:13 2014 +0200 Provides a XML body parser and serializer (#28) Signed-off-by: Clement Escoffier commit 1427b77bd2c43889d496942b1b61b32c3708052c Author: Clement Escoffier Date: Sat Apr 19 17:10:49 2014 +0200 Small improvements in the XML service (#28) added a newDocument method Use Charset instead of String Signed-off-by: Clement Escoffier commit 42e62e529326f8baed9fa19cde87137f3f61ad50 Author: Clement Escoffier Date: Sat Apr 19 12:19:26 2014 +0200 Add method to provides XML document as response's payload (#28) Signed-off-by: Clement Escoffier commit a52a9b840108f8dc60d63671d98bca46529bbba5 Author: Clement Escoffier Date: Sat Apr 19 11:54:22 2014 +0200 Provide a renderable implementation taking an XML document as parameter. This implementation does not use Jackson as the JRE provides enough facilities. Signed-off-by: Clement Escoffier commit bc172308abd746e92b4904a5e5832e1208206820 Author: Clement Escoffier Date: Sat Apr 19 11:49:24 2014 +0200 Initial definition of the XML service (#28) Signed-off-by: Clement Escoffier commit 6ac12abedd5cab08795774fa45c3844afc779a51 Author: Clement Escoffier Date: Sat Apr 19 11:13:03 2014 +0200 Added XML related bundle to the base distribution (#28) Signed-off-by: Clement Escoffier Signed-off-by: Clement Escoffier --- .../content/bodyparsers/BodyParserXML.java | 78 +++ .../content/jackson/JacksonSingleton.java | 449 ++++++++++++++++++ .../wisdom/content/json/JsonSingleton.java | 275 ----------- .../content/serializers/JSONSerializer.java | 2 +- .../content/serializers/XMLSerializer.java | 57 +++ .../content/json/JsonSingletonTest.java | 6 +- .../java/org/wisdom/content/xml/Data.java | 33 ++ .../java/org/wisdom/content/xml/Message.java | 33 ++ .../wisdom/content/xml/XMLSingletonTest.java | 141 ++++++ core/wisdom-api/pom.xml | 4 + .../org/wisdom/api/bodies/RenderableXML.java | 116 +++++ .../main/java/org/wisdom/api/content/Xml.java | 100 ++++ .../main/java/org/wisdom/api/http/Result.java | 20 +- .../java/org/wisdom/api/http/Results.java | 14 +- .../org/wisdom/api/bodies/RenderableTest.java | 48 +- core/wisdom-base-runtime/pom.xml | 13 + .../src/main/assembly/distribution.xml | 4 + pom.xml | 15 + 18 files changed, 1121 insertions(+), 287 deletions(-) create mode 100644 core/content-manager/src/main/java/org/wisdom/content/bodyparsers/BodyParserXML.java create mode 100644 core/content-manager/src/main/java/org/wisdom/content/jackson/JacksonSingleton.java delete mode 100644 core/content-manager/src/main/java/org/wisdom/content/json/JsonSingleton.java create mode 100644 core/content-manager/src/main/java/org/wisdom/content/serializers/XMLSerializer.java create mode 100644 core/content-manager/src/test/java/org/wisdom/content/xml/Data.java create mode 100644 core/content-manager/src/test/java/org/wisdom/content/xml/Message.java create mode 100644 core/content-manager/src/test/java/org/wisdom/content/xml/XMLSingletonTest.java create mode 100644 core/wisdom-api/src/main/java/org/wisdom/api/bodies/RenderableXML.java create mode 100644 core/wisdom-api/src/main/java/org/wisdom/api/content/Xml.java diff --git a/core/content-manager/src/main/java/org/wisdom/content/bodyparsers/BodyParserXML.java b/core/content-manager/src/main/java/org/wisdom/content/bodyparsers/BodyParserXML.java new file mode 100644 index 000000000..64b33dec5 --- /dev/null +++ b/core/content-manager/src/main/java/org/wisdom/content/bodyparsers/BodyParserXML.java @@ -0,0 +1,78 @@ +/* + * #%L + * Wisdom-Framework + * %% + * Copyright (C) 2013 - 2014 Wisdom Framework + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package org.wisdom.content.bodyparsers; + +import org.apache.felix.ipojo.annotations.Component; +import org.apache.felix.ipojo.annotations.Instantiate; +import org.apache.felix.ipojo.annotations.Provides; +import org.apache.felix.ipojo.annotations.Requires; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.wisdom.api.content.BodyParser; +import org.wisdom.api.content.Xml; +import org.wisdom.api.http.Context; +import org.wisdom.api.http.MimeTypes; + +import java.io.IOException; + +@Component +@Provides +@Instantiate +public class BodyParserXML implements BodyParser { + + @Requires + Xml xml; + + private static final String ERROR = "Error parsing incoming XML"; + + private static final Logger LOGGER = LoggerFactory.getLogger(BodyParserXML.class); + + public T invoke(Context context, Class classOfT) { + T t = null; + try { + final String content = context.body(); + if (content == null || content.length() == 0) { + return null; + } + t = xml.xmlMapper().readValue(content, classOfT); + } catch (IOException e) { + LOGGER.error(ERROR, e); + } + + return t; + } + + @Override + public T invoke(byte[] bytes, Class classOfT) { + T t = null; + try { + t = xml.xmlMapper().readValue(bytes, classOfT); + } catch (IOException e) { + LOGGER.error(ERROR, e); + } + + return t; + } + + public String getContentType() { + return MimeTypes.XML; + } + +} diff --git a/core/content-manager/src/main/java/org/wisdom/content/jackson/JacksonSingleton.java b/core/content-manager/src/main/java/org/wisdom/content/jackson/JacksonSingleton.java new file mode 100644 index 000000000..a569f0887 --- /dev/null +++ b/core/content-manager/src/main/java/org/wisdom/content/jackson/JacksonSingleton.java @@ -0,0 +1,449 @@ +/* + * #%L + * Wisdom-Framework + * %% + * Copyright (C) 2013 - 2014 Wisdom Framework + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package org.wisdom.content.jackson; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.google.common.base.Charsets; +import org.apache.commons.io.IOUtils; +import org.apache.felix.ipojo.annotations.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.wisdom.api.content.JacksonModuleRepository; +import org.wisdom.api.content.Json; +import org.wisdom.api.content.Xml; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.*; +import java.nio.charset.Charset; +import java.util.HashSet; +import java.util.Set; + +/** + * This component is a layer on top of Jackson and provides the {@link org.wisdom.api.content.Json} + * and {@link org.wisdom.api.content.Xml} services. + *

+ * This class manages Jackson module dynamically, and recreates a JSON Mapper and XML mapper every time a module arrives + * or leaves. + */ +@Component(immediate = true) +@Provides +@Instantiate +public class JacksonSingleton implements JacksonModuleRepository, Json, Xml { + + /** + * An object used as lock. + */ + private final Object lock = new Object(); + + /** + * The current object mapper. + */ + private ObjectMapper mapper; + + /** + * The current object mapper. + */ + private XmlMapper xml; + + /** + * The document builder factory used to create new document. + */ + private DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + /** + * The logger. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(JacksonSingleton.class); + + /** + * The current set of registered modules. + */ + private Set modules = new HashSet<>(); + + /** + * Gets the current mapper. + * + * @return the mapper. + */ + public ObjectMapper mapper() { + synchronized (lock) { + return mapper; + } + } + + /** + * Converts an object to JsonNode. + * + * @param data Value to convert in Json. + * @return the resulting JSON Node + * @throws java.lang.RuntimeException if the JSON Node cannot be created + */ + public JsonNode toJson(final Object data) { + synchronized (lock) { + try { + return mapper.valueToTree(data); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * Converts a JsonNode to a Java value. + * + * @param json Json value to convert. + * @param clazz Expected Java value type. + * @return the created object + * @throws java.lang.RuntimeException if the object cannot be created + */ + public A fromJson(JsonNode json, Class clazz) { + synchronized (lock) { + try { + return mapper.treeToValue(json, clazz); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * Converts a Json String to a Java value. + * + * @param json Json string to convert. + * @param clazz Expected Java value type. + * @return the created object + * @throws java.lang.RuntimeException if the object cannot be created + */ + public A fromJson(String json, Class clazz) { + synchronized (lock) { + try { + JsonNode node = mapper.readTree(json); + return mapper.treeToValue(node, clazz); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * Converts a JsonNode to its string representation. + * This implementation use a `pretty printer`. + * + * @param json the json node + * @return the String representation of the given Json Object + * @throws java.lang.RuntimeException if the String form cannot be created + */ + public String stringify(JsonNode json) { + try { + return mapper().writerWithDefaultPrettyPrinter().writeValueAsString(json); + } catch (JsonProcessingException e) { + throw new RuntimeException("Cannot stringify the input json node", e); + } + } + + /** + * Parses a String representing a json, and return it as a JsonNode. + * + * @param src the JSON String + * @return the Json Node + * @throws java.lang.RuntimeException if the given string is not a valid JSON String + */ + public JsonNode parse(String src) { + synchronized (lock) { + try { + return mapper.readValue(src, JsonNode.class); + } catch (Exception t) { + throw new RuntimeException(t); + } + } + } + + /** + * Parses a stream representing a json, and return it as a JsonNode. + * The stream is not closed by the method. + * + * @param stream the JSON stream + * @return the JSON node + * @throws java.lang.RuntimeException if the given stream is not a valid JSON String + */ + public JsonNode parse(InputStream stream) { + synchronized (lock) { + try { + return mapper.readValue(stream, JsonNode.class); + } catch (Exception t) { + throw new RuntimeException(t); + } + } + } + + /** + * Creates a new JSON Object. + * + * @return the new Object Node. + */ + @Override + public ObjectNode newObject() { + return mapper().createObjectNode(); + } + + /** + * Creates a new JSON Array. + * + * @return the new Array Node. + */ + @Override + public ArrayNode newArray() { + return mapper().createArrayNode(); + } + + /** + * Starts the JSON and XML support. + * An empty mapper is created. + */ + @Validate + public void validate() { + LOGGER.info("Starting JSON and XML support services"); + setMappers(new ObjectMapper(), new XmlMapper()); + } + + /** + * Sets the object mapper. + * + * @param mapper the object mapper to use + */ + private void setMappers(ObjectMapper mapper, XmlMapper xml) { + synchronized (lock) { + this.mapper = mapper; + this.xml = xml; + if (mapper != null) { + this.mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + } + if (xml != null) { + this.xml.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + } + } + } + + /** + * Stops the JSON and XML management. + * Releases the current mappers. + */ + @Invalidate + public void invalidate() { + setMappers(null, null); + } + + /** + * Registers a new Jackson Module. + * + * @param module the module to register + */ + @Override + public void register(Module module) { + LOGGER.info("Adding JSON module " + module.getModuleName()); + synchronized (lock) { + modules.add(module); + rebuildMappers(); + } + } + + private void rebuildMappers() { + mapper = new ObjectMapper(); + mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + for (Module module : modules) { + mapper.registerModule(module); + } + + xml = new XmlMapper(); + xml.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + for (Module module : modules) { + xml.registerModule(module); + } + } + + /** + * Un-registers a JSON Module. + * + * @param module the module + */ + @Override + public void unregister(Module module) { + LOGGER.info("Removing Jackson module " + module.getModuleName()); + synchronized (lock) { + if (modules.remove(module)) { + rebuildMappers(); + } + } + } + + + /** + * Gets the current XML mapper. + * + * @return the mapper + */ + @Override + public XmlMapper xmlMapper() { + synchronized (lock) { + return xml; + } + } + + /** + * Builds a new XML Document from the given (xml) string. + * By default this method uses UTF-8. If your document does not use UTF-8, + * use {@link #fromInputStream(java.io.InputStream, java.nio.charset.Charset)} that let you set the encoding. + * + * @param xml the xml to parse, must not be {@literal null} + * @return the document + * @throws java.io.IOException if the given string is not a valid XML document + */ + @Override + public Document fromString(String xml) throws IOException { + ByteArrayInputStream stream = null; + try { + stream = new ByteArrayInputStream(xml.getBytes(Charsets.UTF_8)); + return fromInputStream( + stream, + Charsets.UTF_8 + ); + } catch (UnsupportedEncodingException e) { + throw new IOException(e); + } finally { + IOUtils.closeQuietly(stream); + } + } + + /** + * Builds a new XML Document from the given input stream. The stream is not closed by this method, + * and so you must close it. + * + * @param stream the input stream, must not be {@literal null} + * @param encoding the encoding, if {@literal null}, UTF-8 is used. + * @return the built document + * @throws java.io.IOException if the given stream is not a valid XML document, + * or if the given encoding is not supported. + */ + @Override + public Document fromInputStream(InputStream stream, Charset encoding) throws IOException { + try { + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + + InputSource is = new InputSource(stream); + if (encoding == null) { + is.setEncoding(Charsets.UTF_8.name()); + } else { + is.setEncoding(encoding.name()); + } + + return builder.parse(is); + + } catch (ParserConfigurationException | SAXException e) { + throw new IOException("Cannot parse the given XML document", e); + } + } + + /** + * Builds a new instance of the given class clazz from the given XML document. + * + * @param document the XML document + * @param clazz the class of the instance to construct + * @return an instance of the class. + */ + @Override + public A fromXML(Document document, Class clazz) { + return fromXML(stringify(document), clazz); + } + + /** + * Builds a new instance of the given class clazz from the given XML string. + * + * @param xml the XML string + * @param clazz the class of the instance to construct + * @return an instance of the class. + */ + @Override + public A fromXML(String xml, Class clazz) { + try { + return xmlMapper().readValue(xml, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Retrieves the string form of the given XML document. + * + * @param document the XML document, must not be {@literal null} + * @return the String form of the object + */ + @Override + public String stringify(Document document) { + try { + StringWriter sw = new StringWriter(); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + + transformer.transform(new DOMSource(document), new StreamResult(sw)); + return sw.toString(); + } catch (TransformerException e) { + throw new RuntimeException(e); + } + } + + /** + * Creates a new document. + * + * @return the document + */ + public Document newDocument() { + try { + return factory.newDocumentBuilder().newDocument(); + } catch (ParserConfigurationException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/core/content-manager/src/main/java/org/wisdom/content/json/JsonSingleton.java b/core/content-manager/src/main/java/org/wisdom/content/json/JsonSingleton.java deleted file mode 100644 index c27289038..000000000 --- a/core/content-manager/src/main/java/org/wisdom/content/json/JsonSingleton.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * #%L - * Wisdom-Framework - * %% - * Copyright (C) 2013 - 2014 Wisdom Framework - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -package org.wisdom.content.json; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.apache.felix.ipojo.annotations.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.wisdom.api.content.JacksonModuleRepository; -import org.wisdom.api.content.Json; - -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * A component managing Json. - *

- * This class manages JSON module dynamically, and recreates a JSON Mapper every time a module arrives or leaves. - */ -@Component(immediate = true) -@Provides -@Instantiate -public class JsonSingleton implements JacksonModuleRepository, Json { - - /** - * An object used as lock. - */ - private final Object lock = new Object(); - - /** - * The current object mapper. - */ - private ObjectMapper mapper; - - /** - * The logger. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(JsonSingleton.class); - - /** - * The current list of registered modules. - */ - private List modules = new ArrayList<>(); - - /** - * Gets the current mapper. - * - * @return the mapper. - */ - public ObjectMapper mapper() { - synchronized (lock) { - return mapper; - } - } - - /** - * Converts an object to JsonNode. - * - * @param data Value to convert in Json. - * @return the resulting JSON Node - * @throws java.lang.RuntimeException if the JSON Node cannot be created - */ - public JsonNode toJson(final Object data) { - synchronized (lock) { - try { - return mapper.valueToTree(data); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - /** - * Converts a JsonNode to a Java value. - * - * @param json Json value to convert. - * @param clazz Expected Java value type. - * @return the created object - * @throws java.lang.RuntimeException if the object cannot be created - */ - public A fromJson(JsonNode json, Class clazz) { - synchronized (lock) { - try { - return mapper.treeToValue(json, clazz); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - /** - * Converts a Json String to a Java value. - * - * @param json Json string to convert. - * @param clazz Expected Java value type. - * @return the created object - * @throws java.lang.RuntimeException if the object cannot be created - */ - public A fromJson(String json, Class clazz) { - synchronized (lock) { - try { - JsonNode node = mapper.readTree(json); - return mapper.treeToValue(node, clazz); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - /** - * Converts a JsonNode to its string representation. - * This implementation use a `pretty printer`. - * - * @param json the json node - * @return the String representation of the given Json Object - * @throws java.lang.RuntimeException if the String form cannot be created - */ - public String stringify(JsonNode json) { - try { - return mapper().writerWithDefaultPrettyPrinter().writeValueAsString(json); - } catch (JsonProcessingException e) { - throw new RuntimeException("Cannot stringify the input json node", e); - } - } - - /** - * Parses a String representing a json, and return it as a JsonNode. - * - * @param src the JSON String - * @return the Json Node - * @throws java.lang.RuntimeException if the given string is not a valid JSON String - */ - public JsonNode parse(String src) { - synchronized (lock) { - try { - return mapper.readValue(src, JsonNode.class); - } catch (Exception t) { - throw new RuntimeException(t); - } - } - } - - /** - * Parses a stream representing a json, and return it as a JsonNode. - * The stream is not closed by the method. - * - * @param stream the JSON stream - * @return the JSON node - * @throws java.lang.RuntimeException if the given stream is not a valid JSON String - */ - public JsonNode parse(InputStream stream) { - synchronized (lock) { - try { - return mapper.readValue(stream, JsonNode.class); - } catch (Exception t) { - throw new RuntimeException(t); - } - } - } - - /** - * Creates a new JSON Object. - * - * @return the new Object Node. - */ - @Override - public ObjectNode newObject() { - return mapper().createObjectNode(); - } - - /** - * Creates a new JSON Array. - * - * @return the new Array Node. - */ - @Override - public ArrayNode newArray() { - return mapper().createArrayNode(); - } - - /** - * Starts the JSON support. - * An empty mapper is created. - */ - @Validate - public void validate() { - LOGGER.info("Starting JSON support service"); - setMapper(new ObjectMapper()); - } - - /** - * Sets the object mapper. - * - * @param mapper the object mapper to use - */ - private void setMapper(ObjectMapper mapper) { - synchronized (lock) { - this.mapper = mapper; - if (mapper != null) { - this.mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - } - } - } - - /** - * Stops the JSON management. - * Releases the current mapper. - */ - @Invalidate - public void invalidate() { - setMapper(null); - } - - /** - * Registers a new JSON Module. - * - * @param module the module to register - */ - @Override - public void register(Module module) { - LOGGER.info("Adding JSON module " + module.getModuleName()); - synchronized (lock) { - modules.add(module); - rebuildMapper(); - } - } - - private void rebuildMapper() { - mapper = new ObjectMapper(); - mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - for (Module module : modules) { - mapper.registerModule(module); - } - } - - /** - * Un-registers a JSON Module. - * - * @param module the module - */ - @Override - public void unregister(Module module) { - LOGGER.info("Removing JSON module " + module.getModuleName()); - synchronized (lock) { - if (modules.contains(module)) { - rebuildMapper(); - } - } - } - -} diff --git a/core/content-manager/src/main/java/org/wisdom/content/serializers/JSONSerializer.java b/core/content-manager/src/main/java/org/wisdom/content/serializers/JSONSerializer.java index 773e77bc2..55a7a3a1a 100644 --- a/core/content-manager/src/main/java/org/wisdom/content/serializers/JSONSerializer.java +++ b/core/content-manager/src/main/java/org/wisdom/content/serializers/JSONSerializer.java @@ -30,7 +30,7 @@ import org.wisdom.api.http.Renderable; /** - * Renders HTML content. + * Renders JSON content. */ @Component @Instantiate diff --git a/core/content-manager/src/main/java/org/wisdom/content/serializers/XMLSerializer.java b/core/content-manager/src/main/java/org/wisdom/content/serializers/XMLSerializer.java new file mode 100644 index 000000000..c48885ea0 --- /dev/null +++ b/core/content-manager/src/main/java/org/wisdom/content/serializers/XMLSerializer.java @@ -0,0 +1,57 @@ +/* + * #%L + * Wisdom-Framework + * %% + * Copyright (C) 2013 - 2014 Wisdom Framework + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package org.wisdom.content.serializers; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.apache.felix.ipojo.annotations.Component; +import org.apache.felix.ipojo.annotations.Instantiate; +import org.apache.felix.ipojo.annotations.Provides; +import org.apache.felix.ipojo.annotations.Requires; +import org.wisdom.api.content.ContentSerializer; +import org.wisdom.api.content.Xml; +import org.wisdom.api.http.MimeTypes; +import org.wisdom.api.http.Renderable; + +/** + * Renders XML content. + */ +@Component +@Instantiate +@Provides +public class XMLSerializer implements ContentSerializer { + + @Requires + private Xml xml; + + @Override + public String getContentType() { + return MimeTypes.XML; + } + + @Override + public void serialize(Renderable renderable) { + try { + String serialized = xml.xmlMapper().writeValueAsString(renderable.content()); + renderable.setSerializedForm(serialized); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/core/content-manager/src/test/java/org/wisdom/content/json/JsonSingletonTest.java b/core/content-manager/src/test/java/org/wisdom/content/json/JsonSingletonTest.java index 8950a9084..b7f5e062e 100644 --- a/core/content-manager/src/test/java/org/wisdom/content/json/JsonSingletonTest.java +++ b/core/content-manager/src/test/java/org/wisdom/content/json/JsonSingletonTest.java @@ -24,11 +24,9 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.wisdom.api.content.Json; +import org.wisdom.content.jackson.JacksonSingleton; -import java.io.BufferedInputStream; import java.io.InputStream; -import java.util.ArrayList; import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; @@ -38,7 +36,7 @@ */ public class JsonSingletonTest { - JsonSingleton json = new JsonSingleton(); + JacksonSingleton json = new JacksonSingleton(); @Before public void setUp() { diff --git a/core/content-manager/src/test/java/org/wisdom/content/xml/Data.java b/core/content-manager/src/test/java/org/wisdom/content/xml/Data.java new file mode 100644 index 000000000..c964a8d4a --- /dev/null +++ b/core/content-manager/src/test/java/org/wisdom/content/xml/Data.java @@ -0,0 +1,33 @@ +/* + * #%L + * Wisdom-Framework + * %% + * Copyright (C) 2013 - 2014 Wisdom Framework + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package org.wisdom.content.xml; + +/** + * Created by clement on 19/04/2014. + */ +public class Data { + + public int version; + + public String lg; + + public Message message; + +} diff --git a/core/content-manager/src/test/java/org/wisdom/content/xml/Message.java b/core/content-manager/src/test/java/org/wisdom/content/xml/Message.java new file mode 100644 index 000000000..67116aab6 --- /dev/null +++ b/core/content-manager/src/test/java/org/wisdom/content/xml/Message.java @@ -0,0 +1,33 @@ +/* + * #%L + * Wisdom-Framework + * %% + * Copyright (C) 2013 - 2014 Wisdom Framework + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package org.wisdom.content.xml; + +/** + * Created by clement on 19/04/2014. + */ +public class Message { + + public String message; + + public Message(String msg) { + this.message = msg; + } + +} diff --git a/core/content-manager/src/test/java/org/wisdom/content/xml/XMLSingletonTest.java b/core/content-manager/src/test/java/org/wisdom/content/xml/XMLSingletonTest.java new file mode 100644 index 000000000..7085385eb --- /dev/null +++ b/core/content-manager/src/test/java/org/wisdom/content/xml/XMLSingletonTest.java @@ -0,0 +1,141 @@ +/* + * #%L + * Wisdom-Framework + * %% + * Copyright (C) 2013 - 2014 Wisdom Framework + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package org.wisdom.content.xml; + +import com.google.common.base.Charsets; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wisdom.content.jackson.JacksonSingleton; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test the XML support. + */ +public class XMLSingletonTest { + + JacksonSingleton xml = new JacksonSingleton(); + + @Before + public void setUp() { + xml.validate(); + } + + @Test + public void testMapper() throws Exception { + assertThat(xml.xmlMapper()).isNotNull(); + } + + @Test + public void testFromString() throws Exception { + String txt = "\n" + + "\n" + + "Hello\n" + + "Welcome\n" + + "\n"; + + Document document = xml.fromString(txt); + assertThat(document.getDocumentElement().getAttribute("lg")).isEqualTo("english"); + assertThat(document.getDocumentElement().getAttribute("version")).isEqualTo("1"); + assertThat(document.getDocumentElement().getElementsByTagName("message").getLength()).isEqualTo(2); + } + + @Test + public void testFromInputStream() throws Exception { + String txt = "\n" + + "\n" + + "Hello\n" + + "Welcome\n" + + "\n"; + + InputStream stream = new ByteArrayInputStream(txt.getBytes(Charsets.UTF_8)); + Document document = xml.fromInputStream(stream, Charsets.UTF_8); + assertThat(document.getDocumentElement().getAttribute("lg")).isEqualTo("english"); + assertThat(document.getDocumentElement().getAttribute("version")).isEqualTo("1"); + assertThat(document.getDocumentElement().getElementsByTagName("message").getLength()).isEqualTo(2); + IOUtils.closeQuietly(stream); + + // Same without charset. + stream = new ByteArrayInputStream(txt.getBytes(Charsets.UTF_8)); + document = xml.fromInputStream(stream, null); + assertThat(document.getDocumentElement().getAttribute("lg")).isEqualTo("english"); + assertThat(document.getDocumentElement().getAttribute("version")).isEqualTo("1"); + assertThat(document.getDocumentElement().getElementsByTagName("message").getLength()).isEqualTo(2); + IOUtils.closeQuietly(stream); + } + + @Test + public void testFromXMLUsingDocument() throws Exception { + Document document = xml.newDocument(); + Element root = document.createElement("data"); + root.setAttribute("version", "1"); + root.setAttribute("lg", "english"); + Element message = document.createElement("message"); + message.setTextContent("Hello"); + root.appendChild(message); + document.appendChild(root); + + Data data = xml.fromXML(document, Data.class); + assertThat(data.lg).isEqualTo("english"); + assertThat(data.version).isEqualTo(1); + assertThat(data.message.message).isEqualTo("Hello"); + } + + @Test + public void testFromXMLUsingString() throws Exception { + String txt = "" + + "Hello"; + + Data data = xml.fromXML(txt, Data.class); + assertThat(data.lg).isEqualTo("en"); + assertThat(data.version).isEqualTo(1); + assertThat(data.message.message).isEqualTo("Hello"); + } + + @Test + public void testStringify() throws Exception { + Document document = xml.newDocument(); + Element root = document.createElement("data"); + root.setAttribute("version", "1"); + root.setAttribute("lg", "english"); + Element message = document.createElement("message"); + message.setTextContent("Hello"); + Element message2 = document.createElement("message"); + message2.setTextContent("Welcome"); + root.appendChild(message); + root.appendChild(message2); + document.appendChild(root); + + assertThat(xml.stringify(document)) + .contains("?xml") + .contains("UTF-8") + .contains("Hello") + .contains("Welcome"); + } +} diff --git a/core/wisdom-api/pom.xml b/core/wisdom-api/pom.xml index 4c8b8f9f3..1d11f58ed 100644 --- a/core/wisdom-api/pom.xml +++ b/core/wisdom-api/pom.xml @@ -19,6 +19,10 @@ com.fasterxml.jackson.core jackson-databind + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + com.google.guava guava diff --git a/core/wisdom-api/src/main/java/org/wisdom/api/bodies/RenderableXML.java b/core/wisdom-api/src/main/java/org/wisdom/api/bodies/RenderableXML.java new file mode 100644 index 000000000..2671338f3 --- /dev/null +++ b/core/wisdom-api/src/main/java/org/wisdom/api/bodies/RenderableXML.java @@ -0,0 +1,116 @@ +/* + * #%L + * Wisdom-Framework + * %% + * Copyright (C) 2013 - 2014 Wisdom Framework + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package org.wisdom.api.bodies; + +import com.google.common.base.Charsets; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wisdom.api.http.*; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.StringWriter; + +/** + * A renderable object taking an Document as parameter. + */ +public class RenderableXML implements Renderable { + + private final Document document; + private byte[] rendered; + + public RenderableXML(Document doc) { + this.document = doc; + } + + public RenderableXML(Element element) { + this(element.getOwnerDocument()); + } + + @Override + public InputStream render(Context context, Result result) throws RenderableException { + if (rendered == null) { + _render(); + } + return new ByteArrayInputStream(rendered); + } + + private void _render() throws RenderableException { + try { + StringWriter sw = new StringWriter(); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + + transformer.transform(new DOMSource(document), new StreamResult(sw)); + rendered = sw.toString().getBytes(Charsets.UTF_8); + } catch (Exception ex) { + throw new RenderableException("Error converting XML document to String", ex); + } + } + + @Override + public long length() { + if (rendered == null) { + try { + _render(); + } catch (RenderableException e) { //NOSONAR + LoggerFactory.getLogger(RenderableXML.class).warn("Cannot render XML object {}", document, e); + return -1; + } + } + return rendered.length; + } + + @Override + public String mimetype() { + return MimeTypes.XML; + } + + @Override + public Document content() { + return document; + } + + @Override + public boolean requireSerializer() { + return false; + } + + @Override + public void setSerializedForm(String serialized) { + // Nothing because serialization is not supported for this renderable class. + } + + @Override + public boolean mustBeChunked() { + return false; + } + +} diff --git a/core/wisdom-api/src/main/java/org/wisdom/api/content/Xml.java b/core/wisdom-api/src/main/java/org/wisdom/api/content/Xml.java new file mode 100644 index 000000000..b81f964f9 --- /dev/null +++ b/core/wisdom-api/src/main/java/org/wisdom/api/content/Xml.java @@ -0,0 +1,100 @@ +/* + * #%L + * Wisdom-Framework + * %% + * Copyright (C) 2013 - 2014 Wisdom Framework + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package org.wisdom.api.content; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import org.w3c.dom.Document; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +/** + * A service interface used to handle XML objects and String. + * It is basically a layer on top of Jackson. Be aware that implementation should support the dynamic addition and + * removal of serializer/deserializer, making this service the main entry point to the XML support. + */ +public interface Xml { + + /** + * Gets the current XML mapper. + * + * @return the mapper + */ + public XmlMapper xmlMapper(); + + /** + * Builds a new XML Document from the given (xml) string. + * By default this method uses UTF-8. If your document does not use UTF-8, + * use {@link #fromInputStream(java.io.InputStream, java.nio.charset.Charset)} that let you set the encoding. + * + * @param xml the xml to parse, must not be {@literal null} + * @return the document + * @throws java.io.IOException if the given string is not a valid XML document + */ + public Document fromString(String xml) throws IOException; + + /** + * Builds a new XML Document from the given input stream. The stream is not closed by this method, + * and so you must close it. + * + * @param stream the input stream, must not be {@literal null} + * @param encoding the encoding, if {@literal null}, UTF-8 is used. + * @return the built document + * @throws java.io.IOException if the given stream is not a valid XML document, + * or if the given encoding is not supported. + */ + Document fromInputStream(InputStream stream, Charset encoding) throws IOException; + + /** + * Builds a new instance of the given class clazz from the given XML document. + * + * @param document the XML document + * @param clazz the class of the instance to construct + * @return an instance of the class. + */ + A fromXML(Document document, Class clazz); + + /** + * Builds a new instance of the given class clazz from the given XML string. + * + * @param xml the XML string + * @param clazz the class of the instance to construct + * @return an instance of the class. + */ + A fromXML(String xml, Class clazz); + + /** + * Retrieves the string form of the given XML document. + * + * @param xml the XML document, must not be {@literal null} + * @return the String form of the object + */ + String stringify(Document xml); + + /** + * Creates a new document. + * + * @return the new document + */ + Document newDocument(); + + +} diff --git a/core/wisdom-api/src/main/java/org/wisdom/api/http/Result.java b/core/wisdom-api/src/main/java/org/wisdom/api/http/Result.java index 4701de901..333029427 100644 --- a/core/wisdom-api/src/main/java/org/wisdom/api/http/Result.java +++ b/core/wisdom-api/src/main/java/org/wisdom/api/http/Result.java @@ -20,14 +20,11 @@ package org.wisdom.api.http; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Charsets; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import org.wisdom.api.bodies.NoHttpBody; -import org.wisdom.api.bodies.RenderableJson; -import org.wisdom.api.bodies.RenderableObject; -import org.wisdom.api.bodies.RenderableString; +import org.w3c.dom.Document; +import org.wisdom.api.bodies.*; import org.wisdom.api.cookies.Cookie; import org.wisdom.api.utils.DateUtil; @@ -134,6 +131,19 @@ public Result render(JsonNode node) { return this; } + /** + * Sets the content of the current result to the given XML document. It also sets the content-type header to XML + * and the charset to UTF-8. + * + * @param document the content + * @return the current result + */ + public Result render(Document document) { + this.content = new RenderableXML(document); + xml(); + return this; + } + /** * Sets the content of the current result to the given content. * diff --git a/core/wisdom-api/src/main/java/org/wisdom/api/http/Results.java b/core/wisdom-api/src/main/java/org/wisdom/api/http/Results.java index 9135988e2..48bb6dc0d 100644 --- a/core/wisdom-api/src/main/java/org/wisdom/api/http/Results.java +++ b/core/wisdom-api/src/main/java/org/wisdom/api/http/Results.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Preconditions; +import org.w3c.dom.Document; import org.wisdom.api.bodies.NoHttpBody; import org.wisdom.api.bodies.RenderableFile; import org.wisdom.api.bodies.RenderableStream; @@ -60,11 +61,22 @@ public static Result ok() { return status(Result.OK).render(new NoHttpBody()); } + /** + * Generates a result with the {@literal 200 - OK} status and with the given XML content. The result has the + * {@literal Content-Type} header set to {@literal application/xml}. + * + * @param document the XML document + * @return a new configured result + */ + public static Result ok(Document document) { + return status(Result.OK).render(document).as(MimeTypes.XML); + } + /** * Generates a result with the {@literal 200 - OK} status and with the given JSON content. The result has the * {@literal Content-Type} header set to {@literal application/json}. * - * @param node the json object (array of object) + * @param node the json object (JSON array or JSON object) * @return a new configured result */ public static Result ok(JsonNode node) { diff --git a/core/wisdom-api/src/test/java/org/wisdom/api/bodies/RenderableTest.java b/core/wisdom-api/src/test/java/org/wisdom/api/bodies/RenderableTest.java index 8d74bcbd2..bd32d7360 100644 --- a/core/wisdom-api/src/test/java/org/wisdom/api/bodies/RenderableTest.java +++ b/core/wisdom-api/src/test/java/org/wisdom/api/bodies/RenderableTest.java @@ -22,13 +22,19 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Charsets; - import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; import org.wisdom.api.http.MimeTypes; +import org.xml.sax.InputSource; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayInputStream; import java.io.File; +import java.io.StringReader; import java.io.StringWriter; import java.util.Arrays; import java.util.List; @@ -190,6 +196,46 @@ public void testRenderableJson() throws Exception { assertThat(new String(bytes, Charsets.UTF_8)).isEqualTo(nodeasString); } + @Test + public void testRenderableXMLFromDocument() throws Exception { + String LF = System.getProperty("line.separator"); + final String xml = "" + LF + + "hello" + LF; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(new InputSource(new StringReader("hello"))); + + RenderableXML body = new RenderableXML(document); + assertThat(body.length()).isEqualTo(xml.length()); + assertThat(body.mimetype()).isEqualTo(MimeTypes.XML); + assertThat(body.content()).isNotNull(); + assertThat(body.mustBeChunked()).isFalse(); + assertThat(body.requireSerializer()).isFalse(); + byte[] bytes = IOUtils.toByteArray(body.render(null, null)); + assertThat(new String(bytes, Charsets.UTF_8)).isEqualTo(xml); + } + + @Test + public void testRenderableXMLFromElement() throws Exception { + String LF = System.getProperty("line.separator"); + final String xml = "" + LF + + "hello" + LF; + Element node = DocumentBuilderFactory + .newInstance() + .newDocumentBuilder() + .parse(new ByteArrayInputStream("hello".getBytes())) + .getDocumentElement(); + + RenderableXML body = new RenderableXML(node); + assertThat(body.length()).isEqualTo(xml.length()); + assertThat(body.mimetype()).isEqualTo(MimeTypes.XML); + assertThat(body.content()).isNotNull(); + assertThat(body.mustBeChunked()).isFalse(); + assertThat(body.requireSerializer()).isFalse(); + byte[] bytes = IOUtils.toByteArray(body.render(null, null)); + assertThat(new String(bytes, Charsets.UTF_8)).isEqualTo(xml); + } + @Test public void testRenderableObject() throws Exception { List list = Arrays.asList("a", "b", "c"); diff --git a/core/wisdom-base-runtime/pom.xml b/core/wisdom-base-runtime/pom.xml index bae03f421..e294888a4 100644 --- a/core/wisdom-base-runtime/pom.xml +++ b/core/wisdom-base-runtime/pom.xml @@ -154,6 +154,19 @@ com.fasterxml.jackson.dataformat jackson-dataformat-xml + + com.fasterxml.jackson.module + jackson-module-jaxb-annotations + + + org.codehaus.woodstox + woodstox-core-asl + + + org.codehaus.woodstox + stax2-api + + diff --git a/core/wisdom-base-runtime/src/main/assembly/distribution.xml b/core/wisdom-base-runtime/src/main/assembly/distribution.xml index 19d074a26..176dd60c3 100644 --- a/core/wisdom-base-runtime/src/main/assembly/distribution.xml +++ b/core/wisdom-base-runtime/src/main/assembly/distribution.xml @@ -60,6 +60,10 @@ com.fasterxml.jackson.core:* *:jackson-databind + *:jackson-dataformat-xml + *:jackson-module-jaxb-annotations + *:woodstox-core-asl + *:stax2-api *:joda-time diff --git a/pom.xml b/pom.xml index 01d3a9773..5c562c01e 100644 --- a/pom.xml +++ b/pom.xml @@ -454,6 +454,21 @@ jackson-databind 2.3.2 + + com.fasterxml.jackson.module + jackson-module-jaxb-annotations + 2.3.2 + + + org.codehaus.woodstox + woodstox-core-asl + 4.2.1 + + + org.codehaus.woodstox + stax2-api + 3.1.4 + com.typesafe.akka