/*
* Copyright (c) 2010-2014 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.impl.scripting;
import com.evolveum.midpoint.model.api.ScriptExecutionException;
import com.evolveum.midpoint.model.impl.scripting.expressions.SearchEvaluator;
import com.evolveum.midpoint.model.impl.scripting.expressions.SelectEvaluator;
import com.evolveum.midpoint.model.impl.scripting.helpers.JaxbHelper;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.marshaller.QueryConvertor;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.*;
import com.evolveum.prism.xml.ns._public.types_3.RawType;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.Validate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import java.util.HashMap;
import java.util.Map;
/**
* Main entry point for evaluating scripting expressions.
*
* @author mederly
*/
@Component
public class ScriptingExpressionEvaluator {
private static final Trace LOGGER = TraceManager.getTrace(ScriptingExpressionEvaluator.class);
private static final String DOT_CLASS = ScriptingExpressionEvaluator.class + ".";
@Autowired
private TaskManager taskManager;
@Autowired
private SearchEvaluator searchEvaluator;
@Autowired
private SelectEvaluator selectEvaluator;
@Autowired
private JaxbHelper jaxbHelper;
@Autowired
private PrismContext prismContext;
private ObjectFactory objectFactory = new ObjectFactory();
private Map<String,ActionExecutor> actionExecutors = new HashMap<>();
/**
* Asynchronously executes simple scripting expressions, consisting of one search command and one action.
*
* @param objectType Object type to search (e.g. c:UserType)
* @param filter Filter to be applied (ObjectFilter)
* @param actionName Action to be executed on objects found (e.g. "disable", "delete", "recompute", etc).
* @param task Task in context of which the script should execute. The task should be "clean", i.e.
* (1) transient, (2) without any handler. This method puts the task into background,
* and assigns ScriptExecutionTaskHandler to it, to execute the script.
* @param parentResult
* @throws SchemaException
*/
@Deprecated
public void evaluateExpressionInBackground(QName objectType, ObjectFilter filter, String actionName, Task task, OperationResult parentResult) throws SchemaException {
Validate.notNull(objectType);
Validate.notNull(actionName);
Validate.notNull(task);
SearchExpressionType search = new SearchExpressionType();
search.setType(objectType);
if (filter != null) {
search.setSearchFilter(QueryConvertor.createSearchFilterType(filter, prismContext));
}
ActionExpressionType action = new ActionExpressionType();
action.setType(actionName);
search.setScriptingExpression(objectFactory.createAction(action));
evaluateExpressionInBackground(search, task, parentResult);
}
/**
* Asynchronously executes any scripting expression.
*
* @param expression Expression to be executed.
* @param task Task in context of which the script should execute. The task should be "clean", i.e.
* (1) transient, (2) without any handler. This method puts the task into background,
* and assigns ScriptExecutionTaskHandler to it, to execute the script.
* @param parentResult
* @throws SchemaException
*/
public void evaluateExpressionInBackground(ScriptingExpressionType expression, Task task, OperationResult parentResult) throws SchemaException {
OperationResult result = parentResult.createSubresult(DOT_CLASS + "evaluateExpressionInBackground");
if (!task.isTransient()) {
throw new IllegalStateException("Task must be transient");
}
if (task.getHandlerUri() != null) {
throw new IllegalStateException("Task must not have a handler");
}
ExecuteScriptType executeScriptType = new ExecuteScriptType();
executeScriptType.setScriptingExpression(jaxbHelper.toJaxbElement(expression));
task.setExtensionPropertyValue(SchemaConstants.SE_EXECUTE_SCRIPT, executeScriptType);
task.setHandlerUri(ScriptExecutionTaskHandler.HANDLER_URI);
taskManager.switchToBackground(task, result);
result.computeStatus();
}
/**
* Entry point to _synchronous_ script execution, with no input data.
*
* @param expression Scripting expression to execute.
* @param task Task in context of which the script should execute (in foreground!)
* @param result Operation result
* @return ExecutionContext, from which the caller can retrieve the output data via getFinalOutput() method,
* and the console output via getConsoleOutput() method.
* @throws com.evolveum.midpoint.model.api.ScriptExecutionException
*/
public ExecutionContext evaluateExpression(ScriptingExpressionType expression, Task task, OperationResult result) throws ScriptExecutionException {
return evaluateExpression(expression, Data.createEmpty(), null, task, result);
}
public ExecutionContext evaluateExpression(@NotNull ExecuteScriptType executeScript, Task task, OperationResult result) throws ScriptExecutionException {
// TODO parse input data
Validate.notNull(executeScript.getScriptingExpression(), "Scripting expression must be present");
return evaluateExpression(executeScript.getScriptingExpression().getValue(), Data.createEmpty(), executeScript.getOptions(), task, result);
}
// use in tests only (we need the task at least to report progress/statistics)
@Deprecated
public ExecutionContext evaluateExpression(ScriptingExpressionType expression, OperationResult result) throws ScriptExecutionException {
Task task = taskManager.createTaskInstance();
return evaluateExpression(expression, task, result);
}
// main entry point from the outside
private ExecutionContext evaluateExpression(ScriptingExpressionType expression, Data data,
ScriptingExpressionEvaluationOptionsType options, Task task, OperationResult result) throws ScriptExecutionException {
ExecutionContext context = new ExecutionContext(options, task);
Data output;
try {
output = evaluateExpression(expression, data, context, result);
} catch (RuntimeException e) {
result.recordFatalError("Couldn't execute script", e);
throw new ScriptExecutionException("Couldn't execute script: " + e.getMessage(), e);
}
result.computeStatusIfUnknown();
context.setFinalOutput(output);
return context;
}
public Data evaluateExpression(JAXBElement<? extends ScriptingExpressionType> expression, Data input, ExecutionContext context, OperationResult parentResult) throws ScriptExecutionException {
return evaluateExpression(expression.getValue(), input, context, parentResult);
}
public Data evaluateExpression(ScriptingExpressionType value, Data input, ExecutionContext context, OperationResult parentResult) throws ScriptExecutionException {
context.checkTaskStop();
OperationResult result = parentResult.createMinorSubresult(DOT_CLASS + "evaluateExpression");
Data output;
if (value instanceof ExpressionPipelineType) {
output = executePipeline((ExpressionPipelineType) value, input, context, result);
} else if (value instanceof ExpressionSequenceType) {
output = executeSequence((ExpressionSequenceType) value, input, context, result);
} else if (value instanceof ForeachExpressionType) {
output = executeForEach((ForeachExpressionType) value, input, context, result);
} else if (value instanceof SelectExpressionType) {
output = selectEvaluator.evaluate((SelectExpressionType) value, input, context, result);
} else if (value instanceof FilterExpressionType) {
output = executeFilter((FilterExpressionType) value, input, context, result);
} else if (value instanceof SearchExpressionType) {
output = searchEvaluator.evaluate((SearchExpressionType) value, input, context, result);
} else if (value instanceof ActionExpressionType) {
output = executeAction((ActionExpressionType) value, input, context, result);
} else {
throw new IllegalArgumentException("Unsupported expression type: " + (value==null?"(null)":value.getClass()));
}
result.computeStatusIfUnknown();
return output;
}
private Data executeAction(ActionExpressionType command, Data input, ExecutionContext context, OperationResult result) throws ScriptExecutionException {
Validate.notNull(command, "command");
Validate.notNull(command.getType(), "command.actionType");
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Executing action {} on {}", command.getType(), input.debugDump());
} else if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Executing action {}", command.getType());
}
ActionExecutor executor = actionExecutors.get(command.getType());
if (executor == null) {
throw new IllegalStateException("Unsupported action type: " + command.getType());
} else {
Data retval = executor.execute(command, input, context, result);
result.setSummarizeSuccesses(true);
result.summarize();
return retval;
}
}
private Data executeFilter(FilterExpressionType command, Data input, ExecutionContext context, OperationResult result) {
throw new NotImplementedException();
}
private Data executeForEach(ForeachExpressionType command, Data input, ExecutionContext context, OperationResult result) {
return null;
}
private Data executePipeline(ExpressionPipelineType pipeline, Data data, ExecutionContext context, OperationResult result) throws ScriptExecutionException {
for (JAXBElement<? extends ScriptingExpressionType> expressionType : pipeline.getScriptingExpression()) {
data = evaluateExpression(expressionType, data, context, result);
}
return data;
}
private Data executeSequence(ExpressionSequenceType sequence, Data input, ExecutionContext context, OperationResult result) throws ScriptExecutionException {
Data lastOutput = null;
for (JAXBElement<? extends ScriptingExpressionType> expressionType : sequence.getScriptingExpression()) {
lastOutput = evaluateExpression(expressionType, input, context, result);
}
return lastOutput;
}
public Data evaluateConstantExpression(@NotNull RawType constant, @Nullable Class<?> expectedClass, ExecutionContext context, String desc, OperationResult result) throws ScriptExecutionException {
try {
// TODO fix this brutal hacking
PrismValue value;
if (expectedClass == null) {
value = constant.getParsedValue(null, null);
} else {
Object object = constant.getParsedRealValue(expectedClass);
if (object instanceof Referencable) {
value = ((Referencable) object).asReferenceValue();
} else if (object instanceof Containerable) {
value = ((Containerable) object).asPrismContainerValue();
} else {
value = new PrismPropertyValue<>(object);
}
}
if (value.isRaw()) {
throw new IllegalStateException("Raw value while " + desc + ": " + value + ". Please specify type of the value.");
}
return Data.createItem(value, prismContext);
} catch (SchemaException e) {
throw new ScriptExecutionException(e.getMessage(), e);
}
}
public Data evaluateConstantStringExpression(RawType constant, ExecutionContext context, OperationResult result) throws ScriptExecutionException {
try {
String value = constant.getParsedRealValue(String.class);
return Data.createItem(new PrismPropertyValue<>(value), prismContext);
} catch (SchemaException e) {
throw new ScriptExecutionException(e.getMessage(), e);
}
}
public void registerActionExecutor(String actionName, ActionExecutor executor) {
actionExecutors.put(actionName, executor);
}
}