package com.sas.unravl.extractors; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.jayway.jsonpath.JsonPath; import com.sas.unravl.ApiCall; import com.sas.unravl.UnRAVL; import com.sas.unravl.UnRAVLException; import com.sas.unravl.annotations.UnRAVLExtractorPlugin; import com.sas.unravl.generators.Text; import com.sas.unravl.util.Json; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; /** * This extractor evaluates one or more <a * href='https://github.com/jayway/JsonPath'>JsonPath expressions</a> on the * JSON result or another JSON value and binds the resulting values to variables * in the current environment. For example, for the JSON result * * <pre> * { * "results" : [ * { * "elevation" : 8815.7158203125, * "location" : { * "lat" : 27.988056, * "lng" : 86.92527800000001 * }, * "resolution" : 152.7032318115234 * } * ], * "status" : "OK" * } * </pre> * * the following extracts the JsonPath-addressed values from the response and * sets the variables <var>elevation, lat</var>, and <var>lng</var>: * * <pre> * "bind" : [ * { "jsonPath" : { "elevation" : "$.results[0].elevation", * "lat" : "$.results[0].location.lat", * "lng" : "$.results[0].location.lng" }, * "from" : "jsonResponse" } * ], * </pre> * * Also, the JSON response body is bound to the variable * <var>responseBody</var>. * <p> * This extractor supports an optional parameter "from" as described below. * </p> * * <pre> * "from" : "varName" * </pre> * <p> * allows an UnRAVL script to extract from JSON defined by a UnRAVL environment * variable instead of the current API call's response body. The from value can * be a value assigned by another extractor or defined in the "env" element. * </p> * * @author David.Biesack@sas.com */ @UnRAVLExtractorPlugin({ "jsonPath", "jsonpath" }) public class JsonPathExtractor extends JsonExtractor { private static final Logger logger = Logger .getLogger(JsonPathExtractor.class); private ObjectMapper mapper = new ObjectMapper(); @Override public void extract(UnRAVL script, ObjectNode scriptlet, ApiCall call) throws UnRAVLException { Object fromObject = getJsonSource(script, scriptlet, call); ObjectNode bindings = Json.object(Json.firstFieldValue(scriptlet)); // TODO: look for the effective binding // "bind" : [ ..., { "jsonNode" : {}, "wrap" : true }, ...] // in inherited templates (match only if the value is {}). If true, wrap // this one as well. boolean wrap = booleanOption(scriptlet, "wrap"); for (Map.Entry<String, JsonNode> entry : Json.fields(bindings)) { JsonNode path = entry.getValue(); if (!path.isTextual()) { throw new UnRAVLException( "JsonPath extractor requires string path values, found " + path); } String pathString = call.getScript().expand(path.textValue()); Object value = JsonPath.read(fromObject, pathString); if (wrap) { value = Json.wrap(value); } script.bind(entry.getKey(), value); } } private Object getJsonSource(UnRAVL script, ObjectNode scriptlet, ApiCall call) throws UnRAVLException { JsonNode from = scriptlet.get("from"); Object fromObject = null; if (from == null) { // assert response body is valid JSON; extract JSON into // responseBody from = Json.parse(Text.utf8ToString(call.getResponseBody() .toByteArray())); script.bind("responseBody", from); fromObject = Json.unwrap(from); } else { if (from.isTextual()) { Object val = script.binding(from.textValue()); if (val instanceof Map) fromObject = val; else if (val instanceof List) fromObject = val; else if (val instanceof ObjectNode) { fromObject = mapper.convertValue((ObjectNode) val, Map.class); } else if (val instanceof ArrayNode) { fromObject = mapper.convertValue((ObjectNode) val, List.class); } else { String msg = String .format("Variable named by 'from' value %s in %s extractor is not an object or array. Value is %s", from, key(scriptlet), val); logger.error(msg); throw new UnRAVLException(msg); } } else { String msg = String.format( "'from' value %s in %s extractor is not a string.", from, key(scriptlet)); logger.error(msg); throw new UnRAVLException(msg); } } return fromObject; } }