001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2008
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.ejb.beans.workflow;
034:
035: import com.flexive.core.Database;
036: import static com.flexive.core.DatabaseConst.*;
037: import com.flexive.core.structure.StructureLoader;
038: import com.flexive.shared.CacheAdmin;
039: import com.flexive.shared.FxContext;
040: import com.flexive.shared.content.FxPermissionUtils;
041: import com.flexive.shared.exceptions.*;
042: import com.flexive.shared.interfaces.SequencerEngine;
043: import com.flexive.shared.interfaces.SequencerEngineLocal;
044: import com.flexive.shared.interfaces.StepEngine;
045: import com.flexive.shared.interfaces.StepEngineLocal;
046: import com.flexive.shared.security.ACL.Category;
047: import com.flexive.shared.security.Role;
048: import com.flexive.shared.security.UserGroup;
049: import com.flexive.shared.security.UserTicket;
050: import com.flexive.shared.workflow.Step;
051: import com.flexive.shared.workflow.StepDefinition;
052: import com.flexive.shared.workflow.StepPermission;
053: import com.flexive.shared.workflow.StepPermissionEdit;
054: import org.apache.commons.logging.Log;
055: import org.apache.commons.logging.LogFactory;
056:
057: import javax.annotation.Resource;
058: import javax.ejb.*;
059: import java.sql.Connection;
060: import java.sql.ResultSet;
061: import java.sql.SQLException;
062: import java.sql.Statement;
063: import java.util.ArrayList;
064: import java.util.Hashtable;
065: import java.util.List;
066:
067: /**
068: * The StepImpl class provides functions to create, alter and query steps
069: * within a workflow.
070: *
071: * @author Gregor Schober (gregor.schober@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
072: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
073: */
074: @Stateless(name="StepEngine")
075: @TransactionAttribute(TransactionAttributeType.SUPPORTS)
076: @TransactionManagement(TransactionManagementType.CONTAINER)
077: public class StepEngineBean implements StepEngine, StepEngineLocal {
078:
079: private static final transient Log LOG = LogFactory
080: .getLog(StepEngineBean.class);
081:
082: @EJB
083: private SequencerEngineLocal seq;
084: @Resource
085: private SessionContext ctx;
086:
087: /**
088: * Check relate permissions for steps?
089: */
090: public static final boolean USE_RELATE_PERM = false;
091:
092: /** {@inheritDoc} */
093: @TransactionAttribute(TransactionAttributeType.REQUIRED)
094: public long createStep(Step step) throws FxApplicationException {
095: UserTicket ticket = FxContext.get().getTicket();
096: // Security checks
097: FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement);
098:
099: // Create the new step
100: Statement stmt = null;
101: String sql;
102: Connection con = null;
103: boolean success = false;
104: try {
105:
106: // Obtain a database connection
107: con = Database.getDbConnection();
108:
109: // Create any needed unique target step
110: try {
111: StepDefinition def;
112: def = CacheAdmin.getEnvironment().getStepDefinition(
113: step.getStepDefinitionId());
114: if (def.getUniqueTargetId() != -1) {
115: createStep(new Step(-1, def.getUniqueTargetId(),
116: step.getWorkflowId(), step.getAclId()));
117: }
118: } catch (FxLoadException exc) {
119: throw new FxCreateException(LOG,
120: "ex.step.create.uniqueTargets", exc, exc
121: .getMessage());
122: } catch (FxNotFoundException exc) {
123: throw new FxCreateException(
124: "ex.stepdefinition.load.notFound", step
125: .getStepDefinitionId());
126: }
127:
128: // Create the step
129: long newStepId = seq.getId(SequencerEngine.System.STEP);
130: try {
131: stmt = con.createStatement();
132: sql = "INSERT INTO " + TBL_STEP
133: + " (ID, STEPDEF, WORKFLOW, ACL) VALUES ("
134: + newStepId + "," + step.getStepDefinitionId()
135: + "," + step.getWorkflowId() + ","
136: + step.getAclId() + ")";
137: stmt.executeUpdate(sql);
138: } catch (SQLException exc) {
139: // Ignore unique constraint.
140: if (!Database.isUniqueConstraintViolation(exc)) {
141: throw exc;
142: }
143: // get existing workflow, return its ID
144: sql = "SELECT ID FROM " + TBL_STEP + " WHERE STEPDEF="
145: + step.getStepDefinitionId() + " AND WORKFLOW="
146: + step.getWorkflowId();
147: ResultSet rs = stmt.executeQuery(sql);
148: if (rs != null && rs.next()) {
149: // return existing step ID as "new step ID"
150: newStepId = rs.getLong(1);
151: } else {
152: throw new FxCreateException(LOG,
153: "ex.step.exists.load", exc, exc
154: .getMessage());
155: }
156: }
157:
158: // Refresh all UserTicket that are affected.
159: // Do NOT use refreshHavingWorkflow since this function adds a workflow to tickets, so this function would
160: // have no effect.
161: // TODO
162: //UserTicketImpl.refreshHavingACL(ACLId);
163: success = true;
164:
165: // Return the new id
166: return newStepId;
167:
168: } catch (SQLException exc) {
169: throw new FxCreateException(LOG, "ex.step.create", exc, exc
170: .getMessage());
171: } finally {
172: if (!success) {
173: ctx.setRollbackOnly();
174: } else {
175: StructureLoader.reloadWorkflows(FxContext.get()
176: .getDivisionId());
177: }
178: Database.closeObjects(StepEngineBean.class, con, stmt);
179: }
180: }
181:
182: /** {@inheritDoc} */
183: public List<StepPermission> loadAllStepsForUser(long userId)
184: throws FxApplicationException {
185: UserTicket ticket = FxContext.get().getTicket();
186: // Select all step ids
187: final String sql =
188: // 1 , 2 , 3
189: "SELECT DISTINCT step.ID,aclug.ACL,step.WORKFLOW,"
190: // 4 , 5 , 6 , 7 , 8 , 9 , 10
191: + " aclug.PEDIT,aclug.PREAD,aclug.PREMOVE,aclug.PEXPORT,aclug.PREL,aclug.PCREATE,step.STEPDEF"
192: + " FROM "
193: + TBL_ACLS
194: + " acl,"
195: + TBL_ASSIGN_ACLS
196: + " aclug,"
197: + TBL_STEP
198: + " step"
199: + " WHERE"
200: + " aclug.ACL=acl.ID"
201: + " AND acl.CAT_TYPE="
202: + Category.WORKFLOW.getId()
203: + " AND aclug.USERGROUP IN (SELECT DISTINCT USERGROUP FROM "
204: + TBL_ASSIGN_GROUPS + " WHERE ACCOUNT=" + userId
205: + " AND USERGROUP<>" + UserGroup.GROUP_OWNER + ")"
206: + " AND step.ACL=acl.ID";
207:
208: // Security
209: if (!ticket.isGlobalSupervisor()) {
210: if (ticket.getUserId() != userId) {
211: FxNoAccessException na = new FxNoAccessException(
212: "You may not load the Steps for a other user");
213: if (LOG.isInfoEnabled())
214: LOG.info(na);
215: throw na;
216: }
217: }
218:
219: // Obtain a database connection
220: Connection con = null;
221: Statement stmt = null;
222: try {
223: con = Database.getDbConnection();
224:
225: // Load all steps in the database
226: stmt = con.createStatement();
227: ResultSet rs = stmt.executeQuery(sql);
228: //ArrayList result = new ArrayList(50);
229: Hashtable<Integer, StepPermission> result = new Hashtable<Integer, StepPermission>(
230: 50);
231:
232: while (rs != null && rs.next()) {
233: // Fill in a step object
234: Integer stepId = rs.getInt(1);
235: int workflowId = rs.getInt(3);
236: boolean mayEdit = rs.getBoolean(4);
237: boolean mayRead = rs.getBoolean(5);
238: boolean mayDelete = rs.getBoolean(6);
239: boolean mayExport = rs.getBoolean(7);
240: boolean mayRelate = rs.getBoolean(8);
241: boolean mayCreate = rs.getBoolean(9);
242: int stepDefinitionId = rs.getInt(10);
243: StepPermissionEdit data;
244: StepPermission stepPerm = result.get(stepId);
245: if (stepPerm == null) {
246: data = new StepPermissionEdit(new StepPermission(
247: stepId, stepDefinitionId, workflowId,
248: mayRead, mayEdit, mayRelate, mayDelete,
249: mayExport, mayCreate));
250: } else {
251: data = new StepPermissionEdit(stepPerm);
252: if (mayDelete)
253: data.setMayDelete(true);
254: if (mayEdit)
255: data.setMayEdit(true);
256: if (mayExport)
257: data.setMayExport(true);
258: if (mayRelate)
259: data.setMayRelate(true);
260: if (mayRead)
261: data.setMayRead(true);
262: if (mayCreate)
263: data.setMayCreate(true);
264: }
265: result.put(stepId, data);
266: }
267:
268: return new ArrayList<StepPermission>(result.values());
269: } catch (SQLException exc) {
270: throw new FxLoadException(LOG, "ex.step.load.user", exc,
271: userId, exc.getMessage());
272: } finally {
273: Database.closeObjects(StepEngineBean.class, con, stmt);
274: }
275:
276: }
277:
278: /** {@inheritDoc} */
279: @TransactionAttribute(TransactionAttributeType.REQUIRED)
280: public void removeSteps(long workflowId)
281: throws FxApplicationException {
282: deleteStep(workflowId, true);
283: }
284:
285: /** {@inheritDoc} */
286: @TransactionAttribute(TransactionAttributeType.REQUIRED)
287: public void removeStep(long stepId) throws FxApplicationException {
288: deleteStep(stepId, false);
289: }
290:
291: /**
292: * Deletes all steps within a workflow, or a specific step itself.
293: * <p/>
294: * The caller has to be within ROLE_WORKFLOWMANAGEMENT.<br>
295: * <br>
296: *
297: * @param objectId the workflow id if parameter isWorkflow equals true, or a specific step id
298: * @param isWorkflow if true the objectId parameter refers to a workflow, if false the objectId refers to
299: * a specific step id
300: * @throws FxApplicationException TODO
301: */
302: private void deleteStep(long objectId, boolean isWorkflow)
303: throws FxApplicationException {
304: UserTicket ticket = FxContext.get().getTicket();
305: // Security checks
306: FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement);
307:
308: long workflowId = -1;
309: // Check if the workflow exists at all, throws FxNotFoundException
310: if (isWorkflow) {
311: workflowId = CacheAdmin.getEnvironment().getWorkflow(
312: objectId).getId();
313: }
314:
315: // Create the new step
316: Connection con = null;
317: Statement stmt = null;
318: String sql;
319: boolean success = false;
320: try {
321:
322: // Obtain a database connection
323: con = Database.getDbConnection();
324:
325: // May only delete a single step if its not the unique target of a other step
326: if (!isWorkflow) {
327: Step step = CacheAdmin.getEnvironment().getStep(
328: objectId);
329: stmt = con.createStatement();
330: sql = "SELECT COUNT(*) FROM " + TBL_STEP
331: + " WHERE WORKFLOW=" + step.getWorkflowId()
332: + " AND STEPDEF IN (SELECT def.ID FROM "
333: + TBL_STEPDEFINITION + " def, " + TBL_STEP
334: + " stp WHERE stp.ID=" + step.getId()
335: + " AND stp.STEPDEF=def.UNIQUE_TARGET)";
336: ResultSet rs = stmt.executeQuery(sql);
337: int count = 0;
338: if (rs != null && rs.next())
339: count = rs.getInt(1);
340: if (count != 0) {
341: throw new FxEntryInUseException(
342: "ex.step.delete.uniqueTarget", objectId);
343: }
344: stmt.close();
345: }
346:
347: // Delete all routes that use the step(s)
348: if (isWorkflow) {
349: sql = "DELETE FROM " + TBL_ROUTES
350: + " WHERE FROM_STEP in (select id from "
351: + TBL_STEP + " WHERE WORKFLOW=" + workflowId
352: + ")";
353: } else {
354: sql = "DELETE FROM " + TBL_ROUTES + " WHERE FROM_STEP="
355: + objectId + " OR TO_STEP=" + objectId;
356: }
357: stmt = con.createStatement();
358: stmt.executeUpdate(sql);
359: stmt.close();
360:
361: // Delete the step(s) itself
362: stmt = con.createStatement();
363: sql = "DELETE FROM " + TBL_STEP + " WHERE "
364: + (isWorkflow ? "WORKFLOW=" : "ID=") + objectId;
365: stmt.executeUpdate(sql);
366: // Update the active UserTickets, only if commited or we get the old values anyway
367: // TODO
368: //UserTicketImpl.refreshHavingWorkflow(workflowId);
369: success = true;
370: } catch (SQLException exc) {
371: if (isWorkflow) {
372: throw new FxRemoveException(LOG,
373: "ex.step.delete.workflow", exc, exc
374: .getMessage());
375: } else {
376: throw new FxRemoveException(LOG, "ex.step.delete", exc,
377: exc.getMessage());
378: }
379: } finally {
380: if (!success) {
381: ctx.setRollbackOnly();
382: } else {
383: StructureLoader.reloadWorkflows(FxContext.get()
384: .getDivisionId());
385: }
386: Database.closeObjects(StepEngineBean.class, con, stmt);
387: }
388: }
389:
390: /** {@inheritDoc} */
391: @TransactionAttribute(TransactionAttributeType.REQUIRED)
392: public void updateStep(long stepId, long aclId)
393: throws FxApplicationException {
394: UserTicket ticket = FxContext.get().getTicket();
395: // Security checks
396: FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement);
397:
398: // Load the step
399: try {
400: CacheAdmin.getEnvironment().getStep(stepId);
401: } catch (Exception exc) {
402: throw new FxUpdateException(exc);
403: }
404:
405: // Do work ..
406: Statement stmt = null;
407: String sql;
408: Connection con = null;
409: boolean success = false;
410: try {
411:
412: // Obtain a database connection
413: con = Database.getDbConnection();
414:
415: // Update the step
416: stmt = con.createStatement();
417: sql = "UPDATE " + TBL_STEP + " SET ACL=" + aclId
418: + " WHERE ID=" + stepId;
419: int ucount = stmt.executeUpdate(sql);
420:
421: // Is the step defined at all?
422: if (ucount == 0) {
423: throw new FxNotFoundException("ex.step.notFound.id",
424: stepId);
425: }
426:
427: // Update the active UserTickets
428: // Refresh all tickets having the new acl (workflow access might be added) and refreshHavingUser all that
429: // have the affected workflow (workflow access may be removed)
430: // TODO
431: //UserTicketImpl.refreshHavingACL(aclId);
432: //UserTicketImpl.refreshHavingWorkflow(stp.getWorkflowId());
433: success = true;
434:
435: } catch (SQLException exc) {
436: throw new FxUpdateException(LOG, "ex.step.update", exc, exc
437: .getMessage());
438: } finally {
439: if (!success) {
440: ctx.setRollbackOnly();
441: } else {
442: StructureLoader.reloadWorkflows(FxContext.get()
443: .getDivisionId());
444: }
445: Database.closeObjects(StepEngineBean.class, con, stmt);
446: }
447: }
448: }
|