EIPUtils.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.mediators.eip;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.gson.JsonParseException;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.spi.json.GsonJsonProvider;
import com.jayway.jsonpath.spi.json.JsonProvider;
import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
import com.jayway.jsonpath.spi.mapper.MappingProvider;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.JSONObjectExtensionException;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseException;
import org.apache.synapse.util.JSONMergeUtils;
import org.apache.synapse.util.xpath.SynapseJsonPath;
import org.apache.synapse.util.xpath.SynapseXPath;
import org.jaxen.JaxenException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* Utility methods for the EIP mediators
*/
public class EIPUtils {
private static final Log log = LogFactory.getLog(EIPUtils.class);
private static final String JSON_MEMBERS = "members";
private static final String JSON_ELEMENTS = "elements";
/**
* Return the set of elements specified by the XPath over the given envelope
*
* @param envelope SOAPEnvelope from which the elements will be extracted
* @param expression SynapseXPath expression describing the elements to be extracted
* @return List OMElements in the envelope matching the expression
* @throws JaxenException if the XPath expression evaluation fails
*/
public static List getMatchingElements(SOAPEnvelope envelope, SynapseXPath expression)
throws JaxenException {
return getMatchingElements(envelope,null,expression);
}
/**
* Return the set of elements specified by the XPath over the given envelope
*
* @param envelope SOAPEnvelope from which the elements will be extracted
* @param expression SynapseXPath expression describing the elements to be extracted
* @return List OMElements in the envelope matching the expression
* @throws JaxenException if the XPath expression evaluation fails
*/
public static List getMatchingElements(SOAPEnvelope envelope,MessageContext synCtxt, SynapseXPath expression)
throws JaxenException {
Object o = expression.evaluate(envelope, synCtxt);
if (o instanceof OMNode) {
List list = new ArrayList();
list.add(o);
return list;
} else if (o instanceof List) {
return (List) o;
} else {
return new ArrayList();
}
}
/**
* Return the set of detached elements specified by the XPath over the given envelope
*
* @param envelope SOAPEnvelope from which the elements will be extracted
* @param expression SynapseXPath expression describing the elements to be extracted
* @return List detached OMElements in the envelope matching the expression
* @throws JaxenException if the XPath expression evaluation fails
*/
public static List<OMNode> getDetachedMatchingElements(SOAPEnvelope envelope, MessageContext synCtxt,
SynapseXPath expression)
throws JaxenException {
List<OMNode> elementList = new ArrayList<OMNode>();
Object o = expression.evaluate(envelope, synCtxt);
if (o instanceof OMNode) {
elementList.add(((OMNode) o).detach());
} else if (o instanceof List) {
for (Object elem : (List) o) {
if (elem instanceof OMNode) {
elementList.add(((OMNode) elem).detach());
}
}
}
return elementList;
}
/**
* Modifies the envelope based on the provided XPath expression
* element that enriches the first envelope from the second
*
* @param envelope SOAPEnvelope to be enriched with the content
* @param expression SynapseXPath describing the enriching element
* @throws JaxenException on failing of processing the xpath
*/
public static void enrichEnvelope(SOAPEnvelope envelope, MessageContext synCtxt,
SynapseXPath expression) throws JaxenException {
OMElement enrichingElement;
List elementList = getMatchingElements(envelope, synCtxt, expression);
if (checkNotEmpty(elementList)) {
// attach at parent of the first result from the XPath, or to the SOAPBody
Object o = elementList.get(0);
if (o instanceof OMElement &&
((OMElement) o).getParent() != null &&
((OMElement) o).getParent() instanceof OMElement) {
enrichingElement = (OMElement) ((OMElement) o).getParent();
OMElement body = envelope.getBody();
if (!isBody(body, enrichingElement)) {
OMElement nonBodyElem = enrichingElement;
enrichingElement = envelope.getBody();
addChildren(elementList, enrichingElement);
while (!isBody(body, (OMElement) nonBodyElem.getParent())) {
nonBodyElem = (OMElement) nonBodyElem.getParent();
}
nonBodyElem.detach();
}
}
}
}
/**
* Merge two SOAP envelopes using the given XPath expression that specifies the
* element that enriches the first envelope from the second
*
* @param envelope SOAPEnvelope to be enriched with the content
* @param expression SynapseXPath describing the enriching element
* @throws JaxenException on failing of processing the xpath
*/
public static void enrichEnvelope(SOAPEnvelope envelope, SOAPEnvelope enricher,
MessageContext synCtxt,
SynapseXPath expression) throws JaxenException {
OMElement enrichingElement;
enricher.toString();
List list = getMatchingElements(enricher, synCtxt, expression);
if (checkNotEmpty(list)) {
enrichingElement = envelope.getBody();
//This has to introduce because if on complete expression written to extract SOAPEnvelop ex: "."
// then StAXOMBuilder end up in while loop that never exit. This cause to OOM the ESB. Hence the fix is to
// validate child element is a SOAPEnvelop
for (Object child : list) {
if (child instanceof SOAPEnvelope) {
throw new SynapseException("Could not add SOAPEnvelope as child element.");
}
}
addChildren(list, enrichingElement);
} else {
throw new SynapseException("Could not find matching elements to aggregate.");
}
}
private static boolean isBody(OMElement body, OMElement enrichingElement) {
try {
return (body.getLocalName().equals(enrichingElement.getLocalName()) &&
body.getNamespace().getNamespaceURI().equals(enrichingElement.getNamespace().getNamespaceURI()));
} catch (NullPointerException e) {
return false;
}
}
private static boolean checkNotEmpty(List list) {
return list != null && !list.isEmpty();
}
private static void addChildren(List list, OMElement element) {
Iterator itr = list.iterator();
Object o;
while (itr.hasNext()) {
o = itr.next();
if (o != null && o instanceof OMElement) {
element.addChild((OMElement) o);
}
itr.remove();
}
}
/**
* Util functions related to EIP Templates
*/
public static String getTemplatePropertyMapping(String templateName, String parameter) {
return templateName + ":" + parameter;
}
public static void createSynapseEIPTemplateProperty(MessageContext synCtxt, String templateName,
String paramName, Object value) {
String targetSynapsePropName = getTemplatePropertyMapping(templateName,paramName);
synCtxt.setProperty(targetSynapsePropName,value);
}
/**
* Enclose children of the soap body with a specific element
*
* @param envelope SOAPEnvelope which is to be enclosed
* @param encloseElement enclosing element
* @return modified SOAPEnvelope
*/
public static SOAPEnvelope encloseWithElement (SOAPEnvelope envelope, OMElement encloseElement) {
Iterator itr = envelope.getBody().getChildElements();
Object o;
while (itr.hasNext()) {
o = itr.next();
if (o != null && o instanceof OMElement) {
encloseElement.addChild((OMElement) o);
}
}
envelope.getBody().addChild(encloseElement);
return envelope;
}
/**
* Evaluate JSON path and retrieve the result as JsonElement. If multiple matches found, combine matching results
* in a comma separated list and parse as a JSON array.
*
* @param messageContext messageContext which contains the JSON payload.
* @return JsonArray or a JsonPrimitive depending on the JsonPath response.
*/
public static JsonElement getJSONElement(MessageContext messageContext, SynapseJsonPath jsonPath) {
JsonParser parser = new JsonParser();
Object objectList = jsonPath.evaluate(messageContext);
if (objectList instanceof List) {
List list = (List) objectList;
if (!list.isEmpty()) {
if (list.size() > 1) {
String result = "[" + StringUtils.join(list, ',') + "]";
JsonElement element = parser.parse(result);
return element.getAsJsonArray();
}
JsonElement result;
String resultString = list.get(0).toString().trim();
result = tryParseJsonString(parser, resultString);
return result;
}
}
return null;
}
/**
* Evaluate JSON path and retrieve the result as JsonElement while checking if the evaluated results are
* nothing other than JSON objects. Also, whenever there are multiple JSON objects as the result,
* merge them together.
*
* @param messageContext messageContext which contains the JSON payload.
* @return JsonArray or a JsonPrimitive depending on the JsonPath response.
*/
public static JsonElement getJSONObjectAsElement(MessageContext messageContext, SynapseJsonPath jsonPath)
throws JsonParseException, JSONObjectExtensionException {
JsonParser parser = new JsonParser();
Object objectList = jsonPath.evaluate(messageContext);
if (objectList instanceof List) {
List list = (List) objectList;
if (!list.isEmpty()) {
if (list.size() > 1) {
JsonObject resultObject = new JsonObject();
for (Object obj : list) {
String objString = obj.toString().trim();
JsonElement element = tryParseJsonString(parser, objString);
if (element != null) {
if (element.isJsonObject()) {
try {
JSONMergeUtils.extendJSONObject(resultObject,
JSONMergeUtils.ConflictStrategy.MERGE_INTO_ARRAY,
element.getAsJsonObject());
} catch (JSONObjectExtensionException | UnsupportedOperationException e) {
throw new JSONObjectExtensionException("Could not extend JSON object.");
}
} else {
throw new JsonParseException("Invalid JSON object. Could not extend.");
}
}
}
return resultObject;
}
JsonElement result;
String resultString = list.get(0).toString().trim();
result = tryParseJsonString(parser, resultString);
if (!result.isJsonObject()) {
throw new JsonParseException("Invalid JSON object. Could not extend.");
}
return result;
}
}
return null;
}
/**
* Given a json string and a parser this method will return the parsed string.
*
* @param parser JSON parser instance.
* @param inputJson input JSON string.
* @return parsed JsonElement.
*/
public static JsonElement tryParseJsonString(JsonParser parser, String inputJson) {
try {
return parser.parse(validateStringForGson(inputJson));
} catch (JsonSyntaxException e) {
log.error(inputJson + " cannot be parsed to a valid JSON payload", e);
return null;
}
}
/**
* Enclose the string with quotes before parsing with Gson library.
* Due to : https://github.com/google/gson/issues/1286
*
* @param input input String.
* @return validated String.
*/
private static String validateStringForGson(String input) {
String output = input;
try {
Double.parseDouble(input);
} catch (NumberFormatException e) {
// not a number
if (!(input.equals("true") || input.equals("false"))) {
// not a boolean
if (!(input.startsWith("[") && input.endsWith("]"))) {
// not a JSON array
if (!(input.startsWith("{") && input.endsWith("}"))) {
// not a JSON object
if(!(input.startsWith("\"") && input.endsWith("\""))) {
// not a string with quotes -> then add quotes
output = "\"" + input + "\"";
}
}
}
}
}
return output;
}
/**
* Formats the response from jsonpath operations
* JayWay json-path response have additional elements like "members"(for objects) and "elements"(for arrays)
* This method will correct such strings by removing additional elements.
*
* @param input input jsonElement.
* @return corrected jsonObject.
*/
public static Object formatJsonPathResponse(Object input) {
JsonElement jsonElement = (JsonElement) input;
if (jsonElement.isJsonPrimitive()) {
return jsonElement.getAsString();
} else if(jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject.has(JSON_MEMBERS)) {
return jsonObject.get(JSON_MEMBERS);
} else if (jsonObject.has(JSON_ELEMENTS)) {
return jsonObject.get(JSON_ELEMENTS);
}
return jsonObject.toString();
}
return jsonElement.isJsonArray() ? jsonElement : null;
}
/**
* This merges two json objects into one. The PrimaryPayload will have the merged object
* @param primaryPayload The json object where the key value pairs will be added
* @param secondaryPayload The json object whose key value pair will be added to primaryPayload
*/
public static void mergeJsonObjects(JsonObject primaryPayload, JsonObject secondaryPayload) {
for (String key : secondaryPayload.keySet()) {
primaryPayload.add(key, secondaryPayload.get(key));
}
}
/**
* Set default configuration for Jayway JsonPath by providing the JsonProviders and Mapping providers
*/
public static void setJsonPathConfiguration() {
Configuration.setDefaults(new Configuration.Defaults() {
private final JsonProvider jsonProvider = new GsonJsonProvider(new GsonBuilder().serializeNulls().create());
private final MappingProvider mappingProvider = new GsonMappingProvider();
public JsonProvider jsonProvider() {
return jsonProvider;
}
public MappingProvider mappingProvider() {
return mappingProvider;
}
public Set<Option> options() {
return EnumSet.noneOf(Option.class);
}
});
}
}