Skip to content

Commit

Permalink
Switching groovy evaluation from JSR223 to native evaluator
Browse files Browse the repository at this point in the history
  • Loading branch information
semancik committed Mar 13, 2019
1 parent 42add24 commit 359268a
Show file tree
Hide file tree
Showing 12 changed files with 474 additions and 160 deletions.
3 changes: 2 additions & 1 deletion build-system/pom.xml
Expand Up @@ -78,7 +78,7 @@
<jdbc.mariadb>2.3.0</jdbc.mariadb>
<jdbc.mysql>6.0.6</jdbc.mysql>
<wicket.version>8.0.0</wicket.version>
<groovy.version>2.4.14</groovy.version>
<groovy.version>2.5.6</groovy.version>
<activiti-engine.version>5.22.0</activiti-engine.version>
<activiti-spring.version>5.22.0</activiti-spring.version>
<commons-email.version>1.3</commons-email.version>
Expand Down Expand Up @@ -223,6 +223,7 @@
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.python</groupId>
Expand Down
Expand Up @@ -25,6 +25,7 @@ public class MidPointConstants {

public static final String NS_MIDPOINT_PUBLIC_PREFIX = "http://midpoint.evolveum.com/xml/ns/public";
public static final String NS_MIDPOINT_TEST_PREFIX = "http://midpoint.evolveum.com/xml/ns/test";
public static final String EXPRESSION_LANGUAGE_URL_BASE = NS_MIDPOINT_PUBLIC_PREFIX + "/expression/language#";

public static final String NS_RA = NS_MIDPOINT_PUBLIC_PREFIX+"/resource/annotation-3";
public static final String PREFIX_NS_RA = "ra";
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2017 Evolveum
* Copyright (c) 2010-2019 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -52,6 +52,7 @@
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -100,6 +101,8 @@ public class TestUtil {
private static DatatypeFactory datatypeFactory = null;

private static final Trace LOGGER = TraceManager.getTrace(TestUtil.class);

private static final Random RND = new Random();

public static <T> void assertPropertyValueSetEquals(Collection<PrismPropertyValue<T>> actual, T... expected) {
Set<T> set = new HashSet<>();
Expand Down Expand Up @@ -666,4 +669,43 @@ private static void assertPermission(File f, Set<PosixFilePermission> permissio
private static void assertNoPermission(File f, Set<PosixFilePermission> permissions, PosixFilePermission permission) {
assertFalse(permissions.contains(permission), f.getPath() + ": unexpected permission "+permission);
}

public static ParallelTestThread[] multithread(final String TEST_NAME, MultithreadRunner lambda, int numberOfThreads, Integer randomStartDelayRange) {
ParallelTestThread[] threads = new ParallelTestThread[numberOfThreads];
for (int i = 0; i < numberOfThreads; i++) {
threads[i] = new ParallelTestThread(i,
(ii) -> {
randomDelay(randomStartDelayRange);
LOGGER.info("{} starting", Thread.currentThread().getName());
lambda.run(ii);
});
threads[i].setName("Thread " + (i+1) + " of " + numberOfThreads);
threads[i].start();
}
return threads;
}

public static void randomDelay(Integer range) {
if (range == null) {
return;
}
try {
Thread.sleep(RND.nextInt(range));
} catch (InterruptedException e) {
// Nothing to do, really
}
}

public static void waitForThreads(ParallelTestThread[] threads, long timeout) throws InterruptedException {
for (int i = 0; i < threads.length; i++) {
if (threads[i].isAlive()) {
System.out.println("Waiting for " + threads[i]);
threads[i].join(timeout);
}
Throwable threadException = threads[i].getException();
if (threadException != null) {
throw new AssertionError("Test thread "+i+" failed: "+threadException.getMessage(), threadException);
}
}
}
}
@@ -0,0 +1,218 @@
/*
* Copyright (c) 2010-2019 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;

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.common.LocalizationService;
import com.evolveum.midpoint.model.common.expression.functions.FunctionLibrary;
import com.evolveum.midpoint.model.common.expression.script.ScriptEvaluator;
import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionUtil;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.crypto.Protector;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.repo.common.ObjectResolver;
import com.evolveum.midpoint.repo.common.expression.ExpressionSyntaxException;
import com.evolveum.midpoint.repo.common.expression.ExpressionUtil;
import com.evolveum.midpoint.repo.common.expression.ExpressionVariables;
import com.evolveum.midpoint.schema.constants.MidPointConstants;
import com.evolveum.midpoint.schema.internals.InternalCounters;
import com.evolveum.midpoint.schema.internals.InternalMonitor;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ExceptionUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
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
* @param <C> compiled code
*
*/
public abstract class AbstractCachingScriptEvaluator<C> implements ScriptEvaluator {

private static final Trace LOGGER = TraceManager.getTrace(AbstractCachingScriptEvaluator.class);

private final PrismContext prismContext;
private final Protector protector;
private final LocalizationService localizationService;

private final Map<String, C> scriptCache;

public AbstractCachingScriptEvaluator(PrismContext prismContext, Protector protector,
LocalizationService localizationService) {
this.prismContext = prismContext;
this.protector = protector;
this.scriptCache = new ConcurrentHashMap<>();
this.localizationService = localizationService;
}

public PrismContext getPrismContext() {
return prismContext;
}

public Protector getProtector() {
return protector;
}

public LocalizationService getLocalizationService() {
return localizationService;
}

public Map<String, C> getScriptCache() {
return scriptCache;
}

@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, CommunicationException, ConfigurationException, SecurityViolationException {

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();
}

C compiledScript = getCompiledScript(codeString, contextDescription);

Object evalRawResult;
try {
InternalMonitor.recordCount(InternalCounters.SCRIPT_EXECUTION_COUNT);
evalRawResult = evaluateScript(compiledScript, variables, objectResolver, functions, contextDescription, task, result);
} catch (Throwable e) {
throw localizationService.translate(
new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription,
e, ExceptionUtil.getUserFriendlyMessage(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 && (outputDefinition instanceof PrismContainerDefinition<?>)) {
// This is the case when we need a container, but we do not have compile-time class for that
// E.g. this may be container in object extension (MID-5080)
javaReturnType = (Class<T>) PrismContainerValue.class;
}

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<T>) String.class;
}
LOGGER.trace("expected return type: XSD={}, Java={}", xsdReturnType, javaReturnType);

List<V> pvals = new ArrayList<>();

// 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>) PrismValueCollectionsUtil.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;
}

protected C getCompiledScript(String codeString, String contextDescription) throws ExpressionEvaluationException {
C compiledScript = scriptCache.get(codeString);
if (compiledScript != null) {
return compiledScript;
}
InternalMonitor.recordCount(InternalCounters.SCRIPT_COMPILE_COUNT);
try {
compiledScript = compileScript(codeString, contextDescription);
} catch (Exception e) {
throw new ExpressionEvaluationException(e.getMessage() + " while compiling " + contextDescription, e);
}
scriptCache.put(codeString, compiledScript);
return compiledScript;
}

protected abstract C compileScript(String codeString, String contextDescription) throws Exception;

protected abstract Object evaluateScript(C compiledScript, ExpressionVariables variables,
ObjectResolver objectResolver, Collection<FunctionLibrary> functions, String contextDescription,
Task task, OperationResult result)
throws Exception;

protected Map<String,Object> getVariablesMap(ExpressionVariables variables, ObjectResolver objectResolver,
Collection<FunctionLibrary> functions, String contextDescription, Task task, OperationResult result) throws ExpressionSyntaxException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException {
return ScriptExpressionUtil.prepareScriptVariables(variables, objectResolver, functions, contextDescription, getPrismContext(), task, result);
}

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);
}
}

}

0 comments on commit 359268a

Please sign in to comment.