package org.collectionspace.services.common.xmljson;
/**
- * A lightweight representation of a JSON field. Instances are created
+ * <p>A lightweight representation of a JSON field. Instances are created
* by JsonToXmlStreamConverter in the course of parsing JSON, in order
- * to track the current state.
+ * to track the current state.</p>
*
- * Each JSON field has a name and a type, which is either scalar, array,
- * or object.
+ * <p>Each JSON field has a name and a type, which is either scalar, array,
+ * or object.</p>
*/
public class JsonField {
private String name;
import org.apache.commons.io.output.ByteArrayOutputStream;
/**
- * A filter that translates JSON to XML.
+ * <p>A filter that translates JSON requests to XML.</p>
+ *
+ * <p>This filter only has an effect if the content of the request (determined from
+ * the Content-type header) is JSON. If the content is JSON, the request is
+ * wrapped.</p>
+ *
+ * <p>The request wrapper changes the Content-type header to XML, and provides an
+ * input stream containing an XML translation of the original JSON request
+ * content.</p>
*/
public class JsonToXmlFilter implements Filter {
}
/**
- * A request wrapper that .
+ * A request wrapper that has a content type of XML, and replaces the
+ * wrapped input stream with an XML translation.
*/
public class RequestWrapper extends HttpServletRequestWrapper {
public RequestWrapper(HttpServletRequest request) {
throw new IOException("error converting JSON stream to XML", e);
}
- final InputStream xmlInput = out.toInputStream();
+ return new InputStreamWrapper(out.toInputStream());
+ }
+ }
+
+ /**
+ * A ServletInputStream that wraps another input stream.
+ */
+ public class InputStreamWrapper extends ServletInputStream {
- return new ServletInputStream() {
- @Override
- public int read() throws IOException {
- return xmlInput.read();
- }
- };
+ /**
+ * The wrapped input stream.
+ */
+ private InputStream in;
+
+ /**
+ * Creates an InputStreamWrapper that wraps a given input stream.
+ *
+ * @param in the input stream to wrap
+ */
+ public InputStreamWrapper(InputStream in) {
+ this.in = in;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return in.read();
}
}
}
import com.fasterxml.jackson.core.JsonToken;
/**
- * Converts a CSpace JSON payload to an XML payload.
+ * <p>Converts a CSpace JSON payload to an XML payload.</p>
*
- * This class is not intended to serve as a general purpose JSON to XML
+ * <p>This class is not intended to serve as a general purpose JSON to XML
* translator. It is instead a lightweight processor tuned for the kinds
* of JSON generated by CSpace, and the particular transformations needed
- * to generate XML for CSpace.
+ * to generate XML for CSpace.</p>
*
+ * <p>
* The conversion is performed as follows:
* <ul>
* <li>JSON fields starting with "@xmlns:" are converted XML namespace declarations.</li>
* <li>The contents of JSON arrays are expanded into multiple XML elements, each
* named with the field name of the JSON array.</li>
* </ul>
+ * </p>
*
- * This implementation is schema-unaware. It operates by examining only the input
- * document, without utilizing any XML schema information.
+ * <p>This implementation is schema-unaware. It operates by examining only the input
+ * document, without utilizing any XML schema information.</p>
*
- * Example:
+ * <p>Example:</p>
*
+ * <p>
* JSON
* <pre>
* {
* }
* }
* </pre>
- *
+ * </p>
+ *
+ * <p>
* XML
* <pre>
- * <document name="collectionobjects">
- * <ns2:collectionobjects_common xmlns:ns2="http://collectionspace.org/services/collectionobject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
- * <objectNumber>2016.1.1</objectNumber>
- * <objectNameList>
- * <objectNameGroup>
- * <objectNameCurrency/>
- * <objectNameLanguage/>
- * <objectName>Object name</objectName>
- * <objectNameSystem/>
- * <objectNameType/>
- * <objectNameNote/>
- * <objectNameLevel/>
- * </objectNameGroup>
- * <objectNameGroup>
- * <objectNameCurrency/>
- * <objectNameLanguage/>
- * <objectName>Another name</objectName>
- * <objectNameSystem/>
- * <objectNameType/>
- * <objectNameNote/>
- * <objectNameLevel/>
- * </objectNameGroup>
- * </objectNameList>
- * <comments>
- * <comment>Some comment text</comment>
- * <comment>Another comment</comment>
- * </comments>
- * </ns2:collectionobjects_common>
- * </document>
+ * <document name="collectionobjects">
+ * <ns2:collectionobjects_common xmlns:ns2="http://collectionspace.org/services/collectionobject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ * <objectNumber>2016.1.1</objectNumber>
+ * <objectNameList>
+ * <objectNameGroup>
+ * <objectNameCurrency/>
+ * <objectNameLanguage/>
+ * <objectName>Object name</objectName>
+ * <objectNameSystem/>
+ * <objectNameType/>
+ * <objectNameNote/>
+ * <objectNameLevel/>
+ * </objectNameGroup>
+ * <objectNameGroup>
+ * <objectNameCurrency/>
+ * <objectNameLanguage/>
+ * <objectName>Another name</objectName>
+ * <objectNameSystem/>
+ * <objectNameType/>
+ * <objectNameNote/>
+ * <objectNameLevel/>
+ * </objectNameGroup>
+ * </objectNameList>
+ * <comments>
+ * <comment>Some comment text</comment>
+ * <comment>Another comment</comment>
+ * </comments>
+ * </ns2:collectionobjects_common>
+ * </document>
* </pre>
+ * </p>
*
- * This implementation uses a streaming JSON parser and a streaming
+ * <p>This implementation uses a streaming JSON parser and a streaming
* XML writer to do a direct stream-to-stream conversion, without
- * building a complete in-memory representation of the document.
+ * building a complete in-memory representation of the document.</p>
*/
public class JsonToXmlStreamConverter {
import com.fasterxml.jackson.annotation.JsonValue;
/**
- * A lightweight representation of an XML node. Instances are created
+ * <p>A lightweight representation of an XML node. Instances are created
* by XmlToJsonStreamConverter in the course of parsing XML. This class
* differs from a DOM node in that it is intended to contain just the
* information needed to generate JSON for CSpace, in a structure that
- * is optimized for doing that generation.
+ * is optimized for doing that generation.</p>
*
- * Each XML node has a name, and optionally namespaces and attributes.
+ * <p>Each XML node has a name, and optionally namespaces and attributes.
* The node may contain either text or child nodes (not both, as CSpace
- * XML is assumed not to contain mixed-content elements).
+ * XML is assumed not to contain mixed-content elements).</p>
*/
public class XmlNode {
/**
}
/**
- * Gets the value of the node. If this is a text node, the
+ * <p>Gets the value of the node. If this is a text node, the
* value is a String. Otherwise it's a map of the node's
* namespaces, attributes, and children, via
- * getCombinedMap().
+ * getCombinedMap().</p>
*
- * Note that namespaces and attributes are not returned
+ * <p>Note that namespaces and attributes are not returned
* as part of a text node's value. It is assumed that text
- * nodes do not have namespace declarations or attributes.
+ * nodes do not have namespace declarations or attributes.</p>
*
* @return the node's value
*/
}
/**
- * Adds a child node to this node.
+ * <p>Adds a child node to this node.</p>
*
- * If the node contains any text content, the text content
+ * <p>If the node contains any text content, the text content
* is removed, and text content is disallowed from being added
- * in the future.
+ * in the future.</p>
*
- * If the node to be added contains no content, and
- * isDiscardEmptyChildren() is true, the node is not added.
+ * <p>If the node to be added contains no content, and
+ * isDiscardEmptyChildren() is true, the node is not added.</p>
*
* @param node the node to add as a child
*/
import org.apache.commons.io.output.ByteArrayOutputStream;
/**
- * A filter that translates XML responses to JSON.
+ * <p>A filter that translates XML responses to JSON.</p>
*
- * This filter only has an effect if the preferred content type of the response
+ * <p>This filter only has an effect if the preferred content type of the response
* (determined from the Accept header) is JSON. If JSON is preferred, both the
- * request and response are wrapped.
+ * request and response are wrapped.</p>
*
- * The request wrapper modifies the Accept header, ensuring that XML is accepted
+ * <p>The request wrapper modifies the Accept header, ensuring that XML is accepted
* in addition to (but at a lower quality factor than) JSON. This handles the
* case where the original request only accepts JSON. In that case, XML should
* also be accepted, so that the XML response may be translated to JSON on the
- * way back.
+ * way back.</p>
*
- * The response wrapper provides a buffered output stream, so that XML output
+ * <p>The response wrapper provides a buffered output stream, so that XML output
* is captured before being sent over the network. If the content type of the
* response is XML, the content type is changed to JSON, the content of the
* buffer is translated to JSON, and the JSON is written to the original output
* stream. If the content type of the response is not XML, the content type is
* not changed, and the content of the buffer is written to the original
- * output stream unchanged.
+ * output stream unchanged.</p>
*/
public class XmlToJsonFilter implements Filter {
import com.fasterxml.jackson.databind.ObjectMapper;
/**
- * Converts a CSpace XML payload to a JSON payload.
+ * <p>Converts a CSpace XML payload to a JSON payload.</p>
*
- * This class is not intended to serve as a general purpose XML to JSON
+ * <p>This class is not intended to serve as a general purpose XML to JSON
* translator. It is instead a lightweight processor tuned for the kinds
* of XML generated by CSpace, and the particular transformations needed
* to generate JSON for CSpace. The XML input is expected to conform to
- * conventions (described below) of CSpace XML payloads.
+ * conventions (described below) of CSpace XML payloads.</p>
*
+ * <p>
* The conversion is performed as follows:
* <ul>
* <li>XML elements are converted to identically-named JSON fields.</li>
* <li>XML namespace declarations are converted to JSON fields prepended with "@xmlns:".</li>
* <li>Sibling XML elements that have the same name are converted to JSON arrays.</li>
* </ul>
+ * </p>
*
+ * <p>
* This implementation is schema-unaware. It operates by examining only the input
* document, without utilizing any XML schema information. This allows for speed
* and simplicity, but has some consequences:
* necessary to read the entire XML document into memory first, instead of
* doing a direct stream-to-stream conversion.</li>
* </ul>
- *
- * Example:
+ * </p>
+ *
+ * <p>Example:</p>
*
+ * <p>
* XML
* <pre>
- * <document name="collectionobjects">
- * <ns2:collectionspace_core xmlns:ns2="http://collectionspace.org/collectionspace_core/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
- * <createdBy>admin@core.collectionspace.org</createdBy>
- * <createdAt>2016-07-27T04:31:38.290Z</createdAt>
- * </ns2:collectionspace_core>
- * <ns2:collectionobjects_common xmlns:ns2="http://collectionspace.org/services/collectionobject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
- * <objectNumber>2016.1.1</objectNumber>
- * <objectNameList>
- * <objectNameGroup>
- * <objectNameCurrency/>
- * <objectNameLanguage/>
- * <objectName>Object name</objectName>
- * <objectNameSystem/>
- * <objectNameType/>
- * <objectNameNote/>
- * <objectNameLevel/>
- * </objectNameGroup>
- * <objectNameGroup>
- * <objectNameCurrency/>
- * <objectNameLanguage/>
- * <objectName>Another name</objectName>
- * <objectNameSystem/>
- * <objectNameType/>
- * <objectNameNote/>
- * <objectNameLevel/>
- * </objectNameGroup>
- * </objectNameList>
- * <comments>
- * <comment>Some comment text</comment>
- * <comment>Another comment</comment>
- * </comments>
- * </ns2:collectionobjects_common>
- * </document>
+ * <document name="collectionobjects">
+ * <ns2:collectionspace_core xmlns:ns2="http://collectionspace.org/collectionspace_core/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ * <createdBy>admin@core.collectionspace.org</createdBy>
+ * <createdAt>2016-07-27T04:31:38.290Z</createdAt>
+ * </ns2:collectionspace_core>
+ * <ns2:collectionobjects_common xmlns:ns2="http://collectionspace.org/services/collectionobject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ * <objectNumber>2016.1.1</objectNumber>
+ * <objectNameList>
+ * <objectNameGroup>
+ * <objectNameCurrency/>
+ * <objectNameLanguage/>
+ * <objectName>Object name</objectName>
+ * <objectNameSystem/>
+ * <objectNameType/>
+ * <objectNameNote/>
+ * <objectNameLevel/>
+ * </objectNameGroup>
+ * <objectNameGroup>
+ * <objectNameCurrency/>
+ * <objectNameLanguage/>
+ * <objectName>Another name</objectName>
+ * <objectNameSystem/>
+ * <objectNameType/>
+ * <objectNameNote/>
+ * <objectNameLevel/>
+ * </objectNameGroup>
+ * </objectNameList>
+ * <comments>
+ * <comment>Some comment text</comment>
+ * <comment>Another comment</comment>
+ * </comments>
+ * </ns2:collectionobjects_common>
+ * </document>
* </pre>
+ * </p>
*
+ * <p>
* JSON
* <pre>
* {
* }
* }
* </pre>
+ * </p>
*
+ * <p>
* The conversion algorithm assumes that the input XML adheres to the following
* conventions:
*
* only text, they are discarded.</li>
* <li>The XML does not contain sequences of identically-named elements that are
* interrupted by other elements; or if it does, those interruptions are not
- * important. For example, the parent node below contains a list of item
- * elements, interrupted by an other element:
+ * important. For example, the <code>parent</code> node below contains a list of <code>item</code>
+ * elements, interrupted by an <code>other</code> element:
*
* <pre>
- * <parent>
- * <item>a</item>
- * <item>b</item>
- * <item>c</item>
- * <other>uh oh</other>
- * <item>d</item>
- * <item>e</item>
- * </parent>
+ * <parent>
+ * <item>a</item>
+ * <item>b</item>
+ * <item>c</item>
+ * <other>uh oh</other>
+ * <item>d</item>
+ * <item>e</item>
+ * </parent>
* </pre>
*
* This is translated to:
* }
* </pre>
*
- * All of the item children of parent are converted into a single
- * list, so the placement of the other element is not retained in
+ * All of the <code>item</code> children of parent are converted into a single
+ * list, so the placement of the <code>other</code> element is not retained in
* JSON.
* </li>
* </ul>
+ * </p>
*
- * This implementation uses a StAX parser to generate a lightweight
+ * <p>This implementation uses a StAX parser to generate a lightweight
* representation of the input XML document in memory, performs the
* necessary transformations, and outputs a JSON rendering of the
* transformed document. A direct stream-to-stream conversion is
* not possible because of the need to collect identically-named
* XML elements for output as a JSON array; for any element, all children
* must be known before JSON for that element may be written to the
- * output stream.
+ * output stream.</p>
*/
public class XmlToJsonStreamConverter {
/**