public Object eval(String expression, Entity entity) { return eval(expression, entity, ENTITY_REFERENCE_DEFAULT_FETCHING_DEPTH); }
/** * Evaluate a expression for a given entity. * * @param expression JavaScript expression * @param entity entity * @return evaluated expression result, return type depends on the expression. */ public Object eval(String expression, Entity entity, int depth) { return eval(createBindings(entity, depth), expression); }
public JsMagmaScriptEvaluator(NashornScriptEngine jsScriptEngine) throws javax.script.ScriptException, IOException { this.jsScriptEngine = requireNonNull(jsScriptEngine); for (String resourceName : RESOURCE_NAMES) { loadScript(resourceName); } }
/** * Creates magmascript bindings for a given Entity. * * @param entity the entity to bind to the magmascript $ function * @param depth maximum depth to follow references when creating the entity value map * @return Bindings with $ function bound to the entity */ private Bindings createBindings(Entity entity, int depth) { Bindings bindings = new SimpleBindings(); JSObject global = (JSObject) magmaBindings.get("nashorn.global"); JSObject magmaScript = (JSObject) global.getMember(KEY_MAGMA_SCRIPT); JSObject dollarFunction = (JSObject) magmaScript.getMember(KEY_DOLLAR); JSObject bindFunction = (JSObject) dollarFunction.getMember(BIND); Object boundDollar = bindFunction.call(dollarFunction, toScriptEngineValueMap(entity, depth)); bindings.put(KEY_DOLLAR, boundDollar); bindings.put(KEY_NEW_VALUE, magmaScript.getMember(KEY_NEW_VALUE)); bindings.put(KEY_IS_NULL, magmaScript.getMember(KEY_IS_NULL)); return bindings; }
/** * Convert entity to a JavaScript object. Adds "_idValue" as a special key to every level for * quick access to the id value of an entity. * * @param entity The entity to be flattened, should start with non null entity * @param depth Represents the number of reference levels being added to the JavaScript object * @return A JavaScript object in Tree form, containing entities and there references */ private Object toScriptEngineValueMap(Entity entity, int depth) { if (entity != null) { Object idValue = toScriptEngineValue(entity, entity.getEntityType().getIdAttribute(), 0); if (depth == 0) { return idValue; } else { Map<String, Object> map = Maps.newHashMap(); entity .getEntityType() .getAtomicAttributes() .forEach(attr -> map.put(attr.getName(), toScriptEngineValue(entity, attr, depth))); map.put(KEY_ID_VALUE, idValue); return map; } } else { return null; } }
case XREF: Entity xrefEntity = entity.getEntity(attrName); value = toScriptEngineValueMap(xrefEntity, depth - 1); break; case CATEGORICAL_MREF: entity .getEntities(attrName) .forEach(mrefEntity -> mrefValues.add(toScriptEngineValueMap(mrefEntity, depth - 1))); value = jsArray; break;
/** * Resolved boolean expressions * * @param expressions JavaScript expressions * @param entity entity used during expression evaluations * @return for each expression: <code>true</code> or <code>false</code> */ List<Boolean> resolveBooleanExpressions(List<String> expressions, Entity entity) { if (expressions.isEmpty()) { return Collections.emptyList(); } return jsMagmaScriptEvaluator .eval(expressions, entity) .stream() .map(this::convertToBoolean) .collect(toList()); }
/** * Evaluates multiple expressions for a single entity instance. * * @param expressions {@link Collection} containing the expression {@link String}s * @param entity the entity to bind the magmascript $ function to * @return Collection containing the expression result {@link Object}s */ public Collection<Object> eval(Collection<String> expressions, Entity entity) { Stopwatch stopwatch = null; if (LOG.isTraceEnabled()) { stopwatch = Stopwatch.createStarted(); } Bindings bindings = createBindings(entity, ENTITY_REFERENCE_DEFAULT_FETCHING_DEPTH); List<Object> result = expressions.stream().map(expression -> eval(bindings, expression)).collect(toList()); if (stopwatch != null) { stopwatch.stop(); LOG.trace("Script evaluation took {} µs", stopwatch.elapsed(MICROSECONDS)); } return result; }
@Override public Object apply(AttributeMapping attributeMapping, Entity sourceEntity, EntityType sourceEntityType) { String algorithm = attributeMapping.getAlgorithm(); if (isEmpty(algorithm)) { return null; } Object value = jsMagmaScriptEvaluator.eval(algorithm, sourceEntity); return convert(value, attributeMapping.getTargetAttribute()); }
/** Execute a JavaScript using the Magma API */ Object executeScript(String jsScript, Map<String, Object> parameters) { EntityType entityType = entityTypeFactory.create("entity"); Set<String> attributeNames = parameters.keySet(); attributeNames.forEach(key -> entityType.addAttribute(attributeFactory.create().setName(key))); if (attributeNames.iterator().hasNext()) { entityType.getAttribute(attributeNames.iterator().next()).setIdAttribute(true); } Entity entity = new DynamicEntity(entityType); parameters.forEach(entity::set); return jsMagmaScriptEvaluator.eval(jsScript, entity); } }
@Override public Iterable<AlgorithmEvaluation> applyAlgorithm(Attribute targetAttribute, String algorithm, Iterable<Entity> sourceEntities) { return stream(sourceEntities.spliterator(), false).map(entity -> { AlgorithmEvaluation algorithmResult = new AlgorithmEvaluation(entity); Object derivedValue; try { Object result = jsMagmaScriptEvaluator.eval(algorithm, entity); derivedValue = convert(result, targetAttribute); } catch (RuntimeException e) { if (e.getMessage() == null) { return algorithmResult.errorMessage( "Applying an algorithm on a null source value caused an exception. Is the target attribute required?"); } return algorithmResult.errorMessage(e.getMessage()); } return algorithmResult.value(derivedValue); }).collect(toList()); }
@Override public Iterable<AlgorithmEvaluation> applyAlgorithm( Attribute targetAttribute, String algorithm, Iterable<Entity> sourceEntities, int depth) { return stream(sourceEntities.spliterator(), false) .map( entity -> { AlgorithmEvaluation algorithmResult = new AlgorithmEvaluation(entity); Object derivedValue; try { Object result = jsMagmaScriptEvaluator.eval(algorithm, entity, depth); // jsMagmaScriptEvaluator.eval() catches and returns the error instead of throwing // it // so check instance of result object here if (result instanceof ScriptException) { return algorithmResult.errorMessage(((ScriptException) result).getMessage()); } derivedValue = convert(result, targetAttribute); } catch (RuntimeException e) { if (e.getMessage() == null) { return algorithmResult.errorMessage( "Applying an algorithm on a null source value caused an exception. Is the target attribute required?"); } return algorithmResult.errorMessage(e.getLocalizedMessage()); } return algorithmResult.value(derivedValue); }) .collect(toList()); }
@Override public Object apply( AttributeMapping attributeMapping, Entity sourceEntity, EntityType sourceEntityType, int depth) { String algorithm = attributeMapping.getAlgorithm(); if (isEmpty(algorithm)) { return null; } Object result = jsMagmaScriptEvaluator.eval(algorithm, sourceEntity, depth); // jsMagmaScriptEvaluator.eval() catches and returns the error instead of throwing it // so check instance of result object here if (result instanceof ScriptException) { throw new ScriptException( ((ScriptException) result).getMessage(), ((ScriptException) result).getCause()); } return convert(result, attributeMapping.getTargetAttribute()); }