From cd4e4d2a84a4e2b86d0a49c582a5924fd1c4f8df Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Sat, 9 Jul 2016 16:26:02 +0200 Subject: [PATCH] Velocity script executor --- .../prism/util/JavaTypeConverter.java | 40 +- model/model-common/pom.xml | 4 + .../common/expression/ExpressionUtil.java | 107 +++++- .../script/jsr223/Jsr223ScriptEvaluator.java | 126 +------ .../velocity/VelocityScriptEvaluator.java | 147 ++++++++ .../script/TestVelocityExpressions.java | 346 ++++++++++++++++++ .../velocity/expression-func-concatname.xml | 21 ++ .../expression/velocity/expression-func.xml | 21 ++ .../expression/velocity/expression-list.xml | 21 ++ ...ression-objectref-variables-polystring.xml | 21 ++ .../expression-objectref-variables.xml | 21 ++ .../expression-polystring-equals-1.xml | 23 ++ .../expression-polystring-equals-2.xml | 23 ++ ...pression-polystring-equals-stringify-1.xml | 21 ++ ...pression-polystring-equals-stringify-2.xml | 21 ++ .../expression/velocity/expression-simple.xml | 21 ++ .../velocity/expression-string-variables.xml | 21 ++ .../expression-user-extension-ship-path.xml | 22 ++ .../expression-user-extension-ship.xml | 21 ++ .../velocity/expression-user-given-name.xml | 21 ++ .../expression-user-stringify-full-name.xml | 21 ++ model/model-common/testng-unit.xml | 1 + .../SynchronizationServiceRegisterAgent.java | 2 +- .../src/main/resources/ctx-model.xml | 9 +- 24 files changed, 946 insertions(+), 156 deletions(-) create mode 100644 model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/velocity/VelocityScriptEvaluator.java create mode 100644 model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/TestVelocityExpressions.java create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-func-concatname.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-func.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-list.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-objectref-variables-polystring.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-objectref-variables.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-1.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-2.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-stringify-1.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-stringify-2.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-simple.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-string-variables.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-user-extension-ship-path.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-user-extension-ship.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-user-given-name.xml create mode 100644 model/model-common/src/test/resources/expression/velocity/expression-user-stringify-full-name.xml diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/JavaTypeConverter.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/JavaTypeConverter.java index 32311fac3d0..a355c0083e7 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/JavaTypeConverter.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/JavaTypeConverter.java @@ -79,16 +79,16 @@ public static T convert(Class expectedType, Object rawValue) { return (T) ((Boolean)rawValue); } if (expectedType == Boolean.class && rawValue instanceof String) { - return (T) (Boolean)Boolean.parseBoolean(((String)rawValue)); + return (T) (Boolean)Boolean.parseBoolean((((String)rawValue)).trim()); } if (expectedType == Boolean.class && rawValue instanceof PolyString) { - return (T) (Boolean)Boolean.parseBoolean(((PolyString)rawValue).toString()); + return (T) (Boolean)Boolean.parseBoolean((rawValue.toString().trim())); } if (expectedType == boolean.class && rawValue instanceof String) { - return (T) (Boolean)Boolean.parseBoolean(((String)rawValue)); + return (T) (Boolean)Boolean.parseBoolean(((String)rawValue).trim()); } if (expectedType == boolean.class && rawValue instanceof PolyString) { - return (T) (Boolean)Boolean.parseBoolean(((PolyString)rawValue).toString()); + return (T) (Boolean)Boolean.parseBoolean((rawValue).toString().trim()); } if (expectedType == String.class && rawValue instanceof Boolean) { return (T) rawValue.toString(); @@ -99,10 +99,10 @@ public static T convert(Class expectedType, Object rawValue) { return (T)((Integer)rawValue); } if (expectedType == Integer.class && rawValue instanceof String) { - return (T) (Integer)Integer.parseInt(((String)rawValue)); + return (T) (Integer)Integer.parseInt(((String)rawValue).trim()); } if (expectedType == int.class && rawValue instanceof String) { - return (T) (Integer)Integer.parseInt(((String)rawValue)); + return (T) (Integer)Integer.parseInt(((String)rawValue).trim()); } if (expectedType == String.class && rawValue instanceof Integer) { return (T) rawValue.toString(); @@ -112,13 +112,13 @@ public static T convert(Class expectedType, Object rawValue) { } if (expectedType == long.class && rawValue instanceof Long) { - return (T)((Long)rawValue); + return (T)(rawValue); } if (expectedType == Long.class && rawValue instanceof String) { - return (T) (Long)Long.parseLong(((String)rawValue)); + return (T) (Long)Long.parseLong(((String)rawValue).trim()); } if (expectedType == long.class && rawValue instanceof String) { - return (T) (Long)Long.parseLong(((String)rawValue)); + return (T) (Long)Long.parseLong(((String)rawValue).trim()); } if (expectedType == String.class && rawValue instanceof Long) { return (T) rawValue.toString(); @@ -128,10 +128,10 @@ public static T convert(Class expectedType, Object rawValue) { return (T)((Float)rawValue); } if (expectedType == Float.class && rawValue instanceof String) { - return (T) (Float)Float.parseFloat(((String)rawValue)); + return (T) (Float)Float.parseFloat(((String)rawValue).trim()); } if (expectedType == float.class && rawValue instanceof String) { - return (T) (Float)Float.parseFloat(((String)rawValue)); + return (T) (Float)Float.parseFloat(((String)rawValue).trim()); } if (expectedType == String.class && rawValue instanceof Float) { return (T) rawValue.toString(); @@ -141,10 +141,10 @@ public static T convert(Class expectedType, Object rawValue) { return (T)((Double)rawValue); } if (expectedType == Double.class && rawValue instanceof String) { - return (T) (Double)Double.parseDouble(((String)rawValue)); + return (T) (Double)Double.parseDouble(((String)rawValue).trim()); } if (expectedType == double.class && rawValue instanceof String) { - return (T) (Double)Double.parseDouble(((String)rawValue)); + return (T) (Double)Double.parseDouble(((String)rawValue).trim()); } if (expectedType == String.class && rawValue instanceof Float) { return (T) rawValue.toString(); @@ -164,17 +164,17 @@ public static T convert(Class expectedType, Object rawValue) { } if (expectedType == BigInteger.class && rawValue instanceof String) { - return (T) new BigInteger(((String)rawValue)); + return (T) new BigInteger(((String)rawValue).trim()); } if (expectedType == String.class && rawValue instanceof BigInteger) { - return (T) ((BigInteger)rawValue).toString(); + return (T) rawValue.toString().trim(); } if (expectedType == BigDecimal.class && rawValue instanceof String) { - return (T) new BigDecimal(((String)rawValue)); + return (T) new BigDecimal(((String)rawValue).trim()); } if (expectedType == String.class && rawValue instanceof BigDecimal) { - return (T) ((BigDecimal)rawValue).toString(); + return (T) ((BigDecimal)rawValue).toString().trim(); } if (expectedType == PolyString.class && rawValue instanceof String) { @@ -224,7 +224,7 @@ public static T convert(Class expectedType, Object rawValue) { // XML Enums (JAXB) if (expectedType.isEnum() && expectedType.getAnnotation(XmlEnum.class) != null && rawValue instanceof String) { - return XmlTypeConverter.toXmlEnum(expectedType, (String)rawValue); + return XmlTypeConverter.toXmlEnum(expectedType, ((String)rawValue).trim()); } if (expectedType == String.class && rawValue.getClass().isEnum() && rawValue.getClass().getAnnotation(XmlEnum.class) != null) { return (T) XmlTypeConverter.fromXmlEnum(rawValue); @@ -232,7 +232,7 @@ public static T convert(Class expectedType, Object rawValue) { // Java Enums if (expectedType.isEnum() && rawValue instanceof String) { - return (T) Enum.valueOf((Class)expectedType, (String)rawValue); + return (T) Enum.valueOf((Class)expectedType, ((String)rawValue).trim()); } if (expectedType == String.class && rawValue.getClass().isEnum()) { return (T) rawValue.toString(); @@ -243,7 +243,7 @@ public static T convert(Class expectedType, Object rawValue) { return (T) rawValue; } if (expectedType == QName.class && rawValue instanceof String){ - return (T) QNameUtil.uriToQName((String)rawValue); + return (T) QNameUtil.uriToQName(((String)rawValue).trim()); } throw new IllegalArgumentException("Expected "+expectedType+" type, but got "+rawValue.getClass()); diff --git a/model/model-common/pom.xml b/model/model-common/pom.xml index a3291e49dc7..9c41a35250d 100644 --- a/model/model-common/pom.xml +++ b/model/model-common/pom.xml @@ -112,6 +112,10 @@ org.codehaus.groovy groovy-all + + org.apache.velocity + velocity + org.python jython diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java index 93fa3abbefa..c20c8b117e1 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java @@ -22,27 +22,14 @@ import java.util.Map; import java.util.Map.Entry; +import javax.script.Bindings; import javax.xml.namespace.QName; import com.evolveum.midpoint.model.common.expression.functions.BasicExpressionFunctions; import com.evolveum.midpoint.model.common.expression.functions.BasicExpressionFunctionsXPath; import com.evolveum.midpoint.model.common.expression.functions.FunctionLibrary; import com.evolveum.midpoint.model.common.expression.functions.LogExpressionFunctions; -import com.evolveum.midpoint.prism.Item; -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.Objectable; -import com.evolveum.midpoint.prism.PrismContainer; -import com.evolveum.midpoint.prism.PrismContainerDefinition; -import com.evolveum.midpoint.prism.PrismContainerValue; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismObjectDefinition; -import com.evolveum.midpoint.prism.PrismPropertyDefinition; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.prism.PrismValue; -import com.evolveum.midpoint.prism.Structured; -import com.evolveum.midpoint.prism.Visitable; -import com.evolveum.midpoint.prism.Visitor; +import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.crypto.EncryptionException; import com.evolveum.midpoint.prism.crypto.Protector; import com.evolveum.midpoint.prism.delta.ItemDelta; @@ -51,6 +38,7 @@ import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.path.ItemPathSegment; import com.evolveum.midpoint.prism.path.NameItemPathSegment; +import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.query.AllFilter; import com.evolveum.midpoint.prism.query.ExpressionWrapper; import com.evolveum.midpoint.prism.query.InOidFilter; @@ -243,6 +231,67 @@ public static Object resolvePath(ItemPath path, ExpressionVariables variables, O } } + public static Object convertVariableValue(Object originalValue, String variableName, ObjectResolver objectResolver, + String contextDescription, Task task, OperationResult result) throws ExpressionSyntaxException, ObjectNotFoundException { + if (originalValue instanceof ObjectReferenceType) { + try { + originalValue = resolveReference((ObjectReferenceType)originalValue, objectResolver, variableName, + contextDescription, task, result); + } catch (SchemaException e) { + throw new ExpressionSyntaxException("Schema error during variable "+variableName+" resolution in "+contextDescription+": "+e.getMessage(), e); + } + } + if (originalValue instanceof PrismObject) { + return ((PrismObject)originalValue).asObjectable(); + } + if (originalValue instanceof PrismContainerValue) { + return ((PrismContainerValue)originalValue).asContainerable(); + } + if (originalValue instanceof PrismPropertyValue) { + return ((PrismPropertyValue)originalValue).getValue(); + } + if (originalValue instanceof PrismProperty) { + PrismProperty prop = (PrismProperty)originalValue; + PrismPropertyDefinition def = prop.getDefinition(); + if (def != null) { + if (def.isSingleValue()) { + return prop.getRealValue(); + } else { + return prop.getRealValues(); + } + } else { + return prop.getValues(); + } + } + return originalValue; + } + + public static Map prepareScriptVariables(ExpressionVariables variables, ObjectResolver objectResolver, + Collection functions, + String contextDescription, Task task, OperationResult result) throws ExpressionSyntaxException, ObjectNotFoundException { + Map scriptVariables = new HashMap<>(); + // Functions + if (functions != null) { + for (FunctionLibrary funcLib: functions) { + scriptVariables.put(funcLib.getVariableName(), funcLib.getGenericFunctions()); + } + } + // Variables + if (variables != null) { + for (Entry variableEntry: variables.entrySet()) { + if (variableEntry.getKey() == null) { + // This is the "root" node. We have no use for it in JSR223, just skip it + continue; + } + String variableName = variableEntry.getKey().getLocalPart(); + Object variableValue = ExpressionUtil.convertVariableValue(variableEntry.getValue(), variableName, objectResolver, contextDescription, task, result); + scriptVariables.put(variableName, variableValue); + } + } + return scriptVariables; + } + + private static PrismObject resolveReference(ObjectReferenceType ref, ObjectResolver objectResolver, String varDesc, String contextDescription, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException { @@ -813,4 +862,32 @@ public static Object convertToOutputValue(String stri } } + public static boolean isEmpty(T val) { + if (val == null) { + return true; + } + if (val instanceof String && ((String)val).isEmpty()) { + return true; + } + if (val instanceof PolyString && ((PolyString)val).isEmpty()) { + return true; + } + return false; + } + + public static V convertToPrismValue(T value, ItemDefinition definition, String contextDescription, PrismContext prismContext) throws ExpressionEvaluationException { + if (definition instanceof PrismReferenceDefinition) { + return (V) ((ObjectReferenceType) value).asReferenceValue(); + } else if (definition instanceof PrismContainerDefinition) { + try { + prismContext.adopt((Containerable) value); + ((Containerable) value).asPrismContainerValue().applyDefinition(definition); + } catch (SchemaException e) { + throw new ExpressionEvaluationException(e.getMessage() + " " + contextDescription, e); + } + return (V) ((Containerable) value).asPrismContainerValue(); + } else { + return (V) new PrismPropertyValue<>(value); + } + } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/jsr223/Jsr223ScriptEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/jsr223/Jsr223ScriptEvaluator.java index f49aa517a6e..c82a31bc354 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/jsr223/Jsr223ScriptEvaluator.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/jsr223/Jsr223ScriptEvaluator.java @@ -139,59 +139,25 @@ public List evaluate(ScriptExpressionEvaluatorType List pvals = new ArrayList(); - // TODO: cleanup copy&paste block..and what about PrismContianer and + // 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) { + for (Object evalRawResultElement : (Collection)evalRawResult) { T evalResult = convertScalarResult(javaReturnType, evalRawResultElement, contextDescription); - V pval = null; - if (allowEmptyValues || !isEmpty(evalResult)) { - if (outputDefinition instanceof PrismReferenceDefinition){ - pval = (V) ((ObjectReferenceType)evalResult).asReferenceValue(); - } else if (outputDefinition instanceof PrismContainerDefinition){ - try { - prismContext.adopt((Containerable)evalResult); - ((Containerable)evalResult).asPrismContainerValue().applyDefinition(outputDefinition); - } catch (SchemaException e) { - throw new ExpressionEvaluationException(e.getMessage() + " " + contextDescription, e); - } - pval = (V) ((Containerable)evalResult).asPrismContainerValue(); - } else { - pval = (V) new PrismPropertyValue(evalResult); - } - pvals.add(pval); + if (allowEmptyValues || !ExpressionUtil.isEmpty(evalResult)) { + pvals.add((V) ExpressionUtil.convertToPrismValue(evalResult, outputDefinition, contextDescription, prismContext)); } } } else if (evalRawResult instanceof PrismProperty) { pvals.addAll((Collection) PrismPropertyValue.cloneCollection(((PrismProperty)evalRawResult).getValues())); } else { T evalResult = convertScalarResult(javaReturnType, evalRawResult, contextDescription); - V pval = null; - if (allowEmptyValues || !isEmpty(evalResult)) { - if (outputDefinition instanceof PrismReferenceDefinition){ - pval = (V) ((ObjectReferenceType)evalResult).asReferenceValue(); - } else if (outputDefinition instanceof PrismContainerDefinition){ - try { - prismContext.adopt((Containerable)evalResult); - ((Containerable)evalResult).asPrismContainerValue().applyDefinition(outputDefinition); - } catch (SchemaException e) { - throw new ExpressionEvaluationException(e.getMessage() + " " + contextDescription, e); - } - - pval = (V) ((Containerable)evalResult).asPrismContainerValue(); - } else { - pval = (V) new PrismPropertyValue(evalResult); - } - pvals.add(pval); + if (allowEmptyValues || !ExpressionUtil.isEmpty(evalResult)) { + pvals.add((V) ExpressionUtil.convertToPrismValue(evalResult, outputDefinition, contextDescription, prismContext)); } } -// ScriptExpressionReturnTypeType definedReturnType = expressionType.getReturnType(); -// if (definedReturnType == ScriptExpressionReturnTypeType.LIST) { -// -// } - return pvals; } @@ -250,92 +216,14 @@ private T convertScalarResult(Class expectedType, Object rawValue, String } } - private boolean isEmpty(T val) { - if (val == null) { - return true; - } - if (val instanceof String && ((String)val).isEmpty()) { - return true; - } - if (val instanceof PolyString && ((PolyString)val).isEmpty()) { - return true; - } - return false; - } - private Bindings convertToBindings(ExpressionVariables variables, ObjectResolver objectResolver, Collection functions, String contextDescription, Task task, OperationResult result) throws ExpressionSyntaxException, ObjectNotFoundException { Bindings bindings = scriptEngine.createBindings(); - // Functions - if (functions != null) { - for (FunctionLibrary funcLib: functions) { - bindings.put(funcLib.getVariableName(), funcLib.getGenericFunctions()); - } - } - // Variables - if (variables != null) { - for (Entry variableEntry: variables.entrySet()) { - if (variableEntry.getKey() == null) { - // This is the "root" node. We have no use for it in JSR223, just skip it - continue; - } - String variableName = variableEntry.getKey().getLocalPart(); - Object variableValue = convertVariableValue(variableEntry.getValue(), variableName, objectResolver, contextDescription, task, result); - bindings.put(variableName, variableValue); - } - } + bindings.putAll(ExpressionUtil.prepareScriptVariables(variables, objectResolver, functions, contextDescription, task, result)); return bindings; } - private Object convertVariableValue(Object originalValue, String variableName, ObjectResolver objectResolver, - String contextDescription, Task task, OperationResult result) throws ExpressionSyntaxException, ObjectNotFoundException { - if (originalValue instanceof ObjectReferenceType) { - originalValue = resolveReference((ObjectReferenceType)originalValue, objectResolver, variableName, - contextDescription, task, result); - } - if (originalValue instanceof PrismObject) { - return ((PrismObject)originalValue).asObjectable(); - } - if (originalValue instanceof PrismContainerValue) { - return ((PrismContainerValue)originalValue).asContainerable(); - } - if (originalValue instanceof PrismPropertyValue) { - return ((PrismPropertyValue)originalValue).getValue(); - } - if (originalValue instanceof PrismProperty) { - PrismProperty prop = (PrismProperty)originalValue; - PrismPropertyDefinition def = prop.getDefinition(); - if (def != null) { - if (def.isSingleValue()) { - return prop.getRealValue(); - } else { - return prop.getRealValues(); - } - } else { - return prop.getValues(); - } - } - return originalValue; - } - - private Object resolveReference(ObjectReferenceType ref, ObjectResolver objectResolver, String name, String contextDescription, - Task task, OperationResult result) throws ExpressionSyntaxException, ObjectNotFoundException { - if (ref.getOid() == null) { - throw new ExpressionSyntaxException("Null OID in reference in variable "+name+" in "+contextDescription); - } else { - try { - - return objectResolver.resolve(ref, ObjectType.class, null, contextDescription, task, result); - - } catch (ObjectNotFoundException e) { - throw new ObjectNotFoundException("Object not found during variable "+name+" resolution in "+contextDescription+": "+e.getMessage(),e, ref.getOid()); - } catch (SchemaException e) { - throw new ExpressionSyntaxException("Schema error during variable "+name+" resolution in "+contextDescription+": "+e.getMessage(), e); - } - } - } - /* (non-Javadoc) * @see com.evolveum.midpoint.common.expression.ExpressionEvaluator#getLanguageName() */ diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/velocity/VelocityScriptEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/velocity/VelocityScriptEvaluator.java new file mode 100644 index 00000000000..52e1237b701 --- /dev/null +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/velocity/VelocityScriptEvaluator.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2010-2016 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.velocity; + +import com.evolveum.midpoint.common.monitor.InternalMonitor; +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.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.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.xml.ns._public.common.common_3.ScriptExpressionEvaluatorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionReturnTypeType; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; + +import javax.xml.namespace.QName; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Expression evaluator that is using Apache Velocity engine. + * + * @author mederly + * + */ +public class VelocityScriptEvaluator implements ScriptEvaluator { + + private static final String LANGUAGE_URL_BASE = MidPointConstants.NS_MIDPOINT_PUBLIC_PREFIX + "/expression/language#"; + + private PrismContext prismContext; + private Protector protector; + + public VelocityScriptEvaluator(PrismContext prismContext, Protector protector) { + this.prismContext = prismContext; + this.protector = protector; + Velocity.init(); + } + + @Override + public List evaluate(ScriptExpressionEvaluatorType expressionType, + ExpressionVariables variables, ItemDefinition outputDefinition, ScriptExpressionReturnTypeType suggestedReturnType, + ObjectResolver objectResolver, Collection functions, + String contextDescription, Task task, OperationResult result) throws ExpressionEvaluationException, + ObjectNotFoundException, ExpressionSyntaxException { + + VelocityContext context = createVelocityContext(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(); + } + + StringWriter resultWriter = new StringWriter(); + try { + InternalMonitor.recordScriptExecution(); + Velocity.evaluate(context, resultWriter, "", codeString); + } catch (RuntimeException 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 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; + } + + T evalResult; + try { + evalResult = ExpressionUtil.convertValue(javaReturnType, resultWriter.toString(), protector, prismContext); + } catch (IllegalArgumentException e) { + throw new ExpressionEvaluationException(e.getMessage()+" in "+contextDescription, e); + } + + List pvals = new ArrayList(); + if (allowEmptyValues || !ExpressionUtil.isEmpty(evalResult)) { + pvals.add((V) ExpressionUtil.convertToPrismValue(evalResult, outputDefinition, contextDescription, prismContext)); + } + return pvals; + } + + private VelocityContext createVelocityContext(ExpressionVariables variables, ObjectResolver objectResolver, + Collection functions, + String contextDescription, Task task, OperationResult result) throws ExpressionSyntaxException, ObjectNotFoundException { + VelocityContext context = new VelocityContext(); + Map scriptVariables = ExpressionUtil.prepareScriptVariables(variables, objectResolver, functions, contextDescription, task, result); + for (Map.Entry scriptVariable : scriptVariables.entrySet()) { + context.put(scriptVariable.getKey(), scriptVariable.getValue()); + } + return context; + } + + + @Override + public String getLanguageName() { + return "velocity"; + } + + @Override + public String getLanguageUrl() { + return LANGUAGE_URL_BASE + getLanguageName(); + } + +} diff --git a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/TestVelocityExpressions.java b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/TestVelocityExpressions.java new file mode 100644 index 00000000000..54e640056ce --- /dev/null +++ b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/TestVelocityExpressions.java @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2010-2016 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 com.evolveum.midpoint.model.common.expression.ExpressionVariables; +import com.evolveum.midpoint.model.common.expression.script.velocity.VelocityScriptEvaluator; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.crypto.Protector; +import com.evolveum.midpoint.prism.util.PrismTestUtil; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import org.testng.annotations.Test; + +import javax.xml.namespace.QName; +import java.io.File; + +/** + * @author Radovan Semancik + * @author Pavol Mederly + */ +public class TestVelocityExpressions extends AbstractScriptTest { + + @Override + protected ScriptEvaluator createEvaluator(PrismContext prismContext, Protector protector) { + return new VelocityScriptEvaluator(prismContext, protector); + } + + @Override + protected File getTestDir() { + return new File(BASE_TEST_DIR, "velocity"); + } + + @Test + public void testExpressionList() throws Exception { + evaluateAndAssertStringScalarExpresssion( // velocity has no support for output other than String + "expression-list.xml", + "testExpressionList", + ExpressionVariables.create( + new QName(NS_Y, "jack"), + MiscSchemaUtil.createObjectReference(USER_OID, UserType.COMPLEX_TYPE) + ), + "[Leaders, Followers]"); + } + + @Test + public void testExpressionPolyStringEquals101() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-1.xml", + "testExpressionPolyStringEquals101", + ExpressionVariables.create( + new QName(NS_X, "foo"), "FOO", + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.TRUE); + } + + @Test + public void testExpressionPolyStringEquals102() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-1.xml", + "testExpressionPolyStringEquals102", + ExpressionVariables.create( + new QName(NS_X, "foo"), "FOOBAR", + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.FALSE); + } + + @Test + public void testExpressionPolyStringEquals111() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-1.xml", + "testExpressionPolyStringEquals111", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyString("FOO"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.TRUE); // velocity calls '==' on toString value + } + + @Test + public void testExpressionPolyStringEquals112() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-1.xml", + "testExpressionPolyStringEquals112", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyString("FOOBAR"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.FALSE); + } + + @Test + public void testExpressionPolyStringEquals121() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-1.xml", + "testExpressionPolyStringEquals121", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyStringType("FOO"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.TRUE); // velocity calls '==' on toString value + } + + @Test + public void testExpressionPolyStringEquals122() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-1.xml", + "testExpressionPolyStringEquals122", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyStringType("FOOBAR"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.FALSE); + } + + @Test + public void testExpressionPolyStringEquals201() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-2.xml", + "testExpressionPolyStringEquals201", + ExpressionVariables.create( + new QName(NS_X, "foo"), "FOO", + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.TRUE); + } + + @Test + public void testExpressionPolyStringEquals202() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-2.xml", + "testExpressionPolyStringEquals202", + ExpressionVariables.create( + new QName(NS_X, "foo"), "FOOBAR", + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.FALSE); + } + + @Test + public void testExpressionPolyStringEquals211() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-2.xml", + "testExpressionPolyStringEquals211", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyString("FOO"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.TRUE); // velocity calls '==' on toString value + } + + @Test + public void testExpressionPolyStringEquals212() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-2.xml", + "testExpressionPolyStringEquals212", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyString("FOOBAR"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.FALSE); + } + + @Test + public void testExpressionPolyStringEquals221() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-2.xml", + "testExpressionPolyStringEquals221", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyStringType("FOO"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.TRUE); // velocity calls '==' on toString value + } + + @Test + public void testExpressionPolyStringEquals222() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-2.xml", + "testExpressionPolyStringEquals222", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyStringType("FOOBAR"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.FALSE); + } + + @Test + public void testExpressionPolyStringEqualsStringify101() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-stringify-1.xml", + "testExpressionPolyStringEqualsStringify101", + ExpressionVariables.create( + new QName(NS_X, "foo"), "FOO", + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.TRUE); + } + + @Test + public void testExpressionPolyStringEqualsStringify102() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-stringify-1.xml", + "testExpressionPolyStringEqualsStringify102", + ExpressionVariables.create( + new QName(NS_X, "foo"), "FOOBAR", + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.FALSE); + } + + @Test + public void testExpressionPolyStringEqualsStringify111() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-stringify-1.xml", + "testExpressionPolyStringEqualsStringify111", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyString("FOO"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.TRUE); + } + + @Test + public void testExpressionPolyStringEqualsStringify112() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-stringify-1.xml", + "testExpressionPolyStringEqualsStringify112", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyString("FOOBAR"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.FALSE); + } + + @Test + public void testExpressionPolyStringEqualsStringify121() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-stringify-1.xml", + "testExpressionPolyStringEqualsStringify121", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyStringType("FOO"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.TRUE); + } + + @Test + public void testExpressionPolyStringEqualsStringify122() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-stringify-1.xml", + "testExpressionPolyStringEqualsStringify122", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyStringType("FOOBAR"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.FALSE); + } + + @Test + public void testExpressionPolyStringEqualsStringify201() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-stringify-2.xml", + "testExpressionPolyStringEqualsStringify201", + ExpressionVariables.create( + new QName(NS_X, "foo"), "FOO", + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.TRUE); + } + + @Test + public void testExpressionPolyStringEqualsStringify202() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-stringify-2.xml", + "testExpressionPolyStringEqualsStringify202", + ExpressionVariables.create( + new QName(NS_X, "foo"), "FOOBAR", + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.FALSE); + } + + @Test + public void testExpressionPolyStringEqualsStringify211() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-stringify-2.xml", + "testExpressionPolyStringEqualsStringify211", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyString("FOO"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.TRUE); + } + + @Test + public void testExpressionPolyStringEqualsStringify212() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-stringify-2.xml", + "testExpressionPolyStringEqualsStringify212", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyString("FOOBAR"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.FALSE); + } + + @Test + public void testExpressionPolyStringEqualsStringify221() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-stringify-2.xml", + "testExpressionPolyStringEqualsStringify221", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyStringType("FOO"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.TRUE); + } + + @Test + public void testExpressionPolyStringEqualsStringify222() throws Exception { + evaluateAndAssertBooleanScalarExpresssion( + "expression-polystring-equals-stringify-2.xml", + "testExpressionPolyStringEqualsStringify222", + ExpressionVariables.create( + new QName(NS_X, "foo"), PrismTestUtil.createPolyStringType("FOOBAR"), + new QName(NS_Y, "bar"), "BAR" + ), + Boolean.FALSE); + } + +} diff --git a/model/model-common/src/test/resources/expression/velocity/expression-func-concatname.xml b/model/model-common/src/test/resources/expression/velocity/expression-func-concatname.xml new file mode 100644 index 00000000000..78433308adc --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-func-concatname.xml @@ -0,0 +1,21 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-func.xml b/model/model-common/src/test/resources/expression/velocity/expression-func.xml new file mode 100644 index 00000000000..4b4033e10c3 --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-func.xml @@ -0,0 +1,21 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-list.xml b/model/model-common/src/test/resources/expression/velocity/expression-list.xml new file mode 100644 index 00000000000..8fcac682ded --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-list.xml @@ -0,0 +1,21 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-objectref-variables-polystring.xml b/model/model-common/src/test/resources/expression/velocity/expression-objectref-variables-polystring.xml new file mode 100644 index 00000000000..27debac5906 --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-objectref-variables-polystring.xml @@ -0,0 +1,21 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-objectref-variables.xml b/model/model-common/src/test/resources/expression/velocity/expression-objectref-variables.xml new file mode 100644 index 00000000000..523460a16d5 --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-objectref-variables.xml @@ -0,0 +1,21 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-1.xml b/model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-1.xml new file mode 100644 index 00000000000..996e475a5c3 --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-1.xml @@ -0,0 +1,23 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-2.xml b/model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-2.xml new file mode 100644 index 00000000000..3652b4b903a --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-2.xml @@ -0,0 +1,23 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-stringify-1.xml b/model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-stringify-1.xml new file mode 100644 index 00000000000..99d5ac576f8 --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-stringify-1.xml @@ -0,0 +1,21 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-stringify-2.xml b/model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-stringify-2.xml new file mode 100644 index 00000000000..666f364f87f --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-polystring-equals-stringify-2.xml @@ -0,0 +1,21 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-simple.xml b/model/model-common/src/test/resources/expression/velocity/expression-simple.xml new file mode 100644 index 00000000000..a8748badf8e --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-simple.xml @@ -0,0 +1,21 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-string-variables.xml b/model/model-common/src/test/resources/expression/velocity/expression-string-variables.xml new file mode 100644 index 00000000000..594ce1f3441 --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-string-variables.xml @@ -0,0 +1,21 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-user-extension-ship-path.xml b/model/model-common/src/test/resources/expression/velocity/expression-user-extension-ship-path.xml new file mode 100644 index 00000000000..b2993c69498 --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-user-extension-ship-path.xml @@ -0,0 +1,22 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-user-extension-ship.xml b/model/model-common/src/test/resources/expression/velocity/expression-user-extension-ship.xml new file mode 100644 index 00000000000..d706258cb2b --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-user-extension-ship.xml @@ -0,0 +1,21 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-user-given-name.xml b/model/model-common/src/test/resources/expression/velocity/expression-user-given-name.xml new file mode 100644 index 00000000000..82bf49f902e --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-user-given-name.xml @@ -0,0 +1,21 @@ + + + + diff --git a/model/model-common/src/test/resources/expression/velocity/expression-user-stringify-full-name.xml b/model/model-common/src/test/resources/expression/velocity/expression-user-stringify-full-name.xml new file mode 100644 index 00000000000..90cc8d34433 --- /dev/null +++ b/model/model-common/src/test/resources/expression/velocity/expression-user-stringify-full-name.xml @@ -0,0 +1,21 @@ + + + + diff --git a/model/model-common/testng-unit.xml b/model/model-common/testng-unit.xml index cf714fd4764..1963e71a012 100644 --- a/model/model-common/testng-unit.xml +++ b/model/model-common/testng-unit.xml @@ -28,6 +28,7 @@ + diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceRegisterAgent.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceRegisterAgent.java index 873233bdb5e..3ff68110cae 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceRegisterAgent.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceRegisterAgent.java @@ -25,7 +25,7 @@ /** * Helper class that registers SynchronizationService as a provisioning change notification listener. - * It can no longer be done in SynchronizationServiceImpl itself, becase we don't want to register the implementation, but its proxy + * It can no longer be done in SynchronizationServiceImpl itself, because we don't want to register the implementation, but its proxy * generated by Spring AOP. * * @author mederly diff --git a/model/model-impl/src/main/resources/ctx-model.xml b/model/model-impl/src/main/resources/ctx-model.xml index 79d25796df5..ae69785dc02 100644 --- a/model/model-impl/src/main/resources/ctx-model.xml +++ b/model/model-impl/src/main/resources/ctx-model.xml @@ -105,7 +105,13 @@ - + + + + + + @@ -124,6 +130,7 @@ +