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_ROUTES;
037: import static com.flexive.core.DatabaseConst.TBL_STEP;
038: import com.flexive.core.structure.StructureLoader;
039: import com.flexive.shared.CacheAdmin;
040: import com.flexive.shared.FxArrayUtils;
041: import com.flexive.shared.FxContext;
042: import com.flexive.shared.content.FxPermissionUtils;
043: import com.flexive.shared.exceptions.*;
044: import com.flexive.shared.interfaces.RouteEngine;
045: import com.flexive.shared.interfaces.RouteEngineLocal;
046: import com.flexive.shared.interfaces.SequencerEngine.System;
047: import com.flexive.shared.interfaces.SequencerEngineLocal;
048: import com.flexive.shared.interfaces.UserGroupEngineLocal;
049: import com.flexive.shared.security.UserTicket;
050: import com.flexive.shared.security.Role;
051: import com.flexive.shared.workflow.Route;
052: import com.flexive.shared.workflow.Step;
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:
061: /**
062: * The RouteEngine class provides functions to create, modify and query routes between steps
063: * within the workflows.
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="RouteEngine")
069: @TransactionAttribute(TransactionAttributeType.SUPPORTS)
070: @TransactionManagement(TransactionManagementType.CONTAINER)
071: public class RouteEngineBean implements RouteEngine, RouteEngineLocal {
072:
073: private static final transient Log LOG = LogFactory
074: .getLog(StepEngineBean.class);
075:
076: @EJB
077: private UserGroupEngineLocal groupEngine;
078: @EJB
079: private SequencerEngineLocal seq;
080: @Resource
081: private SessionContext ctx;
082:
083: /** {@inheritDoc} */
084: public Step[] getTargets(long fromStep)
085: throws FxApplicationException {
086: Connection con = null;
087: String sql = null;
088: Statement stmt = null;
089:
090: // Check fromStep
091: Step step = CacheAdmin.getEnvironment().getStep(fromStep);
092:
093: try {
094: final UserTicket ticket = FxContext.get().getTicket();
095: con = Database.getDbConnection();
096: stmt = con.createStatement();
097: if (ticket.isMandatorSupervisor()
098: || ticket.isGlobalSupervisor()) {
099: sql = "SELECT ID FROM " + TBL_STEP
100: + " WHERE WORKFLOW=" + step.getWorkflowId()
101: + " AND ID!=" + fromStep;
102: } else {
103: sql = "SELECT DISTINCT TO_STEP FROM "
104: + TBL_ROUTES
105: + " WHERE FROM_STEP="
106: + fromStep
107: + " AND USERGROUP IN ("
108: + FxArrayUtils.toSeparatedList(ticket
109: .getGroups(), ',') + ")";
110: }
111: ResultSet rs = stmt.executeQuery(sql);
112: ArrayList<Step> targets = new ArrayList<Step>(20);
113: int stepId = -1;
114: while (rs != null && rs.next()) {
115: try {
116: stepId = rs.getInt(1);
117: targets.add(CacheAdmin.getEnvironment().getStep(
118: stepId));
119: } catch (Exception exc) {
120: LOG.error(
121: "Failed to load step " + stepId
122: + " (skipping it), err="
123: + exc.getMessage(), exc);
124: }
125: }
126: return targets.toArray(new Step[targets.size()]);
127: } catch (SQLException exc) {
128: throw new FxLoadException(LOG, "ex.step.load.target", exc,
129: fromStep, exc.getMessage());
130: } finally {
131: Database.closeObjects(RouteEngineBean.class, con, stmt);
132: }
133: }
134:
135: /** {@inheritDoc} */
136: public Route[] loadRoutes(long workflowId)
137: throws FxApplicationException {
138:
139: // TODO | Step cache distribution may be too slow to reflect changes,
140: // TODO | this function should read all data from the database instead of using the
141: // TODO | loadStep(..) function.
142:
143: // 1 2 3 4
144: final String sql = "SELECT ro.ID,ro.FROM_STEP,ro.TO_STEP,ro.USERGROUP "
145: + "FROM "
146: + TBL_ROUTES
147: + " ro, "
148: + TBL_STEP
149: + " stp "
150: + "WHERE ro.TO_STEP=stp.ID AND stp.WORKFLOW="
151: + workflowId + " " + "ORDER BY ro.USERGROUP ASC";
152:
153: if (LOG.isDebugEnabled())
154: LOG.debug("getRoute(" + workflowId + ")=" + sql);
155:
156: Connection con = null;
157: Statement stmt = null;
158:
159: try {
160:
161: // Obtain a database connection
162: con = Database.getDbConnection();
163:
164: // Create the new route
165: stmt = con.createStatement();
166: ResultSet rs = stmt.executeQuery(sql);
167: ArrayList<Route> routes = new ArrayList<Route>(50);
168:
169: // Process result set
170: while (rs != null && rs.next()) {
171: long routeId = rs.getLong(1);
172: long fromId = rs.getLong(2);
173: long toId = rs.getLong(3);
174: long groupId = rs.getLong(4);
175: Route route = new Route(routeId, groupId, fromId, toId);
176: routes.add(route);
177: }
178:
179: return routes.toArray(new Route[routes.size()]);
180: } catch (SQLException exc) {
181: throw new FxLoadException(LOG, "ex.routes.load", exc,
182: workflowId, exc.getMessage());
183: } finally {
184: Database.closeObjects(RouteEngineBean.class, con, stmt);
185: }
186: }
187:
188: /** {@inheritDoc} */
189: @TransactionAttribute(TransactionAttributeType.REQUIRED)
190: public long create(long fromStepId, long toStepId, long groupId)
191: throws FxApplicationException {
192: FxPermissionUtils.checkRole(FxContext.get().getTicket(),
193: Role.WorkflowManagement);
194: // Sanity checks.
195: // StepImp.loadStep(..) throws a FxNotFoundException if the steps do not exist.
196: Step fromStep;
197: Step toStep;
198:
199: groupEngine.load(groupId);
200: fromStep = CacheAdmin.getEnvironment().getStep(fromStepId);
201: toStep = CacheAdmin.getEnvironment().getStep(toStepId);
202:
203: // from and to step must be in the same workflow or we may not connect them
204: if (fromStep.getWorkflowId() != toStep.getWorkflowId()) {
205: throw new FxInvalidParameterException("STEP_FROM",
206: "ex.routes.create.differentWorkflows", fromStepId,
207: fromStep.getWorkflowId(), toStepId, toStep
208: .getWorkflowId());
209: }
210: if (fromStepId == toStepId) {
211: throw new FxInvalidParameterException("STEP_FROM",
212: "ex.routes.create.loop");
213: }
214:
215: // Create the route
216: Connection con = null;
217: Statement stmt = null;
218: String sql;
219: String routeString = "[from=" + fromStep.getId() + ",to="
220: + toStep.getId() + ",group=" + groupId + "]";
221: boolean success = false;
222:
223: try {
224:
225: // Obtain a database connection
226: con = Database.getDbConnection();
227:
228: // Create the new route
229: stmt = con.createStatement();
230: long routeId = seq.getId(System.ROUTE);
231: sql = "INSERT INTO " + TBL_ROUTES
232: + " (ID,FROM_STEP,TO_STEP,USERGROUP) VALUES ("
233: + routeId + "," + fromStep.getId() + ","
234: + toStep.getId() + "," + groupId + ")";
235: stmt.executeUpdate(sql);
236:
237: // Return the new id
238: success = true;
239: return routeId;
240: } catch (SQLException exc) {
241: if (Database.isUniqueConstraintViolation(exc)) {
242: throw new FxEntryExistsException(LOG,
243: "ex.routes.create.exists");
244: } else {
245: throw new FxCreateException(LOG, "ex.routes.create",
246: exc, routeString, exc.getMessage());
247: }
248: } finally {
249: if (!success) {
250: ctx.setRollbackOnly();
251: } else {
252: StructureLoader.reloadWorkflows(FxContext.get()
253: .getDivisionId());
254: }
255: Database.closeObjects(RouteEngineBean.class, con, stmt);
256: }
257: }
258:
259: /**
260: * Deletes a route defined by its unique id.
261: *
262: * @param routeId the route id
263: * @throws FxApplicationException if an error occured
264: */
265: private void deleteRoute(long routeId)
266: throws FxApplicationException {
267:
268: // Create the new step
269: Connection con = null;
270: PreparedStatement stmt = null;
271: final String sql = "DELETE FROM " + TBL_ROUTES + " WHERE ID=?";
272: boolean success = false;
273: try {
274: // Obtain a database connection
275: con = Database.getDbConnection();
276:
277: // Create the new workflow instance
278: stmt = con.prepareStatement(sql);
279: stmt.setLong(1, routeId);
280: stmt.executeUpdate();
281: success = true;
282: } catch (SQLException exc) {
283: throw new FxRemoveException(LOG, "ex.routes.delete", exc,
284: routeId, exc.getMessage());
285: } finally {
286: if (!success) {
287: ctx.setRollbackOnly();
288: } else {
289: StructureLoader.reloadWorkflows(FxContext.get()
290: .getDivisionId());
291: }
292: Database.closeObjects(RouteEngineBean.class, con, stmt);
293: }
294: }
295:
296: /** {@inheritDoc} */
297: @TransactionAttribute(TransactionAttributeType.REQUIRED)
298: public void remove(long routeId) throws FxApplicationException {
299: FxPermissionUtils.checkRole(FxContext.get().getTicket(),
300: Role.WorkflowManagement);
301: deleteRoute(routeId);
302: }
303:
304: }
|