/*
* 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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.Validate;
import org.w3c.dom.Element;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.schema.internals.InternalsConfig;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectResolver;
import com.evolveum.midpoint.security.api.SecurityEnforcer;
import com.evolveum.midpoint.util.MiscUtil;
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.SystemException;
import com.evolveum.midpoint.util.exception.TunnelException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionVariableDefinitionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import com.evolveum.prism.xml.ns._public.types_3.RawType;
/**
* @author semancik
*
*/
public class Expression<V extends PrismValue,D extends ItemDefinition> {
final private ExpressionType expressionType;
final private D outputDefinition;
final private PrismContext prismContext;
final private ObjectResolver objectResolver;
final private SecurityEnforcer securityEnforcer;
private List<ExpressionEvaluator<V,D>> evaluators = new ArrayList<>(1);
private static final Trace LOGGER = TraceManager.getTrace(Expression.class);
public Expression(ExpressionType expressionType, D outputDefinition, ObjectResolver objectResolver, SecurityEnforcer securityEnforcer, PrismContext prismContext) {
//Validate.notNull(outputDefinition, "null outputDefinition");
Validate.notNull(objectResolver, "null objectResolver");
Validate.notNull(prismContext, "null prismContext");
this.expressionType = expressionType;
this.outputDefinition = outputDefinition;
this.objectResolver = objectResolver;
this.prismContext = prismContext;
this.securityEnforcer = securityEnforcer;
}
public void parse(ExpressionFactory factory, String contextDescription, Task task, OperationResult result)
throws SchemaException, ObjectNotFoundException {
if (expressionType == null) {
evaluators.add(createDefaultEvaluator(factory, contextDescription, task, result));
return;
}
if (expressionType.getExpressionEvaluator() == null /* && expressionType.getSequence() == null */) {
throw new SchemaException("No evaluator was specified in "+contextDescription);
}
if (expressionType.getExpressionEvaluator() != null) {
ExpressionEvaluator evaluator = createEvaluator(expressionType.getExpressionEvaluator(), factory,
contextDescription, task, result);
evaluators.add(evaluator);
}
if (evaluators.isEmpty()) {
evaluators.add(createDefaultEvaluator(factory, contextDescription, task, result));
}
}
private ExpressionEvaluator<V,D> createEvaluator(Collection<JAXBElement<?>> evaluatorElements, ExpressionFactory factory,
String contextDescription, Task task, OperationResult result)
throws SchemaException, ObjectNotFoundException {
if (evaluatorElements.isEmpty()) {
throw new SchemaException("Empty evaluator list in "+contextDescription);
}
JAXBElement<?> fistEvaluatorElement = evaluatorElements.iterator().next();
ExpressionEvaluatorFactory evaluatorFactory = factory.getEvaluatorFactory(fistEvaluatorElement.getName());
if (evaluatorFactory == null) {
throw new SchemaException("Unknown expression evaluator element "+fistEvaluatorElement.getName()+" in "+contextDescription);
}
return evaluatorFactory.createEvaluator(evaluatorElements, outputDefinition, contextDescription, task, result);
}
private ExpressionEvaluator<V,D> createDefaultEvaluator(ExpressionFactory factory, String contextDescription,
Task task, OperationResult result) throws SchemaException, ObjectNotFoundException {
ExpressionEvaluatorFactory evaluatorFactory = factory.getDefaultEvaluatorFactory();
if (evaluatorFactory == null) {
throw new SystemException("Internal error: No default expression evaluator factory");
}
return evaluatorFactory.createEvaluator(null, outputDefinition, contextDescription, task, result);
}
public PrismValueDeltaSetTriple<V> evaluate(ExpressionEvaluationContext context) throws SchemaException,
ExpressionEvaluationException, ObjectNotFoundException {
ExpressionVariables processedVariables = null;
try {
processedVariables = processInnerVariables(context.getVariables(), context.getContextDescription(),
context.getTask(), context.getResult());
ExpressionEvaluationContext contextWithProcessedVariables = context.shallowClone();
contextWithProcessedVariables.setVariables(processedVariables);
PrismValueDeltaSetTriple<V> outputTriple;
ObjectReferenceType runAsRef = null;
if (expressionType != null) {
runAsRef = expressionType.getRunAsRef();
}
if (runAsRef == null) {
outputTriple = evaluateExpressionEvaluators(contextWithProcessedVariables);
} else {
UserType userType = objectResolver.resolve(runAsRef, UserType.class, null,
"runAs in "+context.getContextDescription(), context.getTask(), context.getResult());
LOGGER.trace("Running {} as {} ({})", context.getContextDescription(), userType, runAsRef);
try {
outputTriple = securityEnforcer.runAs(() -> {
try {
return evaluateExpressionEvaluators(contextWithProcessedVariables);
} catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException e) {
throw new TunnelException(e);
}
}, userType.asPrismObject());
} catch (TunnelException te) {
Throwable e = te.getCause();
if (e instanceof RuntimeException) {
throw (RuntimeException)e;
}
if (e instanceof Error) {
throw (Error)e;
}
if (e instanceof SchemaException) {
throw (SchemaException)e;
}
if (e instanceof ExpressionEvaluationException) {
throw (ExpressionEvaluationException)e;
}
if (e instanceof ObjectNotFoundException) {
throw (ObjectNotFoundException)e;
}
throw te;
}
}
traceSuccess(context, processedVariables, outputTriple);
return outputTriple;
} catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException | RuntimeException | Error e) {
traceFailure(context, processedVariables, e);
throw e;
}
}
private PrismValueDeltaSetTriple<V> evaluateExpressionEvaluators(ExpressionEvaluationContext contextWithProcessedVariables)
throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException {
for (ExpressionEvaluator<?,?> evaluator: evaluators) {
PrismValueDeltaSetTriple<V> outputTriple = (PrismValueDeltaSetTriple<V>) evaluator.evaluate(contextWithProcessedVariables);
if (outputTriple != null) {
boolean allowEmptyRealValues = false;
if (expressionType != null) {
allowEmptyRealValues = BooleanUtils.isTrue(expressionType.isAllowEmptyValues());
}
outputTriple.removeEmptyValues(allowEmptyRealValues);
if (InternalsConfig.consistencyChecks) {
try {
outputTriple.checkConsistence();
} catch (IllegalStateException e) {
throw new IllegalStateException(e.getMessage() + "; in expression " + this +", evaluator " + evaluator, e);
}
}
return outputTriple;
}
}
return null;
}
private void traceSuccess(ExpressionEvaluationContext context, ExpressionVariables processedVariables, PrismValueDeltaSetTriple<V> outputTriple) {
if (!isTrace()) {
return;
}
StringBuilder sb = new StringBuilder();
sb.append("Expression trace:\n");
appendTraceHeader(sb, context, processedVariables);
sb.append("\nResult: ");
if (outputTriple == null) {
sb.append("null");
} else {
sb.append(outputTriple.toHumanReadableString());
}
appendTraceFooter(sb);
trace(sb.toString());
}
private void traceFailure(ExpressionEvaluationContext context, ExpressionVariables processedVariables, Throwable e) {
LOGGER.error("Error evaluating expression in {}: {}", new Object[]{context.getContextDescription(), e.getMessage(), e});
if (!isTrace()) {
return;
}
StringBuilder sb = new StringBuilder();
sb.append("Expression failure:\n");
appendTraceHeader(sb, context, processedVariables);
sb.append("\nERROR: ").append(e.getClass().getSimpleName()).append(": ").append(e.getMessage());
appendTraceFooter(sb);
trace(sb.toString());
}
private boolean isTrace() {
return LOGGER.isTraceEnabled() || (expressionType != null && expressionType.isTrace() == Boolean.TRUE);
}
private void trace(String msg) {
if (expressionType != null && expressionType.isTrace() == Boolean.TRUE) {
LOGGER.info(msg);
} else {
LOGGER.trace(msg);
}
}
private void appendTraceHeader(StringBuilder sb, ExpressionEvaluationContext context, ExpressionVariables processedVariables) {
sb.append("---[ EXPRESSION in ");
sb.append(context.getContextDescription());
sb.append("]---------------------------");
sb.append("\nSources:");
Collection<Source<?,?>> sources = context.getSources();
if (sources == null) {
sb.append(" null");
} else {
for (Source<?,?> source: sources) {
sb.append("\n");
sb.append(source.debugDump(1));
}
}
sb.append("\nVariables:");
if (processedVariables == null) {
sb.append(" null");
} else {
sb.append("\n");
sb.append(processedVariables.debugDump(1));
}
sb.append("\nOutput definition: ").append(MiscUtil.toString(outputDefinition));
sb.append("\nEvaluators: ");
sb.append(shortDebugDump());
}
private void appendTraceFooter(StringBuilder sb) {
sb.append("\n------------------------------------------------------");
}
private ExpressionVariables processInnerVariables(ExpressionVariables variables, String contextDescription,
Task task, OperationResult result) throws SchemaException, ObjectNotFoundException {
if (expressionType == null) {
// shortcut
return variables;
}
ExpressionVariables newVariables = new ExpressionVariables();
// We need to add actor variable before we switch user identity (runAs)
ExpressionUtil.addActorVariable(newVariables, securityEnforcer);
for(Entry<QName,Object> entry: variables.entrySet()) {
newVariables.addVariableDefinition(entry.getKey(), entry.getValue());
}
for (ExpressionVariableDefinitionType variableDefType: expressionType.getVariable()) {
QName varName = variableDefType.getName();
if (varName == null) {
throw new SchemaException("No variable name in expression in "+contextDescription);
}
if (variableDefType.getObjectRef() != null) {
ObjectReferenceType ref = variableDefType.getObjectRef();
ref.setType(prismContext.getSchemaRegistry().qualifyTypeName(ref.getType()));
ObjectType varObject = objectResolver.resolve(ref, ObjectType.class, null, "variable "+varName+" in "+contextDescription, task, result);
newVariables.addVariableDefinition(varName, varObject);
} else if (variableDefType.getValue() != null) {
// Only string is supported now
Object valueObject = variableDefType.getValue();
if (valueObject instanceof String) {
newVariables.addVariableDefinition(varName, valueObject);
} else if (valueObject instanceof Element) {
newVariables.addVariableDefinition(varName, ((Element)valueObject).getTextContent());
} else if (valueObject instanceof RawType) {
newVariables.addVariableDefinition(varName, ((RawType) valueObject).getParsedValue(null, varName));
} else {
throw new SchemaException("Unexpected type "+valueObject.getClass()+" in variable definition "+varName+" in "+contextDescription);
}
} else if (variableDefType.getPath() != null) {
ItemPath itemPath = variableDefType.getPath().getItemPath();
Object resolvedValue = ExpressionUtil.resolvePath(itemPath, variables, null, objectResolver, contextDescription, task, result);
newVariables.addVariableDefinition(varName, resolvedValue);
} else {
throw new SchemaException("No value for variable "+varName+" in "+contextDescription);
}
}
return newVariables;
}
@Override
public String toString() {
return "Expression(expressionType=" + expressionType + ", outputDefinition=" + outputDefinition
+ ": " + shortDebugDump() + ")";
}
public String shortDebugDump() {
if (evaluators == null) {
return "null evaluators";
}
if (evaluators.isEmpty()) {
return "[]";
}
if (evaluators.size() == 1) {
return evaluators.iterator().next().shortDebugDump();
}
StringBuilder sb = new StringBuilder("[");
for (ExpressionEvaluator<V,D> evaluator: evaluators) {
sb.append(evaluator.shortDebugDump());
sb.append(",");
}
sb.append("]");
return sb.toString();
}
}