package com.sas.unravl.extractors; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.sas.unravl.ApiCall; import com.sas.unravl.UnRAVL; import com.sas.unravl.UnRAVLException; import com.sas.unravl.generators.Text; import com.sas.unravl.util.Json; import java.io.IOException; import java.util.Map; import org.apache.log4j.Logger; /** * A base class for script-based extractors, such as {@link GroovyExtractor} and * {@link JavaScriptExtractor}. * <p> * Usage: * * <pre> * "bind" : [ * { "lang" : { var-value-pairs } } * ] * </pre> * * where "lang" is a supported script language name such as "groovy" or * "javascript". * <p> * The <var>var-value-pairs</var> are JSON object notation such as * <code>"varName" : "<em>expression</em>"</code>. Each * <code><em>expression</em></code> may be a string, a string in the form * "@file-or-URL", or an array of such source specifications, as defined in * {@link Text}. The resulting text is evaluated as a script in the target * language. * </p> * <p> * Expressions may use any variable binding that is currently in effect, * including preceding <var>var-value-pairs</var> in the current scriptlet. * Example (for Groovy): * </p> * * <pre> * "bind" : [ * { "groovy" : { "pi" : "Math.PI", * "r" : "json.a[2].doubleValue()", * "pirsquared" : "pi*r*r", * "itemName" : "json.name.textValue()" * } * } * ] * </pre> * * <p> * The script value is subject to environment expansion before running. All * variables in the environment are available as local variables when the script * runs. * </p> * * @author David.Biesack@sas.com */ public class BaseScriptExtractor extends BaseUnRAVLExtractor { private static final Logger logger = Logger .getLogger(BaseUnRAVLExtractor.class); private final String language; public BaseScriptExtractor(String language) { this.language = language; } @Override public void extract(UnRAVL unravl, ObjectNode scriptlet, ApiCall call) throws UnRAVLException { super.extract(unravl, scriptlet, call); boolean unwrap = unwrapOption(scriptlet); ObjectNode bindings = Json.object(Json.firstFieldValue(scriptlet)); for (Map.Entry<String, JsonNode> e : Json.fields(bindings)) { JsonNode sourceNode = null; sourceNode = e.getValue(); try { String name = e.getKey(); if (!(sourceNode.isTextual() || sourceNode.isArray())) { throw new UnRAVLException( key(scriptlet) + " extractor requires a string or array of strings, found " + sourceNode); } String expressionString = new Text(unravl, sourceNode).text(); expressionString = call.getScript().expand(expressionString); Object value = evaluate(unravl, expressionString); unravl.bind(name, unwrap ? Json.unwrap(value) : value); } catch (RuntimeException rte) { logger.error("Script '" + sourceNode + "' (expansion '" + sourceNode + "') threw a runtime exception " + e.getClass().getName() + ", " + rte.getMessage()); throw new UnRAVLException(rte.getMessage(), rte); } catch (IOException ioe) { logger.error("I/O exception " + ioe.getMessage() + " reading script '" + sourceNode); throw new UnRAVLException(ioe.getMessage(), ioe); } } } /** * Evaluate an expression. Subclasses may override this. The default * implementation evaluates the expression using the Java script language * associated with this instance (Groovy, JavaScript, etc.) * * @param unravl * the current UnRAVL script * @param expressionString * the expression to evaluate * @return the result of evaluating the expression * @throws UnRAVLException * if the expression is invalid or results in an error during * evaluation */ protected Object evaluate(UnRAVL unravl, String expressionString) throws UnRAVLException { return unravl.evalWith(expressionString, language); } }