/* * Jopr Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, as * published by the Free Software Foundation, and/or the GNU Lesser * General Public License, version 2.1, also as published by the Free * Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License and the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.jboss.jbossnetwork.product.jbpm.handlers; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jbpm.graph.def.ActionHandler; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.jpdl.el.impl.JbpmExpressionEvaluator; import org.rhq.core.domain.content.transfer.ContentResponseResult; import org.rhq.core.domain.content.transfer.DeployPackageStep; import org.rhq.core.util.exception.ExceptionPackage; /** * Base class for all of our JBPM handlers, providing some basic functionality for transitioning between steps. * * @author Jason Dobies */ public abstract class BaseHandler implements ActionHandler { /** * Describes a successful transition between steps. */ protected static final String TRANSITION_SUCCESS = "success"; /** * Describes an error in a step, preventing transitioning. */ protected static final String TRANSITION_ERROR = "error"; /** * Standard message to describe that no changes were made by the currently executing step. */ protected static final String MESSAGE_NO_CHANGES = "No changes were made in this step."; /** * Logger, keyed to the subclass. */ protected final Log logger = LogFactory.getLog(this.getClass()); /** * Returns a user readable description of what the step in the workflow entails. * * @return should not be <code>null</code> */ public abstract String getDescription(); /** * Tells the handle implementation to actually perform the step indicated. * * @param executionContext cannot be <code>null</code> */ public abstract void run(ExecutionContext executionContext); /** * Sets the default values for properties used by the handler implementation. This may optionally be overridden by * action handler implementations if necessary. */ public void setPropertyDefaults() { // Stub implementation } /** * Requests the action handler substitute into its node any variables necessary, taking the values for these from * the provided execution context. This may optionally be overridden by action handler implementations if necessary. * * @param executionContext JBPM execution context from which the property values should be extracted * * @throws ActionHandlerException if there is an error extraction or substituting the variables */ public void substituteVariables(ExecutionContext executionContext) throws ActionHandlerException { // Stub implementation } /** * Ensures the property values that were set in {@link #substituteVariables(org.jbpm.graph.exe.ExecutionContext)} * are valid. This may optionally be overridden by action handler implementations if necessary. * * @throws ActionHandlerException if any of the properties are invalid */ protected void checkProperties() throws ActionHandlerException { // Stub implementation } public void execute(ExecutionContext executionContext) throws Exception { try { if (logger.isDebugEnabled()) { String nodeName = executionContext.getNode().getName(); logger.debug("Description of step [" + nodeName + "] prior to setting defaults: " + getDescription()); } setPropertyDefaults(); if (logger.isDebugEnabled()) { String nodeName = executionContext.getNode().getName(); logger.debug("Description of step [" + nodeName + "] prior to substituting variables: " + getDescription()); } substituteVariables(executionContext); if (logger.isDebugEnabled()) { String nodeName = executionContext.getNode().getName(); logger .debug("Description of step [" + nodeName + "] prior to checking properties: " + getDescription()); } checkProperties(); } catch (ActionHandlerException e) { error(executionContext, e, MESSAGE_NO_CHANGES, TRANSITION_ERROR); return; } try { run(executionContext); } catch (Exception e) { // error(executionContext, new ActionHandlerException(e), e.getMessage(), TRANSITION_ERROR); logger.error("Error caught from run", e); } } /** * Called by a handler at the end of its processing to indicate an error occurred in the execution of a given step * and closes out the step. * * @param executionContext context in which the step was executing * @param throwable exception that occurred to trigger this call to error * @param additionalMessage additional details to report on the step * @param leavingTransition ? */ protected void error(ExecutionContext executionContext, Throwable throwable, String additionalMessage, String leavingTransition) { String nodeName = executionContext.getNode().getName(); ActionHandlerMessageLog log = logStep(executionContext, throwable, additionalMessage, ContentResponseResult.FAILURE); logger.info("Description of step [" + nodeName + "]: " + getDescription()); logger.error("Result of step [" + nodeName + "]: " + log); executionContext.leaveNode(leavingTransition); } /** * Called by a handler at the end of its processing to indicate a step was completed successfully and transitions to * the next step in the workflow. * * @param executionContext context in which the step was executing * @param message additional details on the step */ protected void complete(ExecutionContext executionContext, String message) { String nodeName = executionContext.getNode().getName(); logger.info("Description of step [" + nodeName + "]: " + getDescription()); ActionHandlerMessageLog log = logStep(executionContext, null, message, ContentResponseResult.SUCCESS); logger.info("Result of step [" + nodeName + "]: " + log); executionContext.leaveNode(TRANSITION_SUCCESS); } /** * Called by a handler at the end of its processing to indicate a step in the workflow has been skipped and * transitions to the next step in the workflow. * * @param executionContext context in which the step was executing * @param exception exception that occurred to trigger this call to error * @param additionalMessage additional details to report on the step * @param leavingTransition ? */ protected void skip(ExecutionContext executionContext, ActionHandlerException exception, String additionalMessage, String leavingTransition) { String nodeName = executionContext.getNode().getName(); ActionHandlerMessageLog log = logStep(executionContext, exception, additionalMessage, ContentResponseResult.NOT_PERFORMED); logger.info("Description of step [" + nodeName + "]: " + getDescription()); logger.warn("Result of step [" + nodeName + "]: " + log); executionContext.leaveNode(leavingTransition); } /** * Called by a handler at the end of its processing to indicate a step was skipped and transitions to the next step * in the workflow. * * @param executionContext context in which the step was executing * @param message additional details on the step */ protected void notRun(ExecutionContext executionContext, String message) { String nodeName = executionContext.getNode().getName(); ActionHandlerMessageLog log = logStep(executionContext, null, message, ContentResponseResult.NOT_PERFORMED); logger.info("Result of step [" + nodeName + "]: " + log); executionContext.leaveNode(TRANSITION_SUCCESS); } /** * Logs a step into the JBPM context. * * @param executionContext context in which the step executed * @param throwable optional error that occurred in the step * @param additionalMessage details of the step * @param result result of executing the step * * @return populated log message, will not be <code>null</code> */ private ActionHandlerMessageLog logStep(ExecutionContext executionContext, Throwable throwable, String additionalMessage, ContentResponseResult result) { ActionHandlerMessageLog log = new ActionHandlerMessageLog(); // The steps must indicate their order for the domain model. Store the variable in the context and use that // to keep track of what step we're on. Integer stepCounter = (Integer) executionContext.getVariable("stepCounter"); if (stepCounter == null) { stepCounter = 0; } stepCounter++; executionContext.setVariable("stepCounter", stepCounter); // This was ported directly from 1.4 like this, but I'm not sure I like the idea of prefixing the throwable's // message before the additional message. It reads better if the handler simply provides an error message // in the additionalMessage field that is customized to what the handler does (i.e. "Could not download file"). // Once we do more testing, I may revisit this and reenable this form of description generation. // jdobies, Mar 6, 2008 // lkrejci, 2009-05-25 - added additional check for nullity of the throwable's message because the description mustn't be null String description = (throwable == null || throwable.getMessage() == null) ? additionalMessage : throwable .getMessage(); DeployPackageStep step = new DeployPackageStep(Integer.toString(stepCounter), description); step.setStepResult(result); if (throwable != null) { String errorMessage = new ExceptionPackage(throwable).getStackTraceString(); step.setStepErrorMessage(errorMessage); } log.setStep(step); executionContext.getProcessInstance().getLoggingInstance().addLog(log); return log; } /** * Substitutes in values found in the workflow (execution context) for the specified expression. * * @param expression expression into which to substitute values from the workflow * @param executionContext context describing the workflow * * @return expression with the proper values substituted in; <code>null</code> if the expression is <code> * null</code> * * @throws ActionHandlerException if there is an error during the substitution */ protected String substituteVariable(String expression, ExecutionContext executionContext) throws ActionHandlerException { // If there is nothing in the value, then a substitution isn't going to change that if (expression == null) { return null; } // One bad thing with this evaluator is that if the variable name can't be found in the // executionContext it just replaces it with the empty string. Would be nicer if it // complained more loudly. That said it shouldn't cause problems since subsequent steps // won't work without the variables being substituted properly. // ccrouch, 1.4 codebase Object valueAfterSubst = JbpmExpressionEvaluator.evaluate(expression, executionContext); return (String) valueAfterSubst; } }