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.TBL_WORKFLOW;
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.structure.FxEnvironment;
042: import com.flexive.shared.exceptions.*;
043: import com.flexive.shared.interfaces.*;
044: import com.flexive.shared.security.Role;
045: import com.flexive.shared.security.UserTicket;
046: import com.flexive.shared.workflow.Route;
047: import com.flexive.shared.workflow.Step;
048: import com.flexive.shared.workflow.Workflow;
049: import org.apache.commons.lang.StringUtils;
050: import org.apache.commons.logging.Log;
051: import org.apache.commons.logging.LogFactory;
052:
053: import javax.annotation.Resource;
054: import javax.ejb.*;
055: import java.sql.Connection;
056: import java.sql.PreparedStatement;
057: import java.sql.SQLException;
058: import java.sql.Statement;
059: import java.util.HashMap;
060: import java.util.Map;
061:
062: /**
063: * Class to handle the basic workflow setup.
064: *
065: * @author Gregor Schober (gregor.schober@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
066: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
067: */
068: @Stateless(name="WorkflowEngine")
069: @TransactionAttribute(TransactionAttributeType.SUPPORTS)
070: @TransactionManagement(TransactionManagementType.CONTAINER)
071: public class WorkflowEngineBean implements WorkflowEngine,
072: WorkflowEngineLocal {
073:
074: private static final transient Log LOG = LogFactory
075: .getLog(WorkflowEngineBean.class);
076:
077: @Resource
078: private SessionContext ctx;
079: @EJB
080: private StepEngineLocal stepEngine;
081: @EJB
082: private RouteEngineLocal routeEngine;
083: @EJB
084: private SequencerEngineLocal seq;
085:
086: /** {@inheritDoc} */
087: @TransactionAttribute(TransactionAttributeType.REQUIRED)
088: public void remove(long workflowId) throws FxApplicationException {
089: UserTicket ticket = FxContext.get().getTicket();
090: // Permission checks
091: FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement);
092:
093: // Do work...
094: Connection con = null;
095: Statement stmt = null;
096: String sql = null;
097: boolean success = false;
098: try {
099: // Obtain a database connection
100: con = Database.getDbConnection();
101: // First delete all steps within the workflow ..
102: stepEngine.removeSteps(workflowId);
103: // .. then delete the workflow itself
104: stmt = con.createStatement();
105: sql = "DELETE FROM " + TBL_WORKFLOW + " WHERE ID="
106: + workflowId;
107: int count = stmt.executeUpdate(sql);
108: // Check if the delete succeeded
109: if (count == 0) {
110: throw new FxNotFoundException(LOG,
111: "ex.workflow.notFound", workflowId);
112: }
113:
114: success = true;
115:
116: // Refesh active UserTickets
117: // TODO
118: //UserTicketImpl.refreshHavingWorkflow(workflowId);
119:
120: } catch (SQLException exc) {
121: throw new FxRemoveException(LOG, "ex.workflow.delete", exc,
122: workflowId, exc.getMessage());
123: } finally {
124: if (!success) {
125: ctx.setRollbackOnly();
126: } else {
127: StructureLoader.reloadWorkflows(FxContext.get()
128: .getDivisionId());
129: }
130: Database.closeObjects(WorkflowEngineBean.class, con, stmt);
131: }
132: }
133:
134: /**
135: * Checks if the given workflow is valid
136: * <p/>
137: * Throws a FxInvalidParameterException if the name or description is not valid.
138: *
139: * @param workflow Workflow to be checked
140: * @throws FxInvalidParameterException if the name is not valid
141: */
142: private void checkIfValid(Workflow workflow)
143: throws FxInvalidParameterException {
144: String name = workflow.getName();
145: String description = workflow.getDescription();
146:
147: // Name checks
148: if (StringUtils.isBlank(name)) {
149: throw new FxInvalidParameterException("NAME",
150: "ex.workflow.name.empty");
151: } else if (name.length() > 60) {
152: throw new FxInvalidParameterException("NAME",
153: "ex.workflow.name.length");
154: } else if (name.indexOf('\'') > -1 || name.indexOf('"') > -1) {
155: throw new FxInvalidParameterException("NAME",
156: "ex.workflow.name.char");
157: }
158:
159: // Description checks
160: if (description != null && description.length() > 1024) {
161: throw new FxInvalidParameterException("DESCRIPTION",
162: "ex.workflow.description.length");
163: }
164: }
165:
166: /** {@inheritDoc} */
167: @TransactionAttribute(TransactionAttributeType.REQUIRED)
168: public void update(Workflow workflow) throws FxApplicationException {
169:
170: UserTicket ticket = FxContext.get().getTicket();
171:
172: // Permission checks
173: FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement);
174:
175: Connection con = null;
176: PreparedStatement stmt = null;
177: String sql = "UPDATE " + TBL_WORKFLOW
178: + " SET NAME=?, DESCRIPTION=? WHERE ID=?";
179:
180: boolean success = false;
181: try {
182: // Sanity checks
183: checkIfValid(workflow);
184:
185: // Obtain a database connection
186: con = Database.getDbConnection();
187:
188: // Update the workflow instance
189: stmt = con.prepareStatement(sql);
190: stmt.setString(1, workflow.getName());
191: stmt.setString(2, StringUtils.defaultString(workflow
192: .getDescription()));
193: stmt.setLong(3, workflow.getId());
194:
195: stmt.executeUpdate();
196:
197: FxEnvironment fxEnvironment = CacheAdmin.getEnvironment();
198: // Remove steps?
199: for (Step step : fxEnvironment.getStepsByWorkflow(workflow
200: .getId())) {
201: if (!workflow.getSteps().contains(step)) {
202: // remove step
203: stepEngine.removeStep(step.getId());
204: }
205: }
206:
207: // Add/update steps, if necessary
208: Map<Long, Step> createdSteps = new HashMap<Long, Step>();
209: for (Step step : workflow.getSteps()) {
210: if (step.getId() < 0) {
211: long newStepId = stepEngine.createStep(step);
212: // map created steps using the old ID - if routes reference them
213: createdSteps.put(step.getId(), new Step(newStepId,
214: step));
215: } else {
216: //ACL changed?
217: if (fxEnvironment.getStep(step.getId()).getAclId() != step
218: .getAclId())
219: stepEngine.updateStep(step.getId(), step
220: .getAclId());
221: }
222: }
223:
224: // Remove routes?
225: Route[] dbRoutes = routeEngine.loadRoutes(workflow.getId());
226: for (Route route : dbRoutes) {
227: if (!workflow.getRoutes().contains(route)) {
228: // remove route
229: routeEngine.remove(route.getId());
230: }
231: }
232:
233: // add routes
234: for (Route route : workflow.getRoutes()) {
235: if (route.getId() < 0) {
236: long fromStepId = resolveTemporaryStep(
237: createdSteps, route.getFromStepId());
238: long toStepId = resolveTemporaryStep(createdSteps,
239: route.getToStepId());
240: routeEngine.create(fromStepId, toStepId, route
241: .getGroupId());
242: }
243: }
244:
245: success = true;
246: } catch (SQLException exc) {
247: if (Database.isUniqueConstraintViolation(exc)) {
248: throw new FxEntryExistsException("ex.workflow.exists");
249: } else {
250: throw new FxUpdateException(LOG, "ex.workflow.update",
251: exc, workflow.getName(), exc.getMessage());
252: }
253: } catch (Exception exc) {
254: throw new FxUpdateException(LOG, "ex.workflow.update", exc,
255: workflow.getName(), exc.getMessage());
256: } finally {
257: if (!success) {
258: ctx.setRollbackOnly();
259: } else {
260: StructureLoader.reloadWorkflows(FxContext.get()
261: .getDivisionId());
262: }
263: Database.closeObjects(WorkflowEngineBean.class, con, stmt);
264: }
265: }
266:
267: /**
268: * Resolve route references to steps that have not been created yet.
269: * If a step is added, a temporary negative index is used for identifying
270: * it in new routes. The createdSteps lookup table is used for mapping
271: * these internal negative IDs to the database-generated, persisted step IDs.
272: *
273: * @param createdSteps mapping between internal step IDs and the created steps
274: * @param stepId the step ID to be mapped (may be negative)
275: * @return the (positive) step ID that actually exists in the database
276: * @throws FxInvalidParameterException if the step could not be mapped
277: */
278: private long resolveTemporaryStep(Map<Long, Step> createdSteps,
279: long stepId) throws FxInvalidParameterException {
280: if (stepId < 0) {
281: if (!createdSteps.containsKey(stepId)) {
282: throw new FxInvalidParameterException("ROUTES",
283: "ex.workflow.route.referencedStep", stepId);
284: }
285: return createdSteps.get(stepId).getId();
286: } else {
287: return stepId;
288: }
289: }
290:
291: /** {@inheritDoc} */
292: @TransactionAttribute(TransactionAttributeType.REQUIRED)
293: public long create(Workflow workflow) throws FxApplicationException {
294: UserTicket ticket = FxContext.get().getTicket();
295: // Permission checks
296: FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement);
297:
298: // Do work ..
299: Connection con = null;
300: PreparedStatement stmt = null;
301: String sCurSql = null;
302: boolean success = false;
303: long id = -1;
304: try {
305: // Sanity checks
306: checkIfValid(workflow);
307:
308: // Obtain a database connection
309: con = Database.getDbConnection();
310:
311: // Create the new workflow instance
312: sCurSql = "INSERT INTO " + TBL_WORKFLOW
313: + " (ID,NAME,DESCRIPTION) VALUES (?,?,?)";
314: stmt = con.prepareStatement(sCurSql);
315: id = seq.getId(SequencerEngine.System.WORKFLOW);
316: stmt.setLong(1, id);
317: stmt.setString(2, workflow.getName());
318: stmt.setString(3, StringUtils.defaultString(workflow
319: .getDescription()));
320: stmt.executeUpdate();
321:
322: // create step(s)
323: final Map<Long, Step> createdSteps = new HashMap<Long, Step>();
324: for (Step step : workflow.getSteps()) {
325: final Step wfstep = new Step(-1, step
326: .getStepDefinitionId(), id, step.getAclId());
327: final long newStepId = stepEngine.createStep(wfstep);
328: createdSteps.put(step.getId(), new Step(newStepId,
329: wfstep));
330: }
331:
332: // create route(s)
333: for (Route route : workflow.getRoutes()) {
334: routeEngine.create(resolveTemporaryStep(createdSteps,
335: route.getFromStepId()), resolveTemporaryStep(
336: createdSteps, route.getToStepId()), route
337: .getGroupId());
338: }
339: success = true;
340: } catch (Exception exc) {
341: if (Database.isUniqueConstraintViolation(exc)) {
342: throw new FxEntryExistsException(LOG,
343: "ex.workflow.exists", workflow.getName());
344: } else {
345: throw new FxCreateException(LOG, "ex.workflow.create",
346: exc, exc.getMessage());
347: }
348: } finally {
349: if (!success) {
350: ctx.setRollbackOnly();
351: } else {
352: StructureLoader.reloadWorkflows(FxContext.get()
353: .getDivisionId());
354: }
355: Database.closeObjects(WorkflowEngineBean.class, con, stmt);
356: }
357: return id;
358: }
359: }
|