Skip to content

Commit

Permalink
Improve custom function library initialization
Browse files Browse the repository at this point in the history
Because of doubts concerning MID-4396 the code that initializes
function libraries (and custom ones in particular) was cleaned up
a bit. Synchronization and privileged access were implemented.
  • Loading branch information
mederly committed Jan 19, 2018
1 parent 66e0ef2 commit c105f0c
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 54 deletions.
Expand Up @@ -21,8 +21,8 @@
import com.evolveum.midpoint.model.common.expression.functions.FunctionLibrary;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.crypto.Protector;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.repo.common.expression.ExpressionFactory;
import com.evolveum.midpoint.repo.common.expression.ExpressionSyntaxException;
import com.evolveum.midpoint.schema.GetOperationOptions;
Expand All @@ -32,12 +32,9 @@
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.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.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FunctionLibraryType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionEvaluatorType;

Expand All @@ -48,19 +45,23 @@
*/
public class ScriptExpressionFactory {

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

public static String DEFAULT_LANGUAGE = "http://midpoint.evolveum.com/xml/ns/public/expression/language#Groovy";

private Map<String,ScriptEvaluator> evaluatorMap = new HashMap<String, ScriptEvaluator>();
private Map<String,ScriptEvaluator> evaluatorMap = new HashMap<>();
private ObjectResolver objectResolver;
private PrismContext prismContext;
private final PrismContext prismContext;
private Collection<FunctionLibrary> functions;
private Protector protector;
private final Protector protector;
private final RepositoryService repositoryService; // might be null during low-level testing

private Map<String, FunctionLibrary> customFunctionLibraryCache;

public ScriptExpressionFactory(PrismContext prismContext, Protector protector) {
public ScriptExpressionFactory(PrismContext prismContext, Protector protector, RepositoryService repositoryService) {
this.prismContext = prismContext;
this.protector = protector;
this.repositoryService = repositoryService;
}

public ObjectResolver getObjectResolver() {
Expand Down Expand Up @@ -89,48 +90,53 @@ public Map<String, ScriptEvaluator> getEvaluators() {
return evaluatorMap;
}

public ScriptExpression createScriptExpression(ScriptExpressionEvaluatorType expressionType, ItemDefinition outputDefinition, ExpressionFactory expressionFactory, String shortDesc, Task task, OperationResult result) throws ExpressionSyntaxException {
public ScriptExpression createScriptExpression(ScriptExpressionEvaluatorType expressionType, ItemDefinition outputDefinition,
ExpressionFactory expressionFactory, String shortDesc, Task task, OperationResult result) throws ExpressionSyntaxException {

initializeCustomFunctionsLibraryCache(expressionFactory, task, result);
//cache cleanup method

ScriptExpression expression = new ScriptExpression(getEvaluator(getLanguage(expressionType), shortDesc), expressionType);
expression.setOutputDefinition(outputDefinition);
expression.setObjectResolver(objectResolver);
expression.setFunctions(new ArrayList<>(functions));
Collection<FunctionLibrary> functionsToUse = new ArrayList<>(functions);
functionsToUse.addAll(customFunctionLibraryCache.values());
expression.setFunctions(functionsToUse);
return expression;
}

// TODO make this code synchronized and ensure that searchIterative below is executed under privileged account
// if performance becomes an issue, replace 'synchronized' with something more elaborate
private synchronized void initializeCustomFunctionsLibraryCache(ExpressionFactory expressionFactory, Task task,
OperationResult result) throws ExpressionSyntaxException {
if (customFunctionLibraryCache != null) {
expression.getFunctions().addAll(customFunctionLibraryCache.values());
} else {
customFunctionLibraryCache = new HashMap<>();
//Custom functions
OperationResult subResult = result.createMinorSubresult(ScriptExpressionUtil.class.getName() + ".searchCustomFunctions");
ResultHandler<FunctionLibraryType> functionLibraryHandler = new ResultHandler<FunctionLibraryType>() {

@Override
public boolean handle(PrismObject<FunctionLibraryType> object, OperationResult parentResult) {
FunctionLibrary customLibrary = new FunctionLibrary();
customLibrary.setVariableName(object.getName().getOrig());
customLibrary.setGenericFunctions(new CustomFunctions(object.asObjectable(), expressionFactory, result, task));
customLibrary.setNamespace(MidPointConstants.NS_FUNC_CUSTOM);
customFunctionLibraryCache.put(object.getName().getOrig(), customLibrary);
expression.getFunctions().add(customLibrary);
return true;
}
};
try {
objectResolver.searchIterative(FunctionLibraryType.class, null,
SelectorOptions.createCollection(GetOperationOptions.createReadOnly()), functionLibraryHandler, task,
subResult);
subResult.recordSuccessIfUnknown();
} catch (SchemaException | CommunicationException | ConfigurationException | SecurityViolationException
| ExpressionEvaluationException | ObjectNotFoundException e) {
subResult.recordFatalError("Failed to initialize custom functions", e);
throw new ExpressionSyntaxException("An error occurred during custom libraries initialization. " + e.getMessage(), e);
}
return;
}
customFunctionLibraryCache = new HashMap<>();
if (repositoryService == null) {
LOGGER.warn("No repository service set for ScriptExpressionFactory; custom functions will not be loaded. This"
+ " can occur during low-level testing; never during standard system execution.");
return;
}
OperationResult subResult = result
.createMinorSubresult(ScriptExpressionUtil.class.getName() + ".searchCustomFunctions");
ResultHandler<FunctionLibraryType> functionLibraryHandler = (object, parentResult) -> {
FunctionLibrary customLibrary = new FunctionLibrary();
customLibrary.setVariableName(object.getName().getOrig());
customLibrary
.setGenericFunctions(new CustomFunctions(object.asObjectable(), expressionFactory, result, task));
customLibrary.setNamespace(MidPointConstants.NS_FUNC_CUSTOM);
customFunctionLibraryCache.put(object.getName().getOrig(), customLibrary);
return true;
};
try {
repositoryService.searchObjectsIterative(FunctionLibraryType.class, null, functionLibraryHandler,
SelectorOptions.createCollection(GetOperationOptions.createReadOnly()), false, subResult);
subResult.recordSuccessIfUnknown();
} catch (SchemaException | RuntimeException e) {
subResult.recordFatalError("Failed to initialize custom functions", e);
throw new ExpressionSyntaxException(
"An error occurred during custom libraries initialization. " + e.getMessage(), e);
}

//cache cleanup method


return expression;
}

public void registerEvaluator(String language, ScriptEvaluator evaluator) {
Expand Down
Expand Up @@ -96,7 +96,7 @@ public void setupFactory() {
Protector protector = new ProtectorImpl();
Collection<FunctionLibrary> functions = new ArrayList<>();
functions.add(FunctionLibraryUtil.createBasicFunctionLibrary(prismContext, protector));
scriptExpressionfactory = new ScriptExpressionFactory(prismContext, protector);
scriptExpressionfactory = new ScriptExpressionFactory(prismContext, protector, null);
scriptExpressionfactory.setObjectResolver(resolver);
scriptExpressionfactory.setFunctions(functions);
localizationService = LocalizationTestUtil.getLocalizationService();
Expand Down Expand Up @@ -271,7 +271,7 @@ private ScriptExpression createScriptExpression(ScriptExpressionEvaluatorType ex
ScriptExpression expression = new ScriptExpression(scriptExpressionfactory.getEvaluators().get(expressionType.getLanguage()), expressionType);
expression.setOutputDefinition(outputDefinition);
expression.setObjectResolver(scriptExpressionfactory.getObjectResolver());
expression.setFunctions(scriptExpressionfactory.getFunctions());
expression.setFunctions(new ArrayList<>(scriptExpressionfactory.getFunctions()));
return expression;
}

Expand Down
Expand Up @@ -89,7 +89,7 @@ public void setupFactory() {
Protector protector = new ProtectorImpl();
Collection<FunctionLibrary> functions = new ArrayList<FunctionLibrary>();
functions.add(FunctionLibraryUtil.createBasicFunctionLibrary(prismContext, protector));
scriptExpressionfactory = new ScriptExpressionFactory(prismContext, protector);
scriptExpressionfactory = new ScriptExpressionFactory(prismContext, protector, null);
scriptExpressionfactory.setObjectResolver(resolver);
scriptExpressionfactory.setFunctions(functions);
evaluator = new Jsr223ScriptEvaluator("groovy", prismContext, protector, LocalizationTestUtil.getLocalizationService());
Expand Down Expand Up @@ -171,7 +171,7 @@ private ScriptExpression createScriptExpression(ScriptExpressionEvaluatorType ex
ScriptExpression expression = new ScriptExpression(scriptExpressionfactory.getEvaluators().get(expressionType.getLanguage()), expressionType);
expression.setOutputDefinition(outputDefinition);
expression.setObjectResolver(scriptExpressionfactory.getObjectResolver());
expression.setFunctions(scriptExpressionfactory.getFunctions());
expression.setFunctions(new ArrayList<>(scriptExpressionfactory.getFunctions()));
return expression;
}

Expand Down
1 change: 1 addition & 0 deletions model/model-impl/src/main/resources/ctx-model.xml
Expand Up @@ -114,6 +114,7 @@
scope="singleton">
<constructor-arg name="prismContext" ref="prismContext"/>
<constructor-arg name="protector" ref="protector"/>
<constructor-arg name="repositoryService" ref="repositoryService"/>
<property name="functions">
<list>
<ref bean="basicFunctionLibrary"/>
Expand Down
Expand Up @@ -42,7 +42,7 @@ public class ExpressionFactory {

private Map<QName,ExpressionEvaluatorFactory> evaluatorFactoriesMap = new HashMap<QName, ExpressionEvaluatorFactory>();
private ExpressionEvaluatorFactory defaultEvaluatorFactory;
private Map<ExpressionIdentifier, Expression<?,?>> cache = new HashMap<ExpressionIdentifier, Expression<?,?>>();
private Map<ExpressionIdentifier, Expression<?,?>> cache = new HashMap<>();
final private PrismContext prismContext;
private ObjectResolver objectResolver; // using setter to allow Spring to handle circular references
final private SecurityContextManager securityContextManager;
Expand All @@ -68,7 +68,7 @@ public LocalizationService getLocalizationService() {
}

public <V extends PrismValue,D extends ItemDefinition> Expression<V,D> makeExpression(ExpressionType expressionType,
D outputDefinition, String shortDesc, Task task, OperationResult result)
D outputDefinition, String shortDesc, Task task, OperationResult result)
throws SchemaException, ObjectNotFoundException {
ExpressionIdentifier eid = new ExpressionIdentifier(expressionType, outputDefinition);
Expression<V,D> expression = (Expression<V,D>) cache.get(eid);
Expand All @@ -80,9 +80,9 @@ public <V extends PrismValue,D extends ItemDefinition> Expression<V,D> makeExpre
}

private <V extends PrismValue,D extends ItemDefinition> Expression<V,D> createExpression(ExpressionType expressionType,
D outputDefinition, String shortDesc, Task task, OperationResult result)
D outputDefinition, String shortDesc, Task task, OperationResult result)
throws SchemaException, ObjectNotFoundException {
Expression<V,D> expression = new Expression<V,D>(expressionType, outputDefinition, objectResolver, securityContextManager, prismContext);
Expression<V,D> expression = new Expression<>(expressionType, outputDefinition, objectResolver, securityContextManager, prismContext);
expression.parse(this, shortDesc, task, result);
return expression;
}
Expand Down

0 comments on commit c105f0c

Please sign in to comment.