/*
* Copyright (c) 2010-2017 Evolveum
*
* Licensed 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 com.evolveum.midpoint.model.common.expression.script.jsr223;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.model.common.expression.ExpressionSyntaxException;
import com.evolveum.midpoint.model.common.expression.ExpressionUtil;
import com.evolveum.midpoint.model.common.expression.ExpressionVariables;
import com.evolveum.midpoint.model.common.expression.functions.FunctionLibrary;
import com.evolveum.midpoint.model.common.expression.script.ScriptEvaluator;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.crypto.Protector;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.schema.constants.MidPointConstants;
import com.evolveum.midpoint.schema.internals.InternalMonitor;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectResolver;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionEvaluatorType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionReturnTypeType;
/**
* Expression evaluator that is using javax.script (JSR-223) engine.
*
* @author Radovan Semancik
*
*/
public class Jsr223ScriptEvaluator implements ScriptEvaluator {
private static final String LANGUAGE_URL_BASE = MidPointConstants.NS_MIDPOINT_PUBLIC_PREFIX + "/expression/language#";
private ScriptEngine scriptEngine;
private PrismContext prismContext;
private Protector protector;
private Map<String, CompiledScript> scriptCache;
public Jsr223ScriptEvaluator(String engineName, PrismContext prismContext, Protector protector) {
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
scriptEngine = scriptEngineManager.getEngineByName(engineName);
if (scriptEngine == null) {
throw new SystemException("The JSR-223 scripting engine for '"+engineName+"' was not found");
}
this.prismContext = prismContext;
this.protector = protector;
this.scriptCache = new ConcurrentHashMap<>();
}
@Override
public <T, V extends PrismValue> List<V> evaluate(ScriptExpressionEvaluatorType expressionType,
ExpressionVariables variables, ItemDefinition outputDefinition,
Function<Object, Object> additionalConvertor,
ScriptExpressionReturnTypeType suggestedReturnType,
ObjectResolver objectResolver, Collection<FunctionLibrary> functions,
String contextDescription, Task task, OperationResult result) throws ExpressionEvaluationException,
ObjectNotFoundException, ExpressionSyntaxException {
Bindings bindings = convertToBindings(variables, objectResolver, functions, contextDescription, task, result);
String codeString = expressionType.getCode();
if (codeString == null) {
throw new ExpressionEvaluationException("No script code in " + contextDescription);
}
boolean allowEmptyValues = false;
if (expressionType.isAllowEmptyValues() != null) {
allowEmptyValues = expressionType.isAllowEmptyValues();
}
CompiledScript compiledScript = createCompiledScript(codeString, contextDescription);
Object evalRawResult;
try {
InternalMonitor.recordScriptExecution();
evalRawResult = compiledScript.eval(bindings);
} catch (Throwable e) {
throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e);
}
if (outputDefinition == null) {
// No outputDefinition means "void" return type, we can return right now
return null;
}
QName xsdReturnType = outputDefinition.getTypeName();
Class<T> javaReturnType = XsdTypeMapper.toJavaType(xsdReturnType);
if (javaReturnType == null) {
javaReturnType = prismContext.getSchemaRegistry().getCompileTimeClass(xsdReturnType);
}
if (javaReturnType == null) {
// TODO quick and dirty hack - because this could be because of enums defined in schema extension (MID-2399)
// ...and enums (xsd:simpleType) are not parsed into ComplexTypeDefinitions
javaReturnType = (Class) String.class;
}
List<V> pvals = new ArrayList<V>();
// TODO: what about PrismContainer and
// PrismReference? Shouldn't they be processed in the same way as
// PrismProperty?
if (evalRawResult instanceof Collection) {
for (Object evalRawResultElement : (Collection)evalRawResult) {
T evalResult = convertScalarResult(javaReturnType, additionalConvertor, evalRawResultElement, contextDescription);
if (allowEmptyValues || !ExpressionUtil.isEmpty(evalResult)) {
pvals.add((V) ExpressionUtil.convertToPrismValue(evalResult, outputDefinition, contextDescription, prismContext));
}
}
} else if (evalRawResult instanceof PrismProperty<?>) {
pvals.addAll((Collection<? extends V>) PrismPropertyValue.cloneCollection(((PrismProperty<T>)evalRawResult).getValues()));
} else {
T evalResult = convertScalarResult(javaReturnType, additionalConvertor, evalRawResult, contextDescription);
if (allowEmptyValues || !ExpressionUtil.isEmpty(evalResult)) {
pvals.add((V) ExpressionUtil.convertToPrismValue(evalResult, outputDefinition, contextDescription, prismContext));
}
}
return pvals;
}
public <T> Object evaluateReportScript(String codeString, ExpressionVariables variables, ObjectResolver objectResolver, Collection<FunctionLibrary> functions,
String contextDescription, OperationResult result) throws ExpressionEvaluationException,
ObjectNotFoundException, ExpressionSyntaxException {
Bindings bindings = convertToBindings(variables, objectResolver, functions, contextDescription, (Task) null, result);
// String codeString = code;
if (codeString == null) {
throw new ExpressionEvaluationException("No script code in " + contextDescription);
}
boolean allowEmptyValues = true;
// if (expressionType.isAllowEmptyValues() != null) {
// allowEmptyValues = expressionType.isAllowEmptyValues();
// }
CompiledScript compiledScript = createCompiledScript(codeString, contextDescription);
Object evalRawResult;
try {
InternalMonitor.recordScriptExecution();
evalRawResult = compiledScript.eval(bindings);
} catch (Throwable e) {
throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e);
}
return evalRawResult;
}
private CompiledScript createCompiledScript(String codeString, String contextDescription) throws ExpressionEvaluationException {
CompiledScript compiledScript = scriptCache.get(codeString);
if (compiledScript != null) {
return compiledScript;
}
try {
InternalMonitor.recordScriptCompile();
compiledScript = ((Compilable)scriptEngine).compile(codeString);
} catch (ScriptException e) {
throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e);
}
scriptCache.put(codeString, compiledScript);
return compiledScript;
}
private <T> T convertScalarResult(Class<T> expectedType, Function<Object, Object> additionalConvertor, Object rawValue, String contextDescription) throws ExpressionEvaluationException {
try {
T convertedValue = ExpressionUtil.convertValue(expectedType, additionalConvertor, rawValue, protector, prismContext);
return convertedValue;
} catch (IllegalArgumentException e) {
throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e);
}
}
private Bindings convertToBindings(ExpressionVariables variables, ObjectResolver objectResolver,
Collection<FunctionLibrary> functions,
String contextDescription, Task task, OperationResult result) throws ExpressionSyntaxException, ObjectNotFoundException {
Bindings bindings = scriptEngine.createBindings();
bindings.putAll(ExpressionUtil.prepareScriptVariables(variables, objectResolver, functions, contextDescription, prismContext, task, result));
return bindings;
}
/* (non-Javadoc)
* @see com.evolveum.midpoint.common.expression.ExpressionEvaluator#getLanguageName()
*/
@Override
public String getLanguageName() {
return scriptEngine.getFactory().getLanguageName();
}
/* (non-Javadoc)
* @see com.evolveum.midpoint.common.expression.ExpressionEvaluator#getLanguageUrl()
*/
@Override
public String getLanguageUrl() {
return LANGUAGE_URL_BASE + getLanguageName();
}
}