com.flexive.ejb.beans.workflow.StepDefinitionEngineBean.java Source code

Java tutorial

Introduction

Here is the source code for com.flexive.ejb.beans.workflow.StepDefinitionEngineBean.java

Source

/***************************************************************
 *  This file is part of the [fleXive](R) framework.
 *
 *  Copyright (c) 1999-2014
 *  UCS - unique computing solutions gmbh (http://www.ucs.at)
 *  All rights reserved
 *
 *  The [fleXive](R) project is free software; you can redistribute
 *  it and/or modify it under the terms of the GNU Lesser General Public
 *  License version 2.1 or higher as published by the Free Software Foundation.
 *
 *  The GNU Lesser General Public License can be found at
 *  http://www.gnu.org/licenses/lgpl.html.
 *  A copy is found in the textfile LGPL.txt and important notices to the
 *  license from the author are found in LICENSE.txt distributed with
 *  these libraries.
 *
 *  This library 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 for more details.
 *
 *  For further information about UCS - unique computing solutions gmbh,
 *  please see the company website: http://www.ucs.at
 *
 *  For further information about [fleXive](R), please see the
 *  project website: http://www.flexive.org
 *
 *
 *  This copyright notice MUST APPEAR in all copies of the file!
 ***************************************************************/
package com.flexive.ejb.beans.workflow;

import com.flexive.core.Database;
import com.flexive.core.storage.StorageManager;
import com.flexive.core.structure.StructureLoader;
import com.flexive.ejb.beans.EJBUtils;
import com.flexive.shared.CacheAdmin;
import com.flexive.shared.FxContext;
import com.flexive.shared.FxSharedUtils;
import com.flexive.shared.FxSystemSequencer;
import com.flexive.shared.content.FxPermissionUtils;
import com.flexive.shared.exceptions.*;
import com.flexive.shared.interfaces.SequencerEngineLocal;
import com.flexive.shared.interfaces.StepDefinitionEngine;
import com.flexive.shared.interfaces.StepDefinitionEngineLocal;
import com.flexive.shared.interfaces.StepEngineLocal;
import com.flexive.shared.security.Role;
import com.flexive.shared.security.UserTicket;
import com.flexive.shared.value.FxString;
import com.flexive.shared.workflow.Step;
import com.flexive.shared.workflow.StepDefinition;
import com.flexive.shared.workflow.Workflow;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.annotation.Resource;
import javax.ejb.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static com.flexive.core.DatabaseConst.ML;
import static com.flexive.core.DatabaseConst.TBL_WORKFLOW_STEPDEFINITION;
import static com.flexive.shared.CacheAdmin.getEnvironment;

/**
 * Class for the maintainance of Global Step Definitions.
 *
 * @author Gregor Schober (gregor.schober@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 */
@Stateless(name = "StepDefinitionEngine", mappedName = "StepDefinitionEngine")
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
@TransactionManagement(TransactionManagementType.CONTAINER)
public class StepDefinitionEngineBean implements StepDefinitionEngine, StepDefinitionEngineLocal {

    private static final Log LOG = LogFactory.getLog(StepDefinitionEngineBean.class);

    @EJB
    private StepEngineLocal stepEngine;
    @EJB
    private SequencerEngineLocal seq;
    @Resource
    private SessionContext ctx;

    /**
     * Checks if a unique target id is valid to use.
     *
     * @param id             the step definition ID to be checked
     * @param uniqueTargetId the unique target to be checked
     * @throws FxInvalidParameterException if the unique target may not be used
     */
    private void checkValidUniqueTarget(long id, long uniqueTargetId) throws FxInvalidParameterException {
        // TODO check for cycles
        if (uniqueTargetId >= 0) {
            if (id == uniqueTargetId) {
                // step may not reference itself
                throw new FxInvalidParameterException("UNIQUETARGET", LOG,
                        "ex.stepdefinition.uniqueTarget.circular.self", id);
            }
            try {
                // check if the target exists and check for cycles
                HashSet<Long> visitedStepDefinitions = new HashSet<Long>();
                if (id != -1) {
                    // must not reach "id" from unique target
                    visitedStepDefinitions.add(id);
                }
                checkForCycles(visitedStepDefinitions, uniqueTargetId);
            } catch (FxRuntimeException exc) {
                if (exc.getCause() instanceof FxNotFoundException) {
                    // provide a clear message if the unique target does not exist
                    throw new FxInvalidParameterException("UNIQUETARGET", LOG,
                            "ex.stepdefinition.uniqueTarget.notFound");
                } else {
                    throw exc;
                }
            }
        }
    }

    /**
     * Follows the unique target of the current node and checks if there are cycles
     * in the step definition graph.
     *
     * @param visitedStepDefinitions a set storing already visited nodes
     * @param stepDefinitionId       the currently visited node ID
     * @throws com.flexive.shared.exceptions.FxInvalidParameterException
     *          if a cycle was detected
     */
    private void checkForCycles(Set<Long> visitedStepDefinitions, long stepDefinitionId)
            throws FxInvalidParameterException {
        if (!visitedStepDefinitions.add(stepDefinitionId)) {
            String id = "" + stepDefinitionId;
            try {
                id = CacheAdmin.getEnvironment().getStepDefinition(stepDefinitionId).getLabel() + " (Id: "
                        + stepDefinitionId + ")";
            } catch (Exception e) {
                //ignore
            }
            throw new FxInvalidParameterException("UNIQUETARGET", LOG, "ex.stepdefinition.uniqueTarget.circular",
                    id);
        }
        StepDefinition sd = getEnvironment().getStepDefinition(stepDefinitionId);
        if (sd.getUniqueTargetId() != -1) {
            checkForCycles(visitedStepDefinitions, sd.getUniqueTargetId());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public long create(StepDefinition stepDefinition) throws FxApplicationException {

        final UserTicket ticket = FxContext.getUserTicket();
        // Security checks
        FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement);
        // Create the new step
        Connection con = null;
        PreparedStatement ps = null;
        String sql;
        FxString label = stepDefinition.getLabel();
        if (StringUtils.isEmpty(stepDefinition.getName()))
            throw new FxInvalidParameterException("NAME", "ex.stepdefinition.name.empty");
        if (label.isEmpty())
            throw new FxInvalidParameterException("LABEL", "ex.stepdefinition.label.empty");
        String name = stepDefinition.getName();
        long uniqueTargetId = stepDefinition.getUniqueTargetId();
        boolean success = false;
        long newId = -1;
        try {
            // Check the unique target
            checkValidUniqueTarget(-1, uniqueTargetId);

            if (StringUtils.isBlank(label.getDefaultTranslation())) {
                FxInvalidParameterException ip = new FxInvalidParameterException("NAME",
                        "ex.stepdefinition.name.empty");
                if (LOG.isDebugEnabled())
                    LOG.debug(ip);
                throw ip;
            }

            // Obtain a database connection
            con = Database.getDbConnection();

            // Create the new workflow instance
            sql = "INSERT INTO " + TBL_WORKFLOW_STEPDEFINITION + " (ID,NAME,UNIQUE_TARGET) VALUES (?,?,?)";
            ps = con.prepareStatement(sql);
            newId = seq.getId(FxSystemSequencer.STEPDEFINITION);
            ps.setLong(1, newId);
            ps.setString(2, name);
            if (uniqueTargetId != -1) {
                ps.setLong(3, uniqueTargetId);
            } else {
                ps.setNull(3, Types.NUMERIC);
            }
            if (ps.executeUpdate() != 1)
                throw new FxCreateException(LOG, "ex.stepdefinition.create");
            Database.storeFxString(label, con, TBL_WORKFLOW_STEPDEFINITION, "name", "id", newId);
            success = true;
        } catch (FxInvalidParameterException exc) {
            throw exc;
        } catch (Exception exc) {
            if (StorageManager.isUniqueConstraintViolation(exc)) {
                FxEntryExistsException ee = new FxEntryExistsException("ex.stepdefinition.name.exists", name);
                if (LOG.isDebugEnabled())
                    LOG.debug(ee);
                throw ee;
            } else {
                FxCreateException ce = new FxCreateException(LOG, "ex.stepdefinition.create", exc);
                LOG.error("Internal error: " + exc.getMessage(), ce);
                throw ce;
            }
        } finally {
            Database.closeObjects(StepDefinitionEngineBean.class, con, ps);
            if (!success) {
                EJBUtils.rollback(ctx);
            } else {
                StructureLoader.reloadWorkflows(FxContext.get().getDivisionId());
            }
        }
        return newId;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void update(StepDefinition stepDefinition) throws FxApplicationException {

        final UserTicket ticket = FxContext.getUserTicket();
        final FxContext ri = FxContext.get();

        // Security checks
        FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement);

        StepDefinition orgDefinition;
        try {
            // Lookup the stepDefinition, throws FxNotFoundException (+FxDbException)
            orgDefinition = getEnvironment().getStepDefinition(stepDefinition.getId());
        } catch (Exception exc) {
            throw new FxUpdateException(exc.getMessage(), exc);
        }

        // Unique target checks
        boolean uniqueTargetAdded = stepDefinition.getId() != -1 && stepDefinition.getUniqueTargetId() != -1
                && stepDefinition.getUniqueTargetId() != orgDefinition.getUniqueTargetId();

        // Check the unique target, throws FxInvalidParameterException if target is invalid (+FxDbException)
        if (stepDefinition.getUniqueTargetId() != -1) {
            checkValidUniqueTarget(stepDefinition.getId(), stepDefinition.getUniqueTargetId());
        }

        // Sanity checks
        if (stepDefinition.getLabel() == null || stepDefinition.getLabel().isEmpty()) {
            throw new FxInvalidParameterException("NAME", "ex.stepdefinition.name.empty");
        }

        Connection con = null;
        PreparedStatement stmt = null;
        String sql;
        boolean success = false;
        try {

            // Obtain a database connection
            con = Database.getDbConnection();

            // store label
            Database.storeFxString(stepDefinition.getLabel(), con, TBL_WORKFLOW_STEPDEFINITION, "name", "id",
                    stepDefinition.getId());

            sql = "UPDATE " + TBL_WORKFLOW_STEPDEFINITION + " SET NAME=?,UNIQUE_TARGET=? WHERE ID=?";
            stmt = con.prepareStatement(sql);
            stmt.setString(1, stepDefinition.getName());
            if (stepDefinition.getUniqueTargetId() != -1) {
                stmt.setLong(2, stepDefinition.getUniqueTargetId());
            } else {
                stmt.setNull(2, Types.NUMERIC);
            }
            stmt.setLong(3, stepDefinition.getId());

            int updateCount = stmt.executeUpdate();

            if (updateCount == 0) {
                FxNotFoundException nfe = new FxNotFoundException("ex.stepdefinition.load.notFound",
                        stepDefinition.getId());
                if (LOG.isInfoEnabled())
                    LOG.info(nfe);
                throw nfe;
            } else if (updateCount != 1) {
                FxUpdateException dbe = new FxUpdateException("ex.stepdefinition.update.rows");
                LOG.error(dbe);
                throw dbe;
            }

            // Unique target has to exist for every workflow
            long workflowId;
            if (uniqueTargetAdded) {
                try {
                    ri.runAsSystem();
                    List<StepDefinition> stepDefinitionList = new ArrayList<StepDefinition>();
                    stepDefinitionList.add(stepDefinition);
                    // Do this for all existing workflows ..
                    for (Workflow workflow : getEnvironment().getWorkflows()) {
                        workflowId = workflow.getId();
                        if (FxSharedUtils.getUsedStepDefinitions(workflow.getSteps(), stepDefinitionList)
                                .size() > 0) {
                            // create step IF the step definition is used by the workflow
                            stepEngine.createStep(
                                    new Step(-1, stepDefinition.getUniqueTargetId(), workflowId, getEnvironment()
                                            .getStepByDefinition(workflowId, stepDefinition.getId()).getAclId()));
                        }
                    }
                } catch (Exception exc) {
                    throw new FxUpdateException(LOG, "ex.stepdefinition.uniqueTarget.create");
                } finally {
                    ri.stopRunAsSystem();
                }
            }
            success = true;
        } catch (SQLException exc) {
            if (StorageManager.isUniqueConstraintViolation(exc)) {
                FxEntryExistsException ee = new FxEntryExistsException("ex.stepdefinition.name.exists",
                        stepDefinition.getName());
                if (LOG.isDebugEnabled())
                    LOG.debug(ee);
                throw ee;
            }
            throw new FxUpdateException(LOG, exc, "ex.db.sqlError", exc.getMessage());
        } finally {
            Database.closeObjects(StepDefinitionEngineBean.class, con, stmt);
            if (!success) {
                EJBUtils.rollback(ctx);
            } else {
                StructureLoader.reloadWorkflows(FxContext.get().getDivisionId());
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void remove(long id) throws FxApplicationException {

        final UserTicket ticket = FxContext.getUserTicket();

        // Cannot delete basis step
        if (id == StepDefinition.LIVE_STEP_ID || id == StepDefinition.EDIT_STEP_ID) {
            throw new FxNoAccessException("ex.stepdefinition.delete.system");
        }

        // Security checks
        FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement);

        // Check existance
        getEnvironment().getStepDefinition(id);

        // Now try to delete the stepDefinition
        Connection con = null;
        Statement stmt = null;
        String sql;
        boolean success = false;
        try {

            // Obtain a database connection
            con = Database.getDbConnection();

            // Read all stepDefinitions from the database
            stmt = con.createStatement();
            sql = "DELETE FROM " + TBL_WORKFLOW_STEPDEFINITION + ML + " WHERE ID=" + id;
            stmt.executeUpdate(sql);
            sql = "SELECT COUNT(*) FROM " + TBL_WORKFLOW_STEPDEFINITION + " WHERE UNIQUE_TARGET=" + id;
            ResultSet rs = stmt.executeQuery(sql);
            rs.next();
            if (rs.getInt(1) > 0) {
                FxRemoveException dbe = new FxRemoveException("ex.stepdefinition.delete.used");
                LOG.error(dbe);
                throw dbe;
            }
            sql = "DELETE FROM " + TBL_WORKFLOW_STEPDEFINITION + " WHERE ID=" + id;
            if (stmt.executeUpdate(sql) == 0) {
                FxEntryInUseException eiu = new FxEntryInUseException("ex.stepdefinition.load.notFound");
                if (LOG.isInfoEnabled())
                    LOG.info(eiu);
                throw eiu;
            }
            success = true;
        } catch (SQLException exc) {
            // TODO add Database.childRecordsExistViolation
            /*if (Database.childRecordsExistViolation(exc)) {
               FxDeleteException dbe = new FxDeleteException("The StepDefinition is in use", exc);
               LOG.error(dbe);
               throw dbe;
               } else {*/
            throw new FxRemoveException(LOG, "ex.stepdefinition.delete", exc);
            //}
        } finally {
            Database.closeObjects(StepDefinitionEngineBean.class, con, stmt);
            if (!success) {
                EJBUtils.rollback(ctx);
            } else {
                StructureLoader.reloadWorkflows(FxContext.get().getDivisionId());
            }
        }
    }

}