/**
* Copyright (c) 2005-2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you 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.
*/
package org.apache.synapse.commons.json;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.synapse.commons.staxon.core.json.JsonXMLConfig;
import org.apache.synapse.commons.staxon.core.json.JsonXMLConfigBuilder;
import org.apache.synapse.commons.staxon.core.json.JsonXMLInputFactory;
import org.apache.synapse.commons.staxon.core.json.JsonXMLOutputFactory;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.OMText;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axiom.om.impl.llom.OMSourcedElementImpl;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.commons.util.MiscellaneousUtil;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.util.Iterator;
import java.util.Properties;
public final class JsonUtil {
private static Log logger = LogFactory.getLog(JsonUtil.class.getName());
private static final String ORG_APACHE_SYNAPSE_COMMONS_JSON_JSON_INPUT_STREAM = "org.apache.synapse.commons.json.JsonInputStream";
private static final String ORG_APACHE_SYNAPSE_COMMONS_JSON_IS_JSON_OBJECT = "org.apache.synapse.commons.json.JsonInputStream.IsJsonObject";
private static final QName JSON_OBJECT = new QName("jsonObject");
private static final QName JSON_ARRAY = new QName("jsonArray");
/**
* If this property is set to <tt>true</tt> the input stream of the JSON payload will be reset
* after writing to the output stream within the #writeAsJson method.
*/
public static final String PRESERVE_JSON_STREAM = "preserve.json.stream";
/// JSON/XML INPUT OUTPUT Formatting Configuration
// TODO: Build thie configuration from a "json.properties" file. Add a debug log to dump() the config to the log.
// TODO: Add another param to empty xml element to null or empty json string mapping <a/> -> "a":null or "a":""
// TODO: Build this configuration into a separate class.
// TODO: Property to remove root element from XML output
// TODO: Axis2 property/synapse static property add XML Namespace to the root element
private static boolean preserverNamespacesForJson = false;
private static final boolean processNCNames;
private static final boolean jsonOutAutoPrimitive;
private static final char jsonOutNamespaceSepChar;
private static final boolean jsonOutEnableNsDeclarations;
private static final String jsonoutcustomRegex;
private static final boolean jsonoutAutoArray;
private static final boolean jsonoutMultiplePI;
private static final boolean xmloutAutoArray;
private static final boolean xmloutMultiplePI;
private static final String jsonoutCustomReplaceRegex;
private static final String jsonoutCustomReplaceSequence;
static {
Properties properties = MiscellaneousUtil.loadProperties("synapse.properties");
if (properties == null) {
preserverNamespacesForJson = processNCNames = jsonOutEnableNsDeclarations = false;
jsonOutAutoPrimitive = true;
jsonOutNamespaceSepChar = '_';
jsonoutcustomRegex=null;
jsonoutCustomReplaceRegex = null;
jsonoutCustomReplaceSequence = "";
jsonoutAutoArray = true;
jsonoutMultiplePI = false;
xmloutAutoArray = true;
xmloutMultiplePI = false;
} else {
// Preserve the namespace declarations() in the JSON output in the XML -> JSON transformation.
String process = properties.getProperty(Constants.SYNAPSE_COMMONS_JSON_PRESERVE_NAMESPACE, "false").trim();
preserverNamespacesForJson = Boolean.parseBoolean(process.toLowerCase());
// Build valid XML NCNames when building XML element names in the JSON -> XML transformation.
process = properties.getProperty(Constants.SYNAPSE_COMMONS_JSON_BUILD_VALID_NC_NAMES, "false").trim();
processNCNames = Boolean.parseBoolean(process.toLowerCase());
// Enable primitive types in json out put in the XML -> JSON transformation.
process = properties.getProperty(Constants.SYNAPSE_COMMONS_JSON_OUTPUT_AUTO_PRIMITIVE, "true").trim();
jsonOutAutoPrimitive = Boolean.parseBoolean(process.toLowerCase());
// The namespace prefix separate character in the JSON output of the XML -> JSON transformation
process = properties.getProperty(Constants.SYNAPSE_COMMONS_JSON_OUTPUT_NAMESPACE_SEP_CHAR, "_").trim();
jsonOutNamespaceSepChar = process.charAt(0);
// Add XML namespace declarations in the JSON output in the XML -> JSON transformation.
process = properties.getProperty(Constants.SYNAPSE_COMMONS_JSON_OUTPUT_ENABLE_NS_DECLARATIONS,
"false").trim();
jsonOutEnableNsDeclarations = Boolean.parseBoolean(process.toLowerCase());
jsonoutCustomReplaceRegex = properties
.getProperty("synapse.commons.json.json.output.disableAutoPrimitive.customReplaceRegex", null);
jsonoutCustomReplaceSequence = properties
.getProperty("synapse.commons.json.json.output.disableAutoPrimitive.customReplaceSequence", "");
jsonoutcustomRegex = properties.getProperty
(Constants.SYNAPSE_COMMONS_JSON_OUTPUT_DISABLE_AUTO_PRIMITIVE_REGEX, null);
jsonoutAutoArray = Boolean.parseBoolean(properties.getProperty
(Constants.SYNAPSE_COMMONS_JSON_OUTPUT_JSON_OUT_AUTO_ARRAY, "true"));
jsonoutMultiplePI = Boolean.parseBoolean(properties.getProperty
(Constants.SYNAPSE_COMMONS_JSON_OUTPUT_JSON_OUT_MULTIPLE_PI, "true"));
xmloutAutoArray = Boolean.parseBoolean(properties.getProperty
(Constants.SYNAPSE_COMMONS_JSON_OUTPUT_XML_OUT_AUTO_ARRAY, "true"));
xmloutMultiplePI = Boolean.parseBoolean(properties.getProperty
(Constants.SYNAPSE_COMMONS_JSON_OUTPUT_XML_OUT_MULTIPLE_PI, "false"));
process = properties.getProperty
(Constants.SYNAPSE_COMMONS_JSON_OUTPUT_EMPTY_XML_ELEM_TO_EMPTY_STR, "true").trim();
}
}
/**
* Configuration used to produce XML that has processing instructions in it.
*/
private static final JsonXMLConfig xmlOutputConfig = new JsonXMLConfigBuilder()
.multiplePI(true)
.autoArray(true)
.autoPrimitive(true)
.namespaceDeclarations(false)
.namespaceSeparator('\u0D89')
.customRegex(jsonoutcustomRegex)
.build();
/**
* Configuration used to produce XML that has no processing instructions in it.
*/
private static final JsonXMLConfig xmlOutputConfigNoPIs = new JsonXMLConfigBuilder()
.multiplePI(xmloutMultiplePI)
.autoArray(xmloutAutoArray)
.autoPrimitive(true)
.namespaceDeclarations(false)
.namespaceSeparator('\u0D89')
.build();
/**
* This configuration is used to format the JSON output produced by the JSON writer.
*/
private static final JsonXMLConfig jsonOutputConfig = new JsonXMLConfigBuilder()
.multiplePI(jsonoutMultiplePI)
.autoArray(jsonoutAutoArray)
.autoPrimitive(jsonOutAutoPrimitive)
.namespaceDeclarations(jsonOutEnableNsDeclarations)
.namespaceSeparator(jsonOutNamespaceSepChar)
.customReplaceRegex(jsonoutCustomReplaceRegex)
.customReplaceSequence(jsonoutCustomReplaceSequence)
.customRegex(jsonoutcustomRegex)
.build();
/// End of JSON/XML INPUT OUTPUT Formatting Configuration.
/**
* Factory used to create JSON Readers
*/
private static final JsonXMLInputFactory jsonInputFactory = new JsonXMLInputFactory(xmlOutputConfig);
/**
* Factory used to create JSON Readers
*/
private static final JsonXMLInputFactory xmlInputFactoryNoPIs = new JsonXMLInputFactory(xmlOutputConfigNoPIs);
/**
* Factory used to create JSON Writers
*/
private static final JsonXMLOutputFactory jsonOutputFactory = new JsonXMLOutputFactory(jsonOutputConfig);
/**
* Factory used to create XML Readers
*/
private static final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
/**
* Converts the XML payload of a message context into its JSON representation and writes it to an output stream.<br/>
* If no XML payload is found, the existing JSON payload will be copied to the output stream.<br/>
* Note that this method removes all existing namespace declarations and namespace prefixes of the payload that is <br/>
* present in the provided message context.
*
* @param messageContext Axis2 Message context that holds the JSON/XML payload.
* @param out Output stream to which the payload(JSON) must be written.
* @throws org.apache.axis2.AxisFault
*/
public static void writeAsJson(MessageContext messageContext, OutputStream out) throws AxisFault {
if (messageContext == null || out == null) {
return;
}
OMElement element = messageContext.getEnvelope().getBody().getFirstElement();
Object o = jsonStream(messageContext, false);
InputStream json = null;
if (o != null) {
json = (InputStream) o;
}
o = messageContext.getProperty(org.apache.synapse.commons.json.Constants.JSON_STRING);
String jsonStr = null;
if (o instanceof String) {
jsonStr = (String) o;
}
if (json != null) { // there is a JSON stream
try {
if (element instanceof OMSourcedElementImpl) {
if (isAJsonPayloadElement(element)) {
writeJsonStream(json, messageContext, out);
} else { // Ignore the JSON stream
writeAsJson(element, out);
}
} else if (element != null) { // element is not an OMSourcedElementImpl. But we ignore the JSON stream.
writeAsJson(element, out);
} else { // element == null.
writeJsonStream(json, messageContext, out);
}
} catch (Exception e) {
//Close the stream
IOUtils.closeQuietly(json);
throw new AxisFault("Could not write JSON stream.", e);
}
} else if (element != null) { // No JSON stream found. Convert the existing element to JSON.
writeAsJson(element, out);
} else if (jsonStr != null) { // No JSON stream or element found. See if there's a JSON_STRING set.
try {
out.write(jsonStr.getBytes());
} catch (IOException e) {
logger.error("#writeAsJson. Could not write JSON string. MessageID: "
+ messageContext.getMessageID() + ". Error>> " + e.getLocalizedMessage());
throw new AxisFault("Could not write JSON string.", e);
}
} else {
logger.error("#writeAsJson. Payload could not be written as JSON. MessageID: " + messageContext.getMessageID());
throw new AxisFault("Payload could not be written as JSON.");
}
}
/**
* Converts a JSON input stream to its XML representation.
*
* @param jsonStream JSON input stream
* @param pIs Whether or not to add XML processing instructions to the output XML.<br/>
* This property is useful when converting JSON payloads with array objects.
* @return OMElement that is the XML representation of the input JSON data.
*/
public static OMElement toXml(InputStream jsonStream, boolean pIs) throws AxisFault {
if (jsonStream == null) {
logger.error("#toXml. Could not convert JSON Stream to XML. JSON input stream is null.");
return null;
}
try {
XMLStreamReader streamReader = getReader(jsonStream, pIs);
return new StAXOMBuilder(streamReader).getDocumentElement();
} catch (XMLStreamException e) {//invalid JSON?
logger.error("#toXml. Could not convert JSON Stream to XML. Cannot handle JSON input. Error>>> " + e.getLocalizedMessage());
throw new AxisFault("Could not convert JSON Stream to XML. Cannot handle JSON input.", e);
}
}
/**
* Returns an XMLStreamReader for a JSON input stream
*
* @param jsonStream InputStream of JSON
* @param pIs Whether to add XML PIs to the XML output. This is used as an instruction to the returned XML Stream Reader.
* @return An XMLStreamReader
* @throws javax.xml.stream.XMLStreamException
*/
public static XMLStreamReader getReader(InputStream jsonStream, boolean pIs) throws XMLStreamException {
if (jsonStream == null) {
logger.error("#getReader. Could not create XMLStreamReader from [null] input stream.");
return null;
}
return pIs ? getReader(jsonStream)
: new JsonReaderDelegate(xmlInputFactoryNoPIs.createXMLStreamReader(jsonStream,
org.apache.synapse.commons.staxon.core.json.stream.impl.Constants.SCANNER.SCANNER_1), processNCNames);
}
/**
* This method is useful when you need to get an XML reader directly for the input JSON stream <br/>
* without adding any additional object wrapper elements such as 'jsonObject' and 'jsonArray'.
*
* @param jsonStream InputStream of JSON
* @return An XMLStreamReader
* @throws javax.xml.stream.XMLStreamException
*/
public static XMLStreamReader getReader(InputStream jsonStream) throws XMLStreamException {
if (jsonStream == null) {
logger.error("#getReader. Could not create XMLStreamReader from [null] input stream.");
return null;
}
return new JsonReaderDelegate(jsonInputFactory.createXMLStreamReader(jsonStream,
org.apache.synapse.commons.staxon.core.json.stream.impl.Constants.SCANNER.SCANNER_1), processNCNames);
}
/**
* Converts an XML element to its JSON representation and writes it to an output stream.<br/>
* Note that this method removes all existing namespace declarations and namespace prefixes of the provided XML element<br/>
*
* @param element XML element of which JSON representation is expected.
* @param outputStream Output Stream to write the JSON representation.<br/>
* At the end of a successful conversion, its flush method will be called.
* @throws AxisFault
*/
public static void writeAsJson(OMElement element, OutputStream outputStream) throws AxisFault {
XMLEventReader xmlEventReader = null;
XMLEventWriter jsonWriter = null;
if (element == null) {
logger.error("#writeAsJson. OMElement is null. Cannot convert to JSON.");
throw new AxisFault("OMElement is null. Cannot convert to JSON.");
}
if (outputStream == null) {
return;
}
transformElement(element, true);
try {
org.apache.commons.io.output.ByteArrayOutputStream xmlStream =
new org.apache.commons.io.output.ByteArrayOutputStream();
element.serialize(xmlStream);
xmlStream.flush();
xmlEventReader = xmlInputFactory.createXMLEventReader(
new XmlReaderDelegate(xmlInputFactory.createXMLStreamReader(
new ByteArrayInputStream(xmlStream.toByteArray())
), processNCNames)
);
jsonWriter = jsonOutputFactory.createXMLEventWriter(outputStream);
jsonWriter.add(xmlEventReader);
outputStream.flush();
} catch (XMLStreamException e) {
logger.error("#writeAsJson. Could not convert OMElement to JSON. Invalid XML payload. Error>>> " + e.getLocalizedMessage());
throw new AxisFault("Could not convert OMElement to JSON. Invalid XML payload.", e);
} catch (IOException e) {
logger.error("#writeAsJson. Could not convert OMElement to JSON. Error>>> " + e.getLocalizedMessage());
throw new AxisFault("Could not convert OMElement to JSON.", e);
} finally {
if (xmlEventReader != null) {
try {
xmlEventReader.close();
} catch (XMLStreamException ex) {
//ignore
}
}
if (jsonWriter != null) {
try {
jsonWriter.close();
} catch (XMLStreamException ex) {
//ignore
}
}
}
}
/**
* Converts an XML element to its JSON representation and returns it as a String.
*
* @param element OMElement to be converted to JSON.
* @return A String builder instance that contains the converted JSON string.
*/
public static StringBuilder toJsonString(OMElement element) throws AxisFault {
if (element == null) {
return new StringBuilder("{}");
}
org.apache.commons.io.output.ByteArrayOutputStream byteStream =
new org.apache.commons.io.output.ByteArrayOutputStream();
writeAsJson(element.cloneOMElement(), byteStream);
return new StringBuilder(new String(byteStream.toByteArray()));
}
/**
* Removes XML namespace declarations, and namespace prefixes from an XML element.
*
* @param element Source XML element
* @param processAttrbs Whether to remove the namespaces from attributes as well
*/
public static void transformElement(OMElement element, boolean processAttrbs) {
if (element == null) {
return;
}
removeIndentations(element);
if (!preserverNamespacesForJson) {
removeNamespaces(element, processAttrbs);
}
if (logger.isDebugEnabled()) {
logger.debug("#transformElement. Transformed OMElement. Result: " + element.toString());
}
}
private static void removeIndentations(OMElement elem) {
Iterator children = elem.getChildren();
while (children.hasNext()) {
OMNode child = (OMNode) children.next();
if (child instanceof OMText) {
if ("".equals(((OMText) child).getText().trim())) {
children.remove();
}
} else if (child instanceof OMElement) {
removeIndentations((OMElement) child);
}
}
}
private static void removeNamespaces(OMElement element, boolean processAttrbs) {
OMNamespace ns = element.getNamespace();
Iterator i = element.getAllDeclaredNamespaces();
while (i.hasNext()) {
i.next();
i.remove();
}
String prefix;
if (ns != null) {
prefix = "";//element.getNamespace().getPrefix();
element.setNamespace(element.getOMFactory().createOMNamespace("", prefix));
}
Iterator children = element.getChildElements();
while (children.hasNext()) {
removeNamespaces((OMElement) children.next(), processAttrbs);
}
if (!processAttrbs) {
return;
}
Iterator attrbs = element.getAllAttributes();
while (attrbs.hasNext()) {
OMAttribute attrb = (OMAttribute) attrbs.next();
prefix = "";//attrb.getQName().getPrefix();
attrb.setOMNamespace(attrb.getOMFactory().createOMNamespace("", prefix));
//element.removeAttribute(attrb);
}
}
/**
* Builds and returns a new JSON payload for a message context with a stream of JSON content. <br/>
* This is the recommended way to build a JSON payload into an Axis2 message context.<br/>
* A JSON payload built into a message context with this method can only be removed by calling
* {@link #removeJsonPayload(org.apache.axis2.context.MessageContext)} method. This added to avoid code breaks
* for previous implementations. For new implementations use getNewJsonPayload method.
*
* @param messageContext Axis2 Message context to which the new JSON payload must be saved (if instructed with <tt>addAsNewFirstChild</tt>).
* @param inputStream JSON content as an input stream.
* @param removeChildren Whether to remove existing child nodes of the existing payload of the message context
* @param addAsNewFirstChild Whether to add the new JSON payload as the first child of this message context *after* removing the existing first child element.<br/>
* Setting this argument to <tt>true</tt> will have no effect if the value of the argument <tt>removeChildren</tt> is already <tt>false</tt>.
* @return Payload object that stores the input JSON content as a Sourced object (See {@link org.apache.axiom.om.OMSourcedElement}) that can build the XML tree for contained JSON payload on demand.
*/
@Deprecated
public static OMElement newJsonPayload(MessageContext messageContext, InputStream inputStream,
boolean removeChildren, boolean addAsNewFirstChild) {
try {
return getNewJsonPayload(messageContext, inputStream, removeChildren, addAsNewFirstChild);
} catch (AxisFault axisFault) {
logger.error("Payload provided is not an JSON payload.");
return null;
}
}
/**
* Builds and returns a new JSON payload for a message context with a stream of JSON content. <br/>
* This is the recommended way to build a JSON payload into an Axis2 message context.<br/>
* A JSON payload built into a message context with this method can only be removed by calling
* {@link #removeJsonPayload(org.apache.axis2.context.MessageContext)} method.
*
* @param messageContext Axis2 Message context to which the new JSON payload must be saved (if instructed with <tt>addAsNewFirstChild</tt>).
* @param inputStream JSON content as an input stream.
* @param removeChildren Whether to remove existing child nodes of the existing payload of the message context
* @param addAsNewFirstChild Whether to add the new JSON payload as the first child of this message context *after* removing the existing first child element.<br/>
* Setting this argument to <tt>true</tt> will have no effect if the value of the argument <tt>removeChildren</tt> is already <tt>false</tt>.
* @return Payload object that stores the input JSON content as a Sourced object (See {@link org.apache.axiom.om.OMSourcedElement}) that can build the XML tree for contained JSON payload on demand.
*/
public static OMElement getNewJsonPayload(MessageContext messageContext, InputStream inputStream,
boolean removeChildren, boolean addAsNewFirstChild) throws AxisFault {
if (messageContext == null) {
logger.error("#getNewJsonPayload. Could not save JSON stream. Message context is null.");
return null;
}
boolean isObject = false;
boolean isArray = false;
if (inputStream != null) {
InputStream json = toReadOnlyStream(inputStream);
messageContext.setProperty(ORG_APACHE_SYNAPSE_COMMONS_JSON_JSON_INPUT_STREAM, json);
// read ahead few characters to see if the stream is valid...
boolean isEmptyPayload = true;
boolean valid = false;
try {
// check for empty/all-whitespace streams
int c = json.read();
while (c != -1 && c != '{' && c != '[') {
if (c != 32) {
isEmptyPayload = false;
}
c = json.read();
}
if (c != -1) {
valid = true;
}
if (c == '{') {
isObject = true;
isArray = false;
} else if (c == '[') {
isArray = true;
isObject = false;
}
json.reset();
} catch (IOException e) {
logger.error(
"#getNewJsonPayload. Could not determine availability of the JSON input stream. MessageID: " +
messageContext.getMessageID() + ". Error>>> " + e.getLocalizedMessage());
return null;
}
if (!valid) {
if (isEmptyPayload) {
//This is a empty payload so return null without doing further processing.
if (logger.isDebugEnabled()) {
logger.debug("#emptyJsonPayload found.MessageID: " + messageContext.getMessageID());
}
logger.debug("#emptyJsonPayload found.MessageID: " + messageContext.getMessageID());
return null;
} else {
/*
* This method required to introduce due the GET request failure with query parameter and
* contenttype=application/json. Because of the logic implemented with '=' sign below,
* it expects a valid JSON string as the query parameter value (string after '=' sign) for GET
* requests with application/json content type. Therefore it fails for requests like
* https://localhost:8243/services/customer?format=xml and throws axis fault. With this fix,
* HTTP method is checked and avoid throwing axis2 fault for GET requests.
* https://wso2.org/jira/browse/ESBJAVA-4270
*/
if (isValidPayloadRequired(messageContext)) {
logger.error(
"#getNewJsonPayload. Could not save JSON payload. Invalid input stream found. MessageID: " +
messageContext.getMessageID());
throw new AxisFault("Payload is not a JSON string.");
} else {
return null;
}
}
}
QName jsonElement = null;
if (isObject) {
jsonElement = JSON_OBJECT;
messageContext.setProperty(ORG_APACHE_SYNAPSE_COMMONS_JSON_IS_JSON_OBJECT, true);
}
if (isArray) {
jsonElement = JSON_ARRAY;
messageContext.setProperty(ORG_APACHE_SYNAPSE_COMMONS_JSON_IS_JSON_OBJECT, false);
}
OMElement elem = new OMSourcedElementImpl(jsonElement, OMAbstractFactory.getOMFactory(), new JsonDataSource(
(InputStream) messageContext.getProperty(ORG_APACHE_SYNAPSE_COMMONS_JSON_JSON_INPUT_STREAM)));
if (!removeChildren) {
if (logger.isTraceEnabled()) {
logger.trace(
"#getNewJsonPayload. Not removing child elements from exiting message. Returning result. MessageID: " +
messageContext.getMessageID());
}
return elem;
}
SOAPEnvelope e = messageContext.getEnvelope();
if (e != null) {
SOAPBody b = e.getBody();
if (b != null) {
removeIndentations(b);
Iterator children = b.getChildren();
while (children.hasNext()) {
Object o = children.next();
if (o instanceof OMNode) {
//((OMNode) o).detach();
children.remove();
}
}
if (logger.isTraceEnabled()) {
logger.trace("#getNewJsonPayload. Removed child elements from exiting message. MessageID: " +
messageContext.getMessageID());
}
if (addAsNewFirstChild) {
b.addChild(elem);
if (logger.isTraceEnabled()) {
logger.trace(
"#getNewJsonPayload. Added the new JSON sourced element as the first child. MessageID: " +
messageContext.getMessageID());
}
}
}
}
return elem;
}
return null;
}
/**
* Builds and returns a new JSON payload for a message context with a JSON string. This is deprecated and use
* getNewJsonPayload for new implementations.
*
* @param messageContext Axis2 Message context to which the new JSON payload must be saved (if instructed with <tt>addAsNewFirstChild</tt>).
* @param jsonString JSON content as a String.
* @param removeChildren Whether to remove existing child nodes of the existing payload of the message context
* @param addAsNewFirstChild Whether to add the new JSON payload as the first child of this message context *after* removing the existing first child element.<br/>
* Setting this argument to <tt>true</tt> will have no effect if the value of the argument <tt>removeChildren</tt> is already <tt>false</tt>.
* @return Payload object that stores the input JSON content as a Sourced object (See {@link org.apache.axiom.om.OMSourcedElement}) that facilitates on demand building of the XML tree.
* @see #getNewJsonPayload(org.apache.axis2.context.MessageContext, java.io.InputStream, boolean, boolean)
*/
@Deprecated
public static OMElement newJsonPayload(MessageContext messageContext, String jsonString, boolean removeChildren,
boolean addAsNewFirstChild) {
try {
return getNewJsonPayload(messageContext, jsonString, removeChildren, addAsNewFirstChild);
} catch (AxisFault axisFault) {
logger.error("Payload provided is not an JSON payload.");
return null;
}
}
/**
* Builds and returns a new JSON payload for a message context with a JSON string.<br/>
*
* @param messageContext Axis2 Message context to which the new JSON payload must be saved (if instructed with <tt>addAsNewFirstChild</tt>).
* @param jsonString JSON content as a String.
* @param removeChildren Whether to remove existing child nodes of the existing payload of the message context
* @param addAsNewFirstChild Whether to add the new JSON payload as the first child of this message context *after* removing the existing first child element.<br/>
* Setting this argument to <tt>true</tt> will have no effect if the value of the argument <tt>removeChildren</tt> is already <tt>false</tt>.
* @return Payload object that stores the input JSON content as a Sourced object (See {@link org.apache.axiom.om.OMSourcedElement}) that facilitates on demand building of the XML tree.
* @see #getNewJsonPayload(org.apache.axis2.context.MessageContext, java.io.InputStream, boolean, boolean)
*/
public static OMElement getNewJsonPayload(MessageContext messageContext, String jsonString, boolean removeChildren,
boolean addAsNewFirstChild) throws AxisFault {
if (jsonString == null || jsonString.isEmpty()) {
jsonString = "{}";
}
return getNewJsonPayload(messageContext, new ByteArrayInputStream(jsonString.getBytes()), removeChildren,
addAsNewFirstChild);
}
/**
* Builds and returns a new JSON payload for a message context with a byte array containing JSON.This method is
* now deprecated and replaced by getNewJsonPayload method.
*
* @param messageContext Axis2 Message context to which the new JSON payload must be saved (if instructed with <tt>addAsNewFirstChild</tt>).
* @param json JSON content as a byte array.
* @param offset starting position of the JSON content in the provided array
* @param length how many bytes to read starting from the offset provided.
* @param removeChildren Whether to remove existing child nodes of the existing payload of the message context
* @param addAsNewFirstChild Whether to add the new JSON payload as the first child of this message context *after* removing the existing first child element.<br/>
* Setting this argument to <tt>true</tt> will have no effect if the value of the argument <tt>removeChildren</tt> is already <tt>false</tt>.
* @return Payload object that stores the input JSON content as a Sourced object (See {@link org.apache.axiom.om.OMSourcedElement}) that facilitates on demand building of the XML tree.
* @see #getNewJsonPayload(org.apache.axis2.context.MessageContext, java.io.InputStream, boolean, boolean)
*/
@Deprecated
public static OMElement newJsonPayload(MessageContext messageContext, byte[] json, int offset, int length,
boolean removeChildren, boolean addAsNewFirstChild) {
try {
return getNewJsonPayload(messageContext, json, offset, length,
removeChildren, addAsNewFirstChild);
} catch (AxisFault axisFault) {
logger.error("Payload provided is not an JSON payload.");
return null;
}
}
/**
* Builds and returns a new JSON payload for a message context with a byte array containing JSON.<br/>
*
* @param messageContext Axis2 Message context to which the new JSON payload must be saved (if instructed with <tt>addAsNewFirstChild</tt>).
* @param json JSON content as a byte array.
* @param offset starting position of the JSON content in the provided array
* @param length how many bytes to read starting from the offset provided.
* @param removeChildren Whether to remove existing child nodes of the existing payload of the message context
* @param addAsNewFirstChild Whether to add the new JSON payload as the first child of this message context *after* removing the existing first child element.<br/>
* Setting this argument to <tt>true</tt> will have no effect if the value of the argument <tt>removeChildren</tt> is already <tt>false</tt>.
* @return Payload object that stores the input JSON content as a Sourced object (See {@link org.apache.axiom.om.OMSourcedElement}) that facilitates on demand building of the XML tree.
* @see #getNewJsonPayload(org.apache.axis2.context.MessageContext, java.io.InputStream, boolean, boolean)
*/
public static OMElement getNewJsonPayload(MessageContext messageContext, byte[] json, int offset, int length,
boolean removeChildren, boolean addAsNewFirstChild) throws AxisFault{
InputStream is;
if (json == null || json.length < 2) {
json = new byte[]{'{', '}'};
is = new ByteArrayInputStream(json);
} else {
is = new ByteArrayInputStream(json, offset, length);
}
return getNewJsonPayload(messageContext, is, removeChildren, addAsNewFirstChild);
}
/**
* Removes the existing JSON payload of a message context if any.<br/>
* This method can only remove a JSON payload that has been set with {@link #getNewJsonPayload(org.apache.axis2.context.MessageContext, java.io.InputStream, boolean, boolean)}
* and its variants.
*
* @param messageContext Axis2 Message context from which the JSON stream must be removed.
* @return <tt>true</tt> if the operation is successful.
*/
public static boolean removeJsonPayload(MessageContext messageContext) {
messageContext.removeProperty(ORG_APACHE_SYNAPSE_COMMONS_JSON_JSON_INPUT_STREAM);
messageContext.removeProperty(ORG_APACHE_SYNAPSE_COMMONS_JSON_IS_JSON_OBJECT);
boolean removeChildren = true;
if (!removeChildren) { // don't change this.
if (logger.isTraceEnabled()) {
logger.trace("#removeJsonPayload. Removed JSON stream. MessageID: " + messageContext.getMessageID());
}
return true;
}
SOAPEnvelope e = messageContext.getEnvelope();
if (e != null) {
SOAPBody b = e.getBody();
if (b != null) {
removeIndentations(b); // cleans payload by removing unnecessary characters
Iterator children = b.getChildren();
while (children.hasNext()) {
Object o = children.next();
if (o instanceof OMNode) {
//((OMNode) o).detach();
children.remove();
}
}
if (logger.isTraceEnabled()) {
logger.trace("#removeJsonPayload. Removed JSON stream and child elements of payload. MessageID: "
+ messageContext.getMessageID());
}
}
}
return true;
}
/**
* Returns the JSON stream associated with the payload of this message context.
*
* @param messageContext Axis2 Message context
* @param reset Whether to reset the input stream that contains this JSON payload so that next read will start from the beginning of this stream.
* @return JSON input stream
*/
private static InputStream jsonStream(MessageContext messageContext, boolean reset) {
if (messageContext == null) {
return null;
}
Object o = messageContext.getProperty(ORG_APACHE_SYNAPSE_COMMONS_JSON_JSON_INPUT_STREAM);
if (o instanceof InputStream) {
InputStream is = (InputStream) o;
if (reset) {
if (is.markSupported()) {
try {
is.reset();
} catch (IOException e) {
logger.error("#jsonStream. Could not reuse JSON Stream. Error>>>\n", e);
return null;
}
}
}
return is;
}
return null;
}
/**
* Returns the READ-ONLY input stream of the JSON payload contained in the provided message context.
*
* @param messageContext Axis2 Message context
* @return {@link java.io.InputStream} of JSON payload contained in the message context. Null otherwise.<br/>
* It is possible to read from this stream right away. This InputStream cannot be <tt>close</tt>d, <tt>mark</tt>ed, or <tt>skip</tt>ped. <br/>
* If <tt>close()</tt> is invoked on this input stream, it will be reset to the beginning.
*/
public static InputStream getJsonPayload(MessageContext messageContext) {
return hasAJsonPayload(messageContext) ? jsonStream(messageContext, true) : null;
}
/**
* Returns a copy of the JSON stream contained in the provided Message Context.
*
* @param messageContext Axis2 Message context that contains a JSON payload.
* @return {@link java.io.InputStream}
*/
private static InputStream copyOfJsonPayload(MessageContext messageContext, boolean closable) {
if (messageContext == null) {
logger.error("#copyOfJsonPayload. Cannot copy JSON stream from message context. [null].");
return null;
}
InputStream jsonStream = jsonStream(messageContext, true);
if (jsonStream == null) {
logger.error("#copyOfJsonPayload. Cannot copy JSON stream from message context. [null] stream.");
return null;
}
org.apache.commons.io.output.ByteArrayOutputStream out = new org.apache.commons.io.output.ByteArrayOutputStream();
try {
IOUtils.copy(jsonStream, out);
out.flush();
return closable ? new ByteArrayInputStream(out.toByteArray())
: toReadOnlyStream(new ByteArrayInputStream(out.toByteArray()));
} catch (IOException e) {
logger.error("#copyOfJsonPayload. Could not copy the JSON stream from message context. Error>>> " + e.getLocalizedMessage());
}
return null;
}
private static void writeJsonStream(InputStream json, MessageContext messageContext, OutputStream out) throws AxisFault {
try {
if (json.markSupported()) {
json.reset();
}
IOUtils.copy(json, out); // Write the JSON stream
if (messageContext.getProperty(PRESERVE_JSON_STREAM) != null) {
if (json.markSupported()) {
json.reset();
}
messageContext.removeProperty(PRESERVE_JSON_STREAM);
}
} catch (IOException e) {
logger.error("#writeJsonStream. Could not write JSON stream. MessageID: "
+ messageContext.getMessageID() + ". Error>> " + e.getLocalizedMessage());
throw new AxisFault("Could not write JSON stream.", e);
}
}
/**
* Returns a reusable cached copy of the JSON stream contained in the provided Message Context.
*
* @param messageContext Axis2 Message context that contains a JSON payload.
* @return {@link java.io.InputStream}
*/
private static InputStream cachedCopyOfJsonPayload(MessageContext messageContext) {
if (messageContext == null) {
logger.error("#cachedCopyOfJsonPayload. Cannot copy JSON stream from message context. [null].");
return null;
}
InputStream jsonStream = jsonStream(messageContext, true);
if (jsonStream == null) {
logger.error("#cachedCopyOfJsonPayload. Cannot copy JSON stream from message context. [null] stream.");
return null;
}
String inputStreamCache = Long.toString(jsonStream.hashCode());
Object o = messageContext.getProperty(inputStreamCache);
if (o instanceof InputStream) {
InputStream inputStream = (InputStream) o;
try {
inputStream.reset();
if (logger.isDebugEnabled()) {
logger.debug("#cachedCopyOfJsonPayload. Cache HIT");
}
return inputStream;
} catch (IOException e) {
logger.warn("#cachedCopyOfJsonPayload. Could not reuse the cached input stream. Error>>> " + e.getLocalizedMessage());
}
}
org.apache.commons.io.output.ByteArrayOutputStream out = new org.apache.commons.io.output.ByteArrayOutputStream();
try {
IOUtils.copy(jsonStream, out);
out.flush();
InputStream inputStream = toReadOnlyStream(new ByteArrayInputStream(out.toByteArray()));
messageContext.setProperty(inputStreamCache, inputStream);
if (logger.isDebugEnabled()) {
logger.debug("#cachedCopyOfJsonPayload. Cache MISS");
}
return inputStream;
} catch (IOException e) {
logger.error("#cachedCopyOfJsonPayload. Could not copy the JSON stream from message context. Error>>> " + e.getLocalizedMessage());
}
return null;
}
/**
* Returns a new instance of a reader that can read from the JSON payload contained in the provided message context.
*
* @param messageContext Axis2 Message context
* @return {@link java.io.Reader} if a JSON payload is found in the message context. null otherwise.
*/
public static Reader newJsonPayloadReader(MessageContext messageContext) {
if (messageContext == null) {
return null;
}
InputStream is = jsonStream(messageContext, true);
if (is == null) {
return null;
}
return new InputStreamReader(is);
}
/**
* Returns the JSON payload contained in the provided message context as a byte array.
*
* @param messageContext Axis2 Message context
* @return <tt>byte</tt> array containing the JSON payload. Empty array if no JSON payload found or invalid message context is passed in.
*/
public static byte[] jsonPayloadToByteArray(MessageContext messageContext) {
if (messageContext == null) {
return new byte[0];
}
InputStream is = jsonStream(messageContext, true);
if (is == null) {
return new byte[0];
}
try {
return IOUtils.toByteArray(is); // IOUtils.toByteArray() doesn't close the input stream.
} catch (IOException e) {
logger.warn("#jsonPayloadToByteArray. Could not convert JSON stream to byte array.");
return new byte[0];
}
}
/**
* Returns the JSON payload contained in the provided message context as a String.
*
* @param messageContext Axis2 Message context
* @return <tt>java.lang.String</tt> representation of the JSON payload. Returns "{}" if no JSON payload found or invalid message context is passed in.
*/
public static String jsonPayloadToString(MessageContext messageContext) {
if (messageContext == null) {
return "{}";
}
InputStream is = jsonStream(messageContext, true);
if (is == null) {
return "{}";
}
try {
return IOUtils.toString(is); // IOUtils.toByteArray() doesn't close the input stream.
} catch (IOException e) {
logger.warn("#jsonPayloadToString. Could not convert JSON stream to String.");
return "{}";
}
}
/**
* Returns whether the provided XML element is an element that stores a sourced JSON payload.
*
* @param element XML element
* @return <tt>true</tt> if the element is a sourced JSON object (ie. an <tt>OMSourcedElement</tt> instance containing a JSON stream).
*/
public static boolean hasAJsonPayload(OMElement element) {
return (element instanceof OMSourcedElementImpl) && isAJsonPayloadElement(element);
}
/**
* Returns true if the element passed in as the parameter is an element that contains a JSON stream.
*
* @param element XML element
* @return <tt>true</tt> if the element has the local name of a sourced (ie. an <tt>OMSourcedElement</tt>) JSON object.
*/
public static boolean isAJsonPayloadElement(OMElement element) {
return element != null
&& (JSON_OBJECT.getLocalPart().equals(element.getLocalName())
|| JSON_ARRAY.getLocalPart().equals(element.getLocalName()));
}
/**
* Returns true if the payload stored in the provided message context is used as a JSON streaming payload.
*
* @param messageContext Axis2 Message context
* @return <tt>true</tt> if the message context contains a Streaming JSON payload.
*/
public static boolean hasAJsonPayload(MessageContext messageContext) {
if (messageContext == null || messageContext.getEnvelope() == null) {
return false;
}
SOAPBody b = messageContext.getEnvelope().getBody();
return b != null && jsonStream(messageContext, false) != null && hasAJsonPayload(b.getFirstElement());
}
/**
* Clones the JSON stream payload contained in the source message context, if any, to the target message context.
*
* @param sourceMc Where to get the payload
* @param targetMc Where to clone and copy the payload
* @return <tt>true</tt> if the cloning was successful.
*/
public static boolean cloneJsonPayload(MessageContext sourceMc, MessageContext targetMc) {
if (!hasAJsonPayload(sourceMc)) {
return false;
}
InputStream json = jsonStream(sourceMc, true);
try {
byte[] stream = IOUtils.toByteArray(json);
getNewJsonPayload(targetMc, new ByteArrayInputStream(stream), true, true);
} catch (IOException e) {
logger.error("#cloneJsonPayload. Could not clone JSON stream. Error>>> " + e.getLocalizedMessage());
return false;
}
return true;
}
/**
* Sets JSON media type 'application/json' as the message type to the current message context.
*
* @param messageContext Axis2 Message context
*/
public static void setContentType(MessageContext messageContext) {
if (messageContext == null) {
return;
}
messageContext.setProperty(org.apache.axis2.Constants.Configuration.MESSAGE_TYPE, "application/json");
}
/**
* Returns a read only, re-readable input stream for an input stream. <br/>
* The returned input stream cannot be closed, marked, or skipped, but it can be reset to the beginning of the stream.
*
* @param inputStream Input stream to be wrapped
* @return {@link java.io.InputStream}
*/
public static InputStream toReadOnlyStream(InputStream inputStream) {
if (inputStream == null) {
return null;
}
return new ReadOnlyBIS(inputStream);
}
/**
* Returns an input stream that contains the JSON representation of an XML element.
*
* @param element XML element of which JSON representation is expected.
* @return {@link java.io.InputStream}
*/
public static InputStream toJsonStream(OMElement element) {
if (element == null) {
logger.error("#toJsonStream. Could not create input stream from XML element [null]");
return null;
}
org.apache.commons.io.output.ByteArrayOutputStream bos = new org.apache.commons.io.output.ByteArrayOutputStream();
try {
JsonUtil.writeAsJson(element.cloneOMElement(), bos);
} catch (AxisFault axisFault) {
logger.error("#toJsonStream. Could not create input stream from XML element ["
+ element.toString() + "]. Error>>> " + axisFault.getLocalizedMessage());
return null;
}
return new ByteArrayInputStream(bos.toByteArray());
}
/**
* Returns a reader that can read from the JSON payload contained in the provided message context as a JavaScript source.<br/>
* The reader returns the '(' character at the beginning of the stream and marks the end with the ')' character.<br/>
* The reader returned by this method can be directly used with the JavaScript {@link javax.script.ScriptEngine#eval(java.io.Reader)} method.
*
* @param messageContext Axis2 Message context
* @return {@link java.io.InputStreamReader}
*/
public static Reader newJavaScriptSourceReader(MessageContext messageContext) {
InputStream jsonStream = jsonStream(messageContext, true);
if (jsonStream == null) {
logger.error("#newJavaScriptSourceReader. Could not create a JavaScript source. Error>>> No JSON stream found.");
return null;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
out.write('(');
IOUtils.copy(jsonStream, out);
out.write(')');
out.flush();
} catch (IOException e) {
logger.error("#newJavaScriptSourceReader. Could not create a JavaScript source. Error>>> " + e.getLocalizedMessage());
return null;
}
return new InputStreamReader(new ByteArrayInputStream(out.toByteArray()));
}
/**
* Returns <tt>true</tt> if the message context contains a JSON payload that is a JSON Object. See {@link #hasAJsonArray(MessageContext)}<br/>
* Example : {"a":1, "b":2}
*
* @param messageContext request message context
* @return
*/
public static boolean hasAJsonObject(MessageContext messageContext) {
return hasAJsonPayload(messageContext) && _hasAJsonObject(messageContext);
}
/**
* Returns <tt>true</tt> if the message context contains a JSON payload that is a JSON Array. See {@link #hasAJsonObject(MessageContext)}<br/>
* Example: [{"a":1}, 2, null]
*
* @param messageContext request message context
* @return
*/
public static boolean hasAJsonArray(MessageContext messageContext) {
return hasAJsonPayload(messageContext) && !_hasAJsonObject(messageContext);
}
private static boolean _hasAJsonObject(MessageContext messageContext) {
Object isObject = messageContext.getProperty(ORG_APACHE_SYNAPSE_COMMONS_JSON_IS_JSON_OBJECT);
return isObject != null && ((Boolean) isObject);
}
/**
* An Un-closable, Read-Only, Reusable, BufferedInputStream
*/
private static class ReadOnlyBIS extends BufferedInputStream {
private static final String LOG_STREAM = "org.apache.synapse.commons.json.JsonReadOnlyStream";
private static final Log logger = LogFactory.getLog(LOG_STREAM);
public ReadOnlyBIS(InputStream inputStream) {
super(inputStream);
super.mark(Integer.MAX_VALUE);
if (logger.isDebugEnabled()) {
logger.debug("<init>");
}
}
@Override
public void close() throws IOException {
super.reset();
//super.mark(Integer.MAX_VALUE);
if (logger.isDebugEnabled()) {
logger.debug("#close");
}
}
@Override
public void mark(int readlimit) {
if (logger.isDebugEnabled()) {
logger.debug("#mark");
}
}
@Override
public boolean markSupported() {
return true; //but we don't mark.
}
@Override
public long skip(long n) {
if (logger.isDebugEnabled()) {
logger.debug("#skip");
}
return 0;
}
}
/**
* Check whether the request HTTP method is required valid payload
*
* @param msgCtx Message Context of incoming request
* @return true if payload required, false otherwise
*/
private static boolean isValidPayloadRequired(MessageContext msgCtx) {
boolean isRequired = true;
if (HTTPConstants.HEADER_GET.equals(msgCtx.getProperty(HTTPConstants.HTTP_METHOD)) || HTTPConstants
.HEADER_DELETE.equals(msgCtx.getProperty(HTTPConstants.HTTP_METHOD))) {
isRequired = false;
}
return isRequired;
}
}