001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2007
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.war.beans.admin.main;
034:
035: import com.flexive.faces.FxJsfUtils;
036: import com.flexive.faces.messages.FxFacesMsgErr;
037: import com.flexive.faces.messages.FxFacesMsgInfo;
038: import com.flexive.faces.messages.FxFacesMsgWarn;
039: import com.flexive.shared.CacheAdmin;
040: import com.flexive.shared.EJBLookup;
041: import com.flexive.shared.FxSharedUtils;
042: import com.flexive.shared.security.UserGroup;
043: import com.flexive.shared.workflow.*;
044: import org.apache.commons.collections.CollectionUtils;
045:
046: import javax.faces.model.SelectItem;
047: import java.util.ArrayList;
048: import java.util.HashMap;
049: import java.util.List;
050: import java.util.Map;
051:
052: /**
053: * Workflow service beans.
054: *
055: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
056: * @version $Rev: 1 $
057: */
058: public class WorkflowBean {
059: /**
060: * Session key to store the last inserted step id (for validation purposes)
061: */
062: private static final String SESSION_LASTSTEPID = "__FXLASTNEWSTEPID__";
063:
064: private WorkflowEdit workflow = null;
065: private List<StepEdit> steps = null;
066: private List<RouteEdit> routes = null;
067: private long stepDefinitionId = -1;
068: private long stepACL = -1;
069: private long workflowId = -1;
070: private int stepIndex = -1;
071: private int routeIndex = -1;
072: private long fromStepId = -1;
073: private long toStepId = -1;
074: private UserGroup userGroup = null;
075:
076: /**
077: * Default constructor
078: */
079: public WorkflowBean() {
080: }
081:
082: /**
083: * Create a new workflow.
084: *
085: * @return the next page
086: */
087: public String create() {
088: try {
089: workflow.setId(EJBLookup.getWorkflowEngine().create(
090: workflow));
091: workflowId = workflow.getId();
092: return edit();
093: } catch (Exception e) {
094: new FxFacesMsgErr(e).addToContext();
095: return "workflowCreate";
096: }
097: /*
098: // test for cache transactions - keep until issue is resolved!
099: *
100: long workflowId = createTestWorkflow();
101: try {
102: ACL myWorkflowACL = EJBLookup.getAclInterface().load(EJBLookup.getAclInterface()
103: .create("CACTUS_TEST_WORKFLOW", new FxString("Test workflow"),
104: FxContext.get().getUserTicket().getMandatorId(),
105: "#000000", "cactus test ACL (ignore)", ACL.CATEGORY.WORKFLOW));
106: EJBLookup.getAclInterface().assign(myWorkflowACL.getId(), Group.GROUP_EVERYONE,
107: true, true, true, true, true, true);
108: WorkflowEdit editWorkflow = new WorkflowEdit(CacheAdmin.getEnvironment().getWorkflow(workflowId));
109: // add two valid steps
110: editWorkflow.getSteps().add(new StepEdit(new Step(-1, StepDefinition.EDIT_STEP_ID,
111: editWorkflow.getId(), myWorkflowACL.getId())));
112: editWorkflow.getSteps().add(new StepEdit(new Step(-2, StepDefinition.LIVE_STEP_ID,
113: editWorkflow.getId(), myWorkflowACL.getId())));
114: // ... and an invalid route (which will cause a rollback after the steps have been added)
115: editWorkflow.getRoutes().add(new Route(-1, Group.GROUP_EVERYONE, -1, -10));
116:
117: List<Step> cachedSteps = CacheAdmin.getEnvironment().getSteps();
118: try {
119: EJBLookup.getWorkflowInterface().update(editWorkflow);
120: assert false : "Should not be able to successfully create workflows with invalid routes.";
121: } catch (Exception e) {
122: if (!cachedSteps.equals(CacheAdmin.getEnvironment().getSteps())) {
123: FxJsfUtils.addErrorMsg("Steps should have been rollbacked.\nSteps before update: "
124: + cachedSteps.toString()
125: + "\nEnvironment: " + CacheAdmin.getEnvironment().getSteps(), null);
126: }
127: }
128: } finally {
129: EJBLookup.getWorkflowInterface().delete(workflowId);
130: }
131: return "workflowOverview";*/
132: }
133:
134: /**
135: * Deletes the workflow set in workflowId.
136: *
137: * @return the next page
138: */
139: public String delete() {
140: try {
141: if (workflowId == -1) {
142: new FxFacesMsgErr("Workflow.err.notSelected")
143: .addToContext();
144: } else {
145: String oldName = CacheAdmin.getEnvironment()
146: .getWorkflow(workflowId).getName();
147: EJBLookup.getWorkflowEngine().remove(workflowId);
148: new FxFacesMsgInfo("Workflow.nfo.deleted", oldName)
149: .addToContext();
150: }
151: } catch (Exception e) {
152: new FxFacesMsgErr(e).addToContext();
153: }
154: return "workflowOverview";
155: }
156:
157: /**
158: * Edit the workflow set in workflowId.
159: *
160: * @return the next page
161: */
162: public String edit() {
163: if (workflowId == -1) {
164: new FxFacesMsgErr("Workflow.err.notSelected")
165: .addToContext();
166: } else {
167: workflow = new WorkflowEdit(CacheAdmin.getEnvironment()
168: .getWorkflow(workflowId));
169: getSteps().clear();
170: for (Step step : workflow.getSteps()) {
171: getSteps().add(new StepEdit(step));
172: }
173: getRoutes().clear();
174: for (Route route : workflow.getRoutes()) {
175: getRoutes().add(new RouteEdit(route));
176: }
177: }
178: return "workflowEdit";
179: }
180:
181: /**
182: * Save the current workflow.
183: *
184: * @return the next page
185: */
186: public String save() {
187: try {
188: workflow.setSteps(steps);
189: workflow.setRoutes(routes);
190: EJBLookup.getWorkflowEngine().update(workflow);
191: } catch (Exception e) {
192: new FxFacesMsgErr(e).addToContext();
193: return "workflowEdit";
194: }
195: new FxFacesMsgInfo("Workflow.nfo.updated", workflow.getName())
196: .addToContext();
197: return "workflowOverview";
198: }
199:
200: /**
201: * Add a new step definition to the current workflow.
202: *
203: * @return the next page
204: */
205: public String addStep() {
206: StepEdit step = new StepEdit(new Step(getNewStepId(),
207: stepDefinitionId, workflow.getId(), stepACL));
208: getSteps().add(step);
209: workflow.setSteps(steps);
210: return "workflowEdit";
211: }
212:
213: /**
214: * Remove the step at position "stepIndex".
215: *
216: * @return the next page
217: */
218: public String removeStep() {
219: if (stepIndex == -1) {
220: new FxFacesMsgErr("Workflow.err.step.notSelected")
221: .addToContext();
222: } else if (stepIndex < 0 || stepIndex > getSteps().size()) {
223: new FxFacesMsgErr("Workflow.err.step.invalidIndex",
224: FxJsfUtils.getLocalizedMessage(
225: "Workflow.err.step.invalidIndex.detail",
226: stepIndex)).addToContext();
227: } else if (getUsedSteps().contains(getSteps().get(stepIndex))) {
228: // TODO add shortcut method
229: String stepLabel = CacheAdmin.getEnvironment()
230: .getStepDefinition(
231: getSteps().get(stepIndex)
232: .getStepDefinitionId()).getLabel()
233: .getBestTranslation();
234: new FxFacesMsgErr("Workflow.err.step.inUse", stepLabel)
235: .addToContext();
236: } else {
237: getSteps().remove(stepIndex);
238: }
239: workflow.setSteps(getSteps());
240: return "workflowEdit";
241: }
242:
243: /**
244: * Add a new route between steps[fromStepIndex] and steps[toStepIndex].
245: *
246: * @return the next page
247: */
248: public String addRoute() {
249: if (getStep(fromStepId) == null || getStep(toStepId) == null) {
250: new FxFacesMsgErr(
251: "Workflow.err.route.create.steps.notFound")
252: .addToContext();
253: return "workflowEdit";
254: }
255: if (fromStepId == toStepId) {
256: new FxFacesMsgErr(
257: "Workflow.err.route.create.steps.identical")
258: .addToContext();
259: return "workflowEdit";
260: }
261: RouteEdit route = new RouteEdit(new Route(-1,
262: userGroup.getId(), fromStepId, toStepId));
263: if (routes.contains(route)) {
264: new FxFacesMsgWarn("Workflow.wng.route.exists")
265: .addToContext();
266: } else {
267: routes.add(route);
268: }
269: return "workflowEdit";
270: }
271:
272: /**
273: * Remove an existing route (by index).
274: *
275: * @return the next page
276: */
277: public String removeRoute() {
278: if (routeIndex < 0 || routeIndex > getRoutes().size()) {
279: new FxFacesMsgErr("Workflow.err.route.remove.invalid",
280: routeIndex).addToContext();
281: return "workflowEdit";
282: }
283: Route route = routes.get(routeIndex);
284: // TODO add convenience methods for route --> step --> stepdefinitionid --> stepdefinition
285: String fromStepName = CacheAdmin.getEnvironment()
286: .getStepDefinition(
287: getStep(route.getFromStepId())
288: .getStepDefinitionId()).getLabel()
289: .getBestTranslation();
290: String toStepName = CacheAdmin.getEnvironment()
291: .getStepDefinition(
292: getStep(route.getToStepId())
293: .getStepDefinitionId()).getLabel()
294: .getBestTranslation();
295: routes.remove(routeIndex);
296: new FxFacesMsgInfo("Workflow.nfo.route.removed", fromStepName,
297: toStepName).addToContext();
298: return "workflowEdit";
299: }
300:
301: /**
302: * Return a list of all workflow step definitions that can be added to the current workflow
303: * (i.e. all defined steps minus the steps already used in the workflow).
304: *
305: * @return a list of all workflow step definitions that can be added to the current workflow
306: */
307: @SuppressWarnings("unchecked")
308: public List<SelectItem> getStepsForAdding() {
309: List<StepDefinition> stepDefinitions = CacheAdmin
310: .getFilteredEnvironment().getStepDefinitions();
311: List<StepDefinition> available = (List<StepDefinition>) CollectionUtils
312: .subtract(stepDefinitions, FxSharedUtils
313: .getUsedStepDefinitions(steps != null ? steps
314: : workflow.getSteps(), stepDefinitions));
315: return FxJsfUtils.asSelectListWithLabel(available);
316: }
317:
318: /**
319: * Return a list of all workflow steps that are available for routes.
320: *
321: * @return a list of all workflow steps that are available for routes.
322: */
323: public List<SelectItem> getStepsForRoutes() {
324: List<SelectItem> result = new ArrayList<SelectItem>();
325: List<? extends Step> listSteps = steps != null ? steps
326: : CacheAdmin.getFilteredEnvironment().getSteps();
327: if (steps == null) {
328: // TODO ugly validation fix to allow temporary steps (negative id)
329: Object lastNewStepIdValue = FxJsfUtils
330: .getSessionAttribute(SESSION_LASTSTEPID);
331: long lastNewStepId = lastNewStepIdValue != null ? (Long) lastNewStepIdValue
332: : -1;
333: for (long i = -1; i >= lastNewStepId; i--) {
334: result.add(new SelectItem(i, ""));
335: }
336: }
337:
338: for (Step step : listSteps) {
339: StepDefinition stepDefinition = CacheAdmin.getEnvironment()
340: .getStepDefinition(step.getStepDefinitionId());
341: result.add(new SelectItem(step.getId(), stepDefinition
342: .getLabel().getBestTranslation()));
343: }
344: return result;
345: }
346:
347: /**
348: * Returns a negative pseudo step ID to be used for identifying steps
349: * in routes before they are persisted to the database.
350: *
351: * @return a new internal step ID to be used for identifying steps
352: */
353: private long getNewStepId() {
354: long minStepId = -1;
355: for (Step step : getSteps()) {
356: if (step.getId() <= minStepId) {
357: minStepId = step.getId() - 1;
358: }
359: }
360: FxJsfUtils.setSessionAttribute(SESSION_LASTSTEPID, minStepId);
361: return minStepId;
362: }
363:
364: /**
365: * Returns a list of all steps used by the current route definitions.
366: *
367: * @return a list of all steps used by the current route definitions.
368: */
369: private List<StepEdit> getUsedSteps() {
370: List<StepEdit> routeSteps = new ArrayList<StepEdit>();
371: for (Route route : getRoutes()) {
372: routeSteps.add(getStep(route.getFromStepId()));
373: routeSteps.add(getStep(route.getToStepId()));
374: }
375: return routeSteps;
376: }
377:
378: /**
379: * Return the step with the given ID (may be negative for temporary
380: * steps).
381: *
382: * @param id step id
383: * @return the step with the requested ID
384: */
385: private StepEdit getStep(long id) {
386: for (StepEdit step : getSteps()) {
387: if (step.getId() == id) {
388: return step;
389: }
390: }
391: return null;
392: }
393:
394: public List<Workflow> getList() {
395: return CacheAdmin.getFilteredEnvironment().getWorkflows();
396: }
397:
398: /**
399: * Return the current workflow.
400: *
401: * @return the current workflow.
402: */
403: public WorkflowEdit getWorkflow() {
404: if (workflow != null) {
405: return workflow;
406: }
407: workflow = new WorkflowEdit();
408: return workflow;
409: }
410:
411: public void setWorkflow(WorkflowEdit workflow) {
412: this .workflow = workflow;
413: }
414:
415: public long getStepDefinitionId() {
416: return stepDefinitionId;
417: }
418:
419: public void setStepDefinitionId(long stepDefinitionId) {
420: this .stepDefinitionId = stepDefinitionId;
421: }
422:
423: public long getStepACL() {
424: return stepACL;
425: }
426:
427: public void setStepACL(long stepACL) {
428: this .stepACL = stepACL;
429: }
430:
431: /**
432: * Return the current step table.
433: *
434: * @return the current step table.
435: */
436: public List<StepEdit> getSteps() {
437: if (steps != null) {
438: return steps;
439: }
440: steps = new ArrayList<StepEdit>();
441: return steps;
442: }
443:
444: public void setSteps(List<StepEdit> steps) {
445: this .steps = steps;
446: }
447:
448: /**
449: * Return a map with all steps indexed by ID.
450: *
451: * @return a map with all steps indexed by ID.
452: */
453: public Map<Long, StepEdit> getStepsById() {
454: HashMap<Long, StepEdit> result = new HashMap<Long, StepEdit>(
455: steps.size());
456: for (StepEdit step : getSteps()) {
457: result.put(step.getId(), step);
458: }
459: return result;
460: }
461:
462: /**
463: * Return the current route table.
464: *
465: * @return the current route table.
466: */
467: public List<RouteEdit> getRoutes() {
468: if (routes != null) {
469: return routes;
470: }
471: routes = new ArrayList<RouteEdit>();
472: return routes;
473: }
474:
475: public void setRoutes(List<RouteEdit> routes) {
476: this .routes = routes;
477: }
478:
479: public long getWorkflowId() {
480: return workflowId;
481: }
482:
483: public void setWorkflowId(long workflowId) {
484: this .workflowId = workflowId;
485: }
486:
487: public int getStepIndex() {
488: return stepIndex;
489: }
490:
491: public void setStepIndex(int stepIndex) {
492: this .stepIndex = stepIndex;
493: }
494:
495: public int getRouteIndex() {
496: return routeIndex;
497: }
498:
499: public void setRouteIndex(int routeIndex) {
500: this .routeIndex = routeIndex;
501: }
502:
503: public long getFromStepId() {
504: return fromStepId;
505: }
506:
507: public void setFromStepId(long fromStepId) {
508: this .fromStepId = fromStepId;
509: }
510:
511: public long getToStepId() {
512: return toStepId;
513: }
514:
515: public void setToStepId(long toStepId) {
516: this .toStepId = toStepId;
517: }
518:
519: public UserGroup getUserGroup() {
520: return userGroup;
521: }
522:
523: public void setUserGroup(UserGroup userGroup) {
524: this.userGroup = userGroup;
525: }
526:
527: }
|