Java tutorial
/*************************************************************** * 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.FxSystemSequencer; import com.flexive.shared.content.FxPermissionUtils; import com.flexive.shared.exceptions.*; import com.flexive.shared.interfaces.SequencerEngineLocal; import com.flexive.shared.interfaces.StepEngine; import com.flexive.shared.interfaces.StepEngineLocal; import com.flexive.shared.security.ACLCategory; import com.flexive.shared.security.Role; import com.flexive.shared.security.UserGroup; import com.flexive.shared.security.UserTicket; import com.flexive.shared.structure.FxEnvironment; import com.flexive.shared.workflow.Step; import com.flexive.shared.workflow.StepDefinition; import com.flexive.shared.workflow.StepPermission; import com.flexive.shared.workflow.StepPermissionEdit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.annotation.Resource; import javax.ejb.*; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import static com.flexive.core.DatabaseConst.*; /** * The StepImpl class provides functions to create, alter and query steps * within a workflow. * * @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) */ @Stateless(name = "StepEngine", mappedName = "StepEngine") @TransactionAttribute(TransactionAttributeType.SUPPORTS) @TransactionManagement(TransactionManagementType.CONTAINER) public class StepEngineBean implements StepEngine, StepEngineLocal { private static final Log LOG = LogFactory.getLog(StepEngineBean.class); @EJB private SequencerEngineLocal seq; @Resource private SessionContext ctx; /** * Check relate permissions for steps? */ public static final boolean USE_RELATE_PERM = false; /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public long createStep(Step step) throws FxApplicationException { UserTicket ticket = FxContext.getUserTicket(); // Security checks FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement); // Create the new step Statement stmt = null; String sql; Connection con = null; boolean success = false; try { // Obtain a database connection con = Database.getDbConnection(); // Create any needed unique target step try { StepDefinition def; def = CacheAdmin.getEnvironment().getStepDefinition(step.getStepDefinitionId()); if (def.getUniqueTargetId() != -1) { createStep(new Step(-1, def.getUniqueTargetId(), step.getWorkflowId(), step.getAclId())); } } catch (FxLoadException exc) { throw new FxCreateException(LOG, "ex.step.create.uniqueTargets", exc, exc.getMessage()); } catch (FxNotFoundException exc) { throw new FxCreateException("ex.stepdefinition.load.notFound", step.getStepDefinitionId()); } //check if that step already exists long newStepId; stmt = con.createStatement(); sql = "SELECT ID FROM " + TBL_WORKFLOW_STEP + " WHERE STEPDEF=" + step.getStepDefinitionId() + " AND WORKFLOW=" + step.getWorkflowId(); ResultSet rs = stmt.executeQuery(sql); if (rs != null && rs.next()) { // return existing step ID as "new step ID" newStepId = rs.getLong(1); } else { // Create the step newStepId = seq.getId(FxSystemSequencer.STEP); // get maximum position sql = "SELECT MAX(pos) FROM " + TBL_WORKFLOW_STEP + " WHERE workflow=" + step.getWorkflowId(); ResultSet rs2 = stmt.executeQuery(sql); final int newPos; if (rs2.next()) { newPos = rs2.getInt(1) + 1; // 1-based } else { newPos = 1; // should not happen } sql = "INSERT INTO " + TBL_WORKFLOW_STEP + " (ID, STEPDEF, WORKFLOW, ACL, POS) VALUES (" + newStepId + "," + step.getStepDefinitionId() + "," + step.getWorkflowId() + "," + step.getAclId() + ", " + newPos + ")"; stmt.executeUpdate(sql); } // Refresh all UserTicket that are affected. // Do NOT use refreshHavingWorkflow since this function adds a workflow to tickets, so this function would // have no effect. // TODO //UserTicketImpl.refreshHavingACL(ACLId); success = true; // Return the new id return newStepId; } catch (SQLException exc) { throw new FxCreateException(LOG, "ex.step.create", exc, exc.getMessage()); } finally { Database.closeObjects(StepEngineBean.class, con, stmt); if (!success) { EJBUtils.rollback(ctx); } else { StructureLoader.reloadWorkflows(FxContext.get().getDivisionId()); } } } /** * {@inheritDoc} */ @Override public List<StepPermission> loadAllStepsForUser(long userId) throws FxApplicationException { UserTicket ticket = FxContext.getUserTicket(); // Select all step ids final String sql = // 1 , 2 , 3 "SELECT DISTINCT step.ID,aclug.ACL,step.WORKFLOW," // 4 , 5 , 6 , 7 , 8 , 9 , 10 + " aclug.PEDIT,aclug.PREAD,aclug.PREMOVE,aclug.PEXPORT,aclug.PREL,aclug.PCREATE,step.STEPDEF" + " FROM " + TBL_ACLS + " acl," + TBL_ACLS_ASSIGNMENT + " aclug," + TBL_WORKFLOW_STEP + " step" + " WHERE" + " aclug.ACL=acl.ID" + " AND acl.CAT_TYPE=" + ACLCategory.WORKFLOW.getId() + " AND aclug.USERGROUP IN (SELECT DISTINCT USERGROUP FROM " + TBL_ASSIGN_GROUPS + " WHERE ACCOUNT=" + userId + " AND USERGROUP<>" + UserGroup.GROUP_OWNER + ")" + " AND step.ACL=acl.ID"; // Security if (!ticket.isGlobalSupervisor()) { if (ticket.getUserId() != userId) { FxNoAccessException na = new FxNoAccessException("You may not load the Steps for a other user"); if (LOG.isInfoEnabled()) LOG.info(na); throw na; } } // Obtain a database connection Connection con = null; Statement stmt = null; try { con = Database.getDbConnection(); // Load all steps in the database stmt = con.createStatement(); ResultSet rs = stmt.executeQuery(sql); //ArrayList result = new ArrayList(50); Hashtable<Integer, StepPermission> result = new Hashtable<Integer, StepPermission>(50); while (rs != null && rs.next()) { // Fill in a step object Integer stepId = rs.getInt(1); int workflowId = rs.getInt(3); boolean mayEdit = rs.getBoolean(4); boolean mayRead = rs.getBoolean(5); boolean mayDelete = rs.getBoolean(6); boolean mayExport = rs.getBoolean(7); boolean mayRelate = rs.getBoolean(8); boolean mayCreate = rs.getBoolean(9); int stepDefinitionId = rs.getInt(10); StepPermissionEdit data; StepPermission stepPerm = result.get(stepId); if (stepPerm == null) { data = new StepPermissionEdit(new StepPermission(stepId, stepDefinitionId, workflowId, mayRead, mayEdit, mayRelate, mayDelete, mayExport, mayCreate)); } else { data = new StepPermissionEdit(stepPerm); if (mayDelete) data.setMayDelete(true); if (mayEdit) data.setMayEdit(true); if (mayExport) data.setMayExport(true); if (mayRelate) data.setMayRelate(true); if (mayRead) data.setMayRead(true); if (mayCreate) data.setMayCreate(true); } result.put(stepId, data); } return new ArrayList<StepPermission>(result.values()); } catch (SQLException exc) { throw new FxLoadException(LOG, "ex.step.load.user", exc, userId, exc.getMessage()); } finally { Database.closeObjects(StepEngineBean.class, con, stmt); } } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void removeSteps(long workflowId) throws FxApplicationException { deleteStep(workflowId, true); } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void removeStep(long stepId) throws FxApplicationException { deleteStep(stepId, false); } /** * Deletes all steps within a workflow, or a specific step itself. * <p/> * The caller has to be within ROLE_WORKFLOWMANAGEMENT.<br> * <br> * * @param objectId the workflow id if parameter isWorkflow equals true, or a specific step id * @param isWorkflow if true the objectId parameter refers to a workflow, if false the objectId refers to * a specific step id * @throws FxApplicationException TODO */ private void deleteStep(long objectId, boolean isWorkflow) throws FxApplicationException { UserTicket ticket = FxContext.getUserTicket(); // Security checks FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement); final FxEnvironment env = CacheAdmin.getEnvironment(); long workflowId = -1; // Check if the workflow exists at all, throws FxNotFoundException if (isWorkflow) workflowId = env.getWorkflow(objectId).getId(); // Create the new step Connection con = null; Statement stmt = null; String sql; boolean success = false; try { // Obtain a database connection con = Database.getDbConnection(); // May only delete a single step if its not the unique target of a other step if (!isWorkflow) { Step step = env.getStep(objectId); stmt = con.createStatement(); sql = "SELECT COUNT(*) FROM " + TBL_WORKFLOW_STEP + " WHERE WORKFLOW=" + step.getWorkflowId() + " AND STEPDEF IN (SELECT def.ID FROM " + TBL_WORKFLOW_STEPDEFINITION + " def, " + TBL_WORKFLOW_STEP + " stp WHERE stp.ID=" + step.getId() + " AND stp.STEPDEF=def.UNIQUE_TARGET)"; ResultSet rs = stmt.executeQuery(sql); int count = 0; if (rs != null && rs.next()) count = rs.getInt(1); if (count != 0) { throw new FxEntryInUseException("ex.step.delete.uniqueTarget", objectId); } stmt.close(); } // Delete all routes that use the step(s) if (isWorkflow) { sql = "DELETE FROM " + TBL_WORKFLOW_ROUTES + " WHERE FROM_STEP in (select id from " + TBL_WORKFLOW_STEP + " WHERE WORKFLOW=" + workflowId + ")"; } else { sql = "DELETE FROM " + TBL_WORKFLOW_ROUTES + " WHERE FROM_STEP=" + objectId + " OR TO_STEP=" + objectId; } stmt = con.createStatement(); stmt.executeUpdate(sql); stmt.close(); // Delete the step(s) itself stmt = con.createStatement(); sql = "DELETE FROM " + TBL_WORKFLOW_STEP + " WHERE " + (isWorkflow ? "WORKFLOW=" : "ID=") + objectId; stmt.executeUpdate(sql); success = true; } catch (SQLException exc) { if (isWorkflow) { if (StorageManager.isForeignKeyViolation(exc)) throw new FxRemoveException("ex.step.delete.workflow.inUse", env.getWorkflow(objectId).getName()); throw new FxRemoveException(LOG, "ex.step.delete.workflow", exc, exc.getMessage()); } else { if (StorageManager.isForeignKeyViolation(exc)) { String stepName; try { Step step = env.getStep(objectId); stepName = env.getStepDefinition(step.getStepDefinitionId()).getName(); } catch (Exception e) { stepName = "unknown"; } throw new FxRemoveException("ex.step.delete.inUse", stepName); } throw new FxRemoveException(LOG, "ex.step.delete", exc, exc.getMessage()); } } finally { Database.closeObjects(StepEngineBean.class, con, stmt); if (success) { if (!isWorkflow) //dont reload if removing for a workflow StructureLoader.reloadWorkflows(FxContext.get().getDivisionId()); } } } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void updateStep(long stepId, long aclId, int position) throws FxApplicationException { UserTicket ticket = FxContext.getUserTicket(); // Security checks FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement); // Load the step try { CacheAdmin.getEnvironment().getStep(stepId); } catch (Exception exc) { throw new FxUpdateException(exc); } // Do work .. Statement stmt = null; String sql; Connection con = null; boolean success = false; try { // Obtain a database connection con = Database.getDbConnection(); // Update the step stmt = con.createStatement(); sql = "UPDATE " + TBL_WORKFLOW_STEP + " SET ACL=" + aclId + ", POS=" + position + " WHERE ID=" + stepId; int ucount = stmt.executeUpdate(sql); // Is the step defined at all? if (ucount == 0) { throw new FxNotFoundException("ex.step.notFound.id", stepId); } // Update the active UserTickets // Refresh all tickets having the new acl (workflow access might be added) and refreshHavingUser all that // have the affected workflow (workflow access may be removed) // TODO //UserTicketImpl.refreshHavingACL(aclId); //UserTicketImpl.refreshHavingWorkflow(stp.getWorkflowId()); success = true; } catch (SQLException exc) { throw new FxUpdateException(LOG, "ex.step.update", exc, exc.getMessage()); } finally { Database.closeObjects(StepEngineBean.class, con, stmt); if (!success) { EJBUtils.rollback(ctx); } else { StructureLoader.reloadWorkflows(FxContext.get().getDivisionId()); } } } }