/*
 * 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;
    }
}