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.ML;
037: import static com.flexive.core.DatabaseConst.TBL_STEPDEFINITION;
038: import com.flexive.core.structure.StructureLoader;
039: import static com.flexive.shared.CacheAdmin.getEnvironment;
040: import com.flexive.shared.FxContext;
041: import com.flexive.shared.FxSharedUtils;
042: import com.flexive.shared.CacheAdmin;
043: import com.flexive.shared.content.FxPermissionUtils;
044: import com.flexive.shared.exceptions.*;
045: import com.flexive.shared.interfaces.*;
046: import com.flexive.shared.security.Role;
047: import com.flexive.shared.security.UserTicket;
048: import com.flexive.shared.value.FxString;
049: import com.flexive.shared.workflow.Step;
050: import com.flexive.shared.workflow.StepDefinition;
051: import com.flexive.shared.workflow.Workflow;
052: import org.apache.commons.lang.StringUtils;
053: import org.apache.commons.logging.Log;
054: import org.apache.commons.logging.LogFactory;
055:
056: import javax.annotation.Resource;
057: import javax.ejb.*;
058: import java.sql.*;
059: import java.util.ArrayList;
060: import java.util.HashSet;
061: import java.util.List;
062: import java.util.Set;
063:
064: /**
065: * Class for the maintainance of Global Step Definitions.
066: *
067: * @author Gregor Schober (gregor.schober@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
068: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
069: * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
070: */
071: @Stateless(name="StepDefinitionEngine")
072: @TransactionAttribute(TransactionAttributeType.SUPPORTS)
073: @TransactionManagement(TransactionManagementType.CONTAINER)
074: public class StepDefinitionEngineBean implements StepDefinitionEngine,
075: StepDefinitionEngineLocal {
076:
077: private static final transient Log LOG = LogFactory
078: .getLog(StepDefinitionEngineBean.class);
079:
080: @EJB
081: private StepEngineLocal stepEngine;
082: @EJB
083: private SequencerEngineLocal seq;
084: @Resource
085: private SessionContext ctx;
086:
087: /**
088: * Checks if a unique target id is valid to use.
089: *
090: * @param id the step definition ID to be checked
091: * @param uniqueTargetId the unique target to be checked
092: * @throws FxInvalidParameterException if the unique target may not be used
093: */
094: private void checkValidUniqueTarget(long id, long uniqueTargetId)
095: throws FxInvalidParameterException {
096: // TODO check for cycles
097: if (uniqueTargetId >= 0) {
098: if (id == uniqueTargetId) {
099: // step may not reference itself
100: throw new FxInvalidParameterException("UNIQUETARGET",
101: LOG,
102: "ex.stepdefinition.uniqueTarget.circular.self",
103: id);
104: }
105: try {
106: // check if the target exists and check for cycles
107: HashSet<Long> visitedStepDefinitions = new HashSet<Long>();
108: if (id != -1) {
109: // must not reach "id" from unique target
110: visitedStepDefinitions.add(id);
111: }
112: checkForCycles(visitedStepDefinitions, uniqueTargetId);
113: } catch (FxRuntimeException exc) {
114: if (exc.getCause() instanceof FxNotFoundException) {
115: // provide a clear message if the unique target does not exist
116: throw new FxInvalidParameterException(
117: "UNIQUETARGET", LOG,
118: "ex.stepdefinition.uniqueTarget.notFound");
119: } else {
120: throw exc;
121: }
122: }
123: }
124: }
125:
126: /**
127: * Follows the unique target of the current node and checks if there are cycles
128: * in the step definition graph.
129: *
130: * @param visitedStepDefinitions a set storing already visited nodes
131: * @param stepDefinitionId the currently visited node ID
132: * @throws com.flexive.shared.exceptions.FxInvalidParameterException if a cycle was detected
133: */
134: private void checkForCycles(Set<Long> visitedStepDefinitions,
135: long stepDefinitionId) throws FxInvalidParameterException {
136: if (!visitedStepDefinitions.add(stepDefinitionId)) {
137: String id = "" + stepDefinitionId;
138: try {
139: id = CacheAdmin.getEnvironment().getStepDefinition(
140: stepDefinitionId).getLabel()
141: + " (Id: " + stepDefinitionId + ")";
142: } catch (Exception e) {
143: //ignore
144: }
145: throw new FxInvalidParameterException("UNIQUETARGET", LOG,
146: "ex.stepdefinition.uniqueTarget.circular", id);
147: }
148: StepDefinition sd = getEnvironment().getStepDefinition(
149: stepDefinitionId);
150: if (sd.getUniqueTargetId() != -1) {
151: checkForCycles(visitedStepDefinitions, sd
152: .getUniqueTargetId());
153: }
154: }
155:
156: /** {@inheritDoc} */
157: @TransactionAttribute(TransactionAttributeType.REQUIRED)
158: public long create(StepDefinition stepDefinition)
159: throws FxApplicationException {
160:
161: final UserTicket ticket = FxContext.get().getTicket();
162: // Security checks
163: FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement);
164: // Create the new step
165: Connection con = null;
166: PreparedStatement ps = null;
167: String sql;
168: FxString name = stepDefinition.getLabel();
169: if (name.isEmpty())
170: throw new FxInvalidParameterException("NAME",
171: "ex.stepdefinition.name.empty");
172: String description = stepDefinition.getDescription();
173: long uniqueTargetId = stepDefinition.getUniqueTargetId();
174: boolean success = false;
175: long newId = -1;
176: try {
177: // Check the unique target
178: checkValidUniqueTarget(-1, uniqueTargetId);
179:
180: if (StringUtils.isBlank(name.getDefaultTranslation())) {
181: FxInvalidParameterException ip = new FxInvalidParameterException(
182: "NAME", "ex.stepdefinition.name.empty");
183: if (LOG.isDebugEnabled())
184: LOG.debug(ip);
185: throw ip;
186: }
187:
188: // Obtain a database connection
189: con = Database.getDbConnection();
190:
191: // Create the new workflow instance
192: sql = "INSERT INTO " + TBL_STEPDEFINITION
193: + " (ID, DESCRIPTION,UNIQUE_TARGET) VALUES (?,?,?)";
194: ps = con.prepareStatement(sql);
195: newId = seq.getId(SequencerEngine.System.STEPDEFINITION);
196: ps.setLong(1, newId);
197: ps.setString(2, description);
198: if (uniqueTargetId != -1) {
199: ps.setLong(3, uniqueTargetId);
200: } else {
201: ps.setNull(3, Types.NUMERIC);
202: }
203: if (ps.executeUpdate() != 1)
204: throw new FxCreateException(LOG,
205: "ex.stepdefinition.create");
206: Database.storeFxString(name, con, TBL_STEPDEFINITION,
207: "name", "id", newId);
208: success = true;
209: } catch (FxInvalidParameterException exc) {
210: throw exc;
211: } catch (Exception exc) {
212: if (Database.isUniqueConstraintViolation(exc)) {
213: FxEntryExistsException ee = new FxEntryExistsException(
214: "ex.stepdefinition.name.exists", name
215: .getDefaultTranslation());
216: if (LOG.isDebugEnabled())
217: LOG.debug(ee);
218: throw ee;
219: } else {
220: FxCreateException ce = new FxCreateException(LOG,
221: "ex.stepdefinition.create", exc);
222: LOG.error("Internal error: " + exc.getMessage(), ce);
223: throw ce;
224: }
225: } finally {
226: if (!success) {
227: ctx.setRollbackOnly();
228: } else {
229: StructureLoader.reloadWorkflows(FxContext.get()
230: .getDivisionId());
231: }
232: Database.closeObjects(StepDefinitionEngineBean.class, con,
233: ps);
234: }
235: return newId;
236: }
237:
238: /** {@inheritDoc} */
239: @TransactionAttribute(TransactionAttributeType.REQUIRED)
240: public void update(StepDefinition stepDefinition)
241: throws FxApplicationException {
242:
243: final UserTicket ticket = FxContext.get().getTicket();
244: final FxContext ri = FxContext.get();
245:
246: // Security checks
247: FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement);
248:
249: StepDefinition orgDefinition;
250: try {
251: // Lookup the stepDefinition, throws FxNotFoundException (+FxDbException)
252: orgDefinition = getEnvironment().getStepDefinition(
253: stepDefinition.getId());
254: } catch (Exception exc) {
255: throw new FxUpdateException(exc.getMessage(), exc);
256: }
257:
258: // Unique target checks
259: boolean uniqueTargetAdded = stepDefinition.getId() != -1
260: && stepDefinition.getUniqueTargetId() != -1
261: && stepDefinition.getUniqueTargetId() != orgDefinition
262: .getUniqueTargetId();
263:
264: // Check the unique target, throws FxInvalidParameterException if target is invalid (+FxDbException)
265: if (stepDefinition.getUniqueTargetId() != -1) {
266: checkValidUniqueTarget(stepDefinition.getId(),
267: stepDefinition.getUniqueTargetId());
268: }
269:
270: // Sanity checks
271: if (stepDefinition.getLabel() == null
272: || stepDefinition.getLabel().isEmpty()) {
273: throw new FxInvalidParameterException("NAME",
274: "ex.stepdefinition.name.empty");
275: }
276:
277: Connection con = null;
278: PreparedStatement stmt = null;
279: String sql;
280: boolean success = false;
281: try {
282:
283: // Obtain a database connection
284: con = Database.getDbConnection();
285:
286: // store label
287: Database.storeFxString(stepDefinition.getLabel(), con,
288: TBL_STEPDEFINITION, "name", "id", stepDefinition
289: .getId());
290:
291: sql = "UPDATE " + TBL_STEPDEFINITION
292: + " SET DESCRIPTION=?,UNIQUE_TARGET=? WHERE ID=?";
293: stmt = con.prepareStatement(sql);
294: stmt.setString(1, stepDefinition.getDescription());
295: if (stepDefinition.getUniqueTargetId() != -1) {
296: stmt.setLong(2, stepDefinition.getUniqueTargetId());
297: } else {
298: stmt.setNull(2, Types.NUMERIC);
299: }
300: stmt.setLong(3, stepDefinition.getId());
301:
302: int updateCount = stmt.executeUpdate();
303:
304: if (updateCount == 0) {
305: FxNotFoundException nfe = new FxNotFoundException(
306: "ex.stepdefinition.load.notFound",
307: stepDefinition.getId());
308: if (LOG.isInfoEnabled())
309: LOG.info(nfe);
310: throw nfe;
311: } else if (updateCount != 1) {
312: FxUpdateException dbe = new FxUpdateException(
313: "ex.stepdefinition.update.rows");
314: LOG.error(dbe);
315: throw dbe;
316: }
317:
318: // Unique target has to exist for every workflow
319: long workflowId;
320: if (uniqueTargetAdded) {
321: try {
322: ri.runAsSystem();
323: List<StepDefinition> stepDefinitionList = new ArrayList<StepDefinition>();
324: stepDefinitionList.add(stepDefinition);
325: // Do this for all existing workflows ..
326: for (Workflow workflow : getEnvironment()
327: .getWorkflows()) {
328: workflowId = workflow.getId();
329: if (FxSharedUtils
330: .getUsedStepDefinitions(
331: workflow.getSteps(),
332: stepDefinitionList).size() > 0) {
333: // create step IF the step definition is used by the workflow
334: stepEngine.createStep(new Step(-1,
335: stepDefinition.getUniqueTargetId(),
336: workflowId, getEnvironment()
337: .getStepByDefinition(
338: workflowId,
339: stepDefinition
340: .getId())
341: .getAclId()));
342: }
343: }
344: } catch (Exception exc) {
345: throw new FxUpdateException(LOG,
346: "ex.stepdefinition.uniqueTarget.create");
347: } finally {
348: ri.stopRunAsSystem();
349: }
350: }
351: success = true;
352: } catch (SQLException exc) {
353: throw new FxUpdateException(LOG, exc, "ex.db.sqlError", exc
354: .getMessage());
355: } finally {
356: if (!success) {
357: ctx.setRollbackOnly();
358: } else {
359: StructureLoader.reloadWorkflows(FxContext.get()
360: .getDivisionId());
361: }
362: Database.closeObjects(StepDefinitionEngineBean.class, con,
363: stmt);
364: }
365: }
366:
367: /** {@inheritDoc} */
368: @TransactionAttribute(TransactionAttributeType.REQUIRED)
369: public void remove(long id) throws FxApplicationException {
370:
371: final UserTicket ticket = FxContext.get().getTicket();
372:
373: // Cannot delete basis step
374: if (id == StepDefinition.LIVE_STEP_ID
375: || id == StepDefinition.EDIT_STEP_ID) {
376: throw new FxNoAccessException(
377: "ex.stepdefinition.delete.system");
378: }
379:
380: // Security checks
381: FxPermissionUtils.checkRole(ticket, Role.WorkflowManagement);
382:
383: // Check existance
384: getEnvironment().getStepDefinition(id);
385:
386: // Now try to delete the stepDefinition
387: Connection con = null;
388: Statement stmt = null;
389: String sql;
390: boolean success = false;
391: try {
392:
393: // Obtain a database connection
394: con = Database.getDbConnection();
395:
396: // Read all stepDefinitions from the database
397: stmt = con.createStatement();
398: sql = "DELETE FROM " + TBL_STEPDEFINITION + ML
399: + " WHERE ID=" + id;
400: stmt.executeUpdate(sql);
401: sql = "SELECT COUNT(*) FROM " + TBL_STEPDEFINITION
402: + " WHERE UNIQUE_TARGET=" + id;
403: ResultSet rs = stmt.executeQuery(sql);
404: rs.next();
405: if (rs.getInt(1) > 0) {
406: FxRemoveException dbe = new FxRemoveException(
407: "ex.stepdefinition.delete.used");
408: LOG.error(dbe);
409: throw dbe;
410: }
411: sql = "DELETE FROM " + TBL_STEPDEFINITION + " WHERE ID="
412: + id;
413: if (stmt.executeUpdate(sql) == 0) {
414: FxEntryInUseException eiu = new FxEntryInUseException(
415: "ex.stepdefinition.load.notFound");
416: if (LOG.isInfoEnabled())
417: LOG.info(eiu);
418: throw eiu;
419: }
420: success = true;
421: } catch (SQLException exc) {
422: // TODO add Database.childRecordsExistViolation
423: /*if (Database.childRecordsExistViolation(exc)) {
424: FxDeleteException dbe = new FxDeleteException("The StepDefinition is in use", exc);
425: LOG.error(dbe);
426: throw dbe;
427: } else {*/
428: throw new FxRemoveException(LOG,
429: "ex.stepdefinition.delete", exc);
430: //}
431: } finally {
432: if (!success) {
433: ctx.setRollbackOnly();
434: } else {
435: StructureLoader.reloadWorkflows(FxContext.get()
436: .getDivisionId());
437: }
438: Database.closeObjects(StepDefinitionEngineBean.class, con,
439: stmt);
440: }
441: }
442:
443: }
|