/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community 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://opensource.org/licenses/ecl2.txt
*
* 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 org.opencastproject.workflow.impl;
import static com.entwinemedia.fn.data.ListBuilders.LIA;
import static java.lang.String.format;
import org.opencastproject.job.api.Incident.Severity;
import org.opencastproject.security.api.UnauthorizedException;
import org.opencastproject.util.JobCanceledException;
import org.opencastproject.workflow.api.ResumableWorkflowOperationHandler;
import org.opencastproject.workflow.api.WorkflowException;
import org.opencastproject.workflow.api.WorkflowInstance;
import org.opencastproject.workflow.api.WorkflowOperationException;
import org.opencastproject.workflow.api.WorkflowOperationHandler;
import org.opencastproject.workflow.api.WorkflowOperationInstance;
import org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState;
import org.opencastproject.workflow.api.WorkflowOperationResult;
import org.opencastproject.workflow.api.WorkflowOperationResult.Action;
import com.entwinemedia.fn.bool.Bool;
import com.entwinemedia.fn.parser.Parsers;
import com.entwinemedia.fn.parser.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Handles execution of a workflow operation.
*/
final class WorkflowOperationWorker {
private static final Logger logger = LoggerFactory.getLogger(WorkflowOperationWorker.class);
/**
* Boolean expression parser that interprets non replaced variable strings of pattern <code>${VAR}</code> as false.
*/
private static final Bool booleanExpressionEvaluator = new Bool(LIA.mk(Parsers.token(Parsers.regex(Pattern.compile("\\$\\{.*?\\}")))
.bind(Parsers.ignorePrevious(Parsers.yield(false)))));
private WorkflowOperationHandler handler = null;
private WorkflowInstance workflow = null;
private WorkflowServiceImpl service = null;
private Map<String, String> properties = null;
/**
* Creates a worker that will execute the given handler and thereby the current operation of the workflow instance.
* When the worker is finished, a callback will be made to the workflow service reporting either success or failure of
* the current workflow operation.
*
* @param handler
* the workflow operation handler
* @param workflow
* the workflow instance
* @param service
* the workflow service.
*/
WorkflowOperationWorker(WorkflowOperationHandler handler, WorkflowInstance workflow,
WorkflowServiceImpl service) {
this.handler = handler;
this.workflow = workflow;
this.service = service;
}
/**
* Creates a worker that will execute the given handler and thereby the current operation of the workflow instance.
* When the worker is finished, a callback will be made to the workflow service reporting either success or failure of
* the current workflow operation.
*
* @param handler
* the workflow operation handler
* @param workflow
* the workflow instance
* @param properties
* the properties used to execute the operation
* @param service
* the workflow service.
*/
WorkflowOperationWorker(WorkflowOperationHandler handler, WorkflowInstance workflow,
Map<String, String> properties, WorkflowServiceImpl service) {
this(handler, workflow, service);
this.properties = properties;
}
/**
* Creates a worker that still needs an operation handler to be set. When the worker is finished, a callback will be
* made to the workflow service reporting either success or failure of the current workflow operation.
*
* @param workflow
* the workflow instance
* @param properties
* the properties used to execute the operation
* @param service
* the workflow service.
*/
WorkflowOperationWorker(WorkflowInstance workflow, Map<String, String> properties, WorkflowServiceImpl service) {
this(null, workflow, service);
this.properties = properties;
}
/**
* Creates a worker that still needs an operation handler to be set. When the worker is finished, a callback will be
* made to the workflow service reporting either success or failure of the current workflow operation.
*
* @param workflow
* the workflow instance
* @param service
* the workflow service.
*/
WorkflowOperationWorker(WorkflowInstance workflow, WorkflowServiceImpl service) {
this(null, workflow, service);
}
/**
* Sets the workflow operation handler to use.
*
* @param operationHandler
* the handler
*/
public void setHandler(WorkflowOperationHandler operationHandler) {
this.handler = operationHandler;
}
/**
* Executes the workflow operation logic.
*/
public WorkflowInstance execute() {
WorkflowOperationInstance operation = workflow.getCurrentOperation();
try {
WorkflowOperationResult result = null;
switch (operation.getState()) {
case INSTANTIATED:
case RETRY:
result = start();
break;
case PAUSED:
result = resume();
break;
default:
throw new IllegalStateException("Workflow operation '" + operation + "' is in unexpected state '"
+ operation.getState() + "'");
}
if (result == null || Action.CONTINUE.equals(result.getAction()) || Action.SKIP.equals(result.getAction())) {
if (handler != null) {
handler.destroy(workflow, null);
}
}
workflow = service.handleOperationResult(workflow, result);
} catch (JobCanceledException e) {
logger.info(e.getMessage());
} catch (Exception e) {
Throwable t = e.getCause();
if (t != null) {
logger.error("Workflow operation '" + operation + "' failed", t);
} else {
logger.error("Workflow operation '" + operation + "' failed", e);
}
// the associated job shares operation's id
service.getServiceRegistry().incident().unhandledException(operation.getId(), Severity.FAILURE, e);
try {
workflow = service.handleOperationException(workflow, operation);
} catch (Exception e2) {
logger.error("Error handling workflow operation '{}' failure: {}", new Object[] { operation, e2.getMessage(),
e2 });
}
}
return workflow;
}
/**
* Starts executing the workflow operation.
*
* @return the workflow operation result
* @throws WorkflowOperationException
* if executing the workflow operation handler fails
* @throws WorkflowException
* if there is a problem processing the workflow
*/
public WorkflowOperationResult start() throws WorkflowOperationException, WorkflowException, UnauthorizedException {
final WorkflowOperationInstance operation = workflow.getCurrentOperation();
// Do we need to execute the operation?
final String executionCondition = operation.getExecutionCondition(); // if
final boolean execute;
if (executionCondition == null) {
execute = true;
} else {
final Result<Boolean> parsed = booleanExpressionEvaluator.eval(executionCondition);
if (parsed.isDefined() && parsed.getRest().isEmpty()) {
execute = parsed.getResult();
} else {
operation.setState(OperationState.FAILED);
throw new WorkflowOperationException(format("Unable to parse execution condition '%s'. Result is '%s'",
executionCondition, parsed.toString()));
}
}
operation.setState(OperationState.RUNNING);
service.update(workflow);
try {
WorkflowOperationResult result = null;
if (execute) {
if (handler == null) {
// If there is no handler for the operation, yet we are supposed to run it, we must fail
logger.warn("No handler available to execute operation '{}'", operation.getTemplate());
throw new IllegalStateException("Unable to find a workflow handler for '" + operation.getTemplate() + "'");
}
result = handler.start(workflow, null);
} else {
// Allow for null handlers when we are skipping an operation
if (handler != null) {
result = handler.skip(workflow, null);
result.setAction(Action.SKIP);
}
}
return result;
} catch (Exception e) {
operation.setState(OperationState.FAILED);
if (e instanceof WorkflowOperationException)
throw (WorkflowOperationException) e;
throw new WorkflowOperationException(e);
}
}
/**
* Resumes a previously suspended workflow operation. Note that only workflow operation handlers that implement
* {@link ResumableWorkflowOperationHandler} can be resumed.
*
* @return the workflow operation result
* @throws WorkflowOperationException
* if executing the workflow operation handler fails
* @throws WorkflowException
* if there is a problem processing the workflow
* @throws IllegalStateException
* if the workflow operation cannot be resumed
*/
public WorkflowOperationResult resume() throws WorkflowOperationException, WorkflowException, IllegalStateException,
UnauthorizedException {
WorkflowOperationInstance operation = workflow.getCurrentOperation();
// Make sure we have a (suitable) handler
if (handler == null) {
// If there is no handler for the operation, yet we are supposed to run it, we must fail
logger.warn("No handler available to resume operation '{}'", operation.getTemplate());
throw new IllegalStateException("Unable to find a workflow handler for '" + operation.getTemplate() + "'");
} else if (!(handler instanceof ResumableWorkflowOperationHandler)) {
throw new IllegalStateException("An attempt was made to resume a non-resumable operation");
}
ResumableWorkflowOperationHandler resumableHandler = (ResumableWorkflowOperationHandler) handler;
operation.setState(OperationState.RUNNING);
service.update(workflow);
try {
WorkflowOperationResult result = resumableHandler.resume(workflow, null, properties);
return result;
} catch (Exception e) {
operation.setState(OperationState.FAILED);
if (e instanceof WorkflowOperationException)
throw (WorkflowOperationException) e;
throw new WorkflowOperationException(e);
}
}
}