001: /******************************************************************************
002: * JBoss, a division of Red Hat *
003: * Copyright 2006, Red Hat Middleware, LLC, and individual *
004: * contributors as indicated by the @authors tag. See the *
005: * copyright.txt in the distribution for a full listing of *
006: * individual contributors. *
007: * *
008: * This is free software; you can redistribute it and/or modify it *
009: * under the terms of the GNU Lesser General Public License as *
010: * published by the Free Software Foundation; either version 2.1 of *
011: * the License, or (at your option) any later version. *
012: * *
013: * This software is distributed in the hope that it will be useful, *
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of *
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
016: * Lesser General Public License for more details. *
017: * *
018: * You should have received a copy of the GNU Lesser General Public *
019: * License along with this software; if not, write to the Free *
020: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *
021: * 02110-1301 USA, or see the FSF site: http://www.fsf.org. *
022: ******************************************************************************/package org.jboss.portal.cms.workflow;
023:
024: import org.apache.log4j.Logger;
025: import org.jboss.portal.common.io.IOTools;
026: import org.jboss.portal.identity.IdentityContext;
027: import org.jboss.portal.identity.IdentityServiceController;
028: import org.jboss.portal.identity.MembershipModule;
029: import org.jboss.portal.identity.Role;
030: import org.jboss.portal.identity.UserModule;
031: import org.jboss.portal.jems.as.JNDI;
032: import org.jboss.portal.jems.as.system.AbstractJBossService;
033: import org.jboss.portal.workflow.WorkflowException;
034: import org.jboss.portal.workflow.service.WorkflowService;
035: import org.jbpm.JbpmContext;
036: import org.jbpm.db.GraphSession;
037: import org.jbpm.graph.def.ProcessDefinition;
038: import org.jbpm.graph.exe.ProcessInstance;
039: import org.jbpm.graph.exe.Token;
040: import org.jbpm.taskmgmt.exe.TaskInstance;
041: import org.w3c.dom.Document;
042: import org.w3c.dom.Element;
043:
044: import javax.xml.parsers.DocumentBuilderFactory;
045: import java.io.ByteArrayInputStream;
046: import java.io.InputStream;
047: import java.util.ArrayList;
048: import java.util.Collection;
049: import java.util.HashSet;
050: import java.util.Iterator;
051: import java.util.List;
052: import java.util.Set;
053: import java.util.StringTokenizer;
054:
055: /**
056: * Created on : Dec 19, 2006
057: *
058: * @author Sohil Shah - sohil.shah@jboss.com
059: */
060: public class ApprovePublishImpl extends AbstractJBossService implements
061: ApprovePublish {
062: /**
063: *
064: */
065: private static final Logger log = Logger
066: .getLogger(ApprovePublishImpl.class);
067:
068: /**
069: *
070: */
071: private WorkflowService workflowService = null;
072: private IdentityServiceController identityServiceController = null;
073: private String process = null;
074: private String processName = null;
075: private String managerRoles = null;
076: private String[] managers = null;
077: private Set managerSet = null;
078: private boolean overwrite = false;
079: private String from = null;
080: private String subject = null;
081: private String body = null;
082: private JNDI.Binding jndiBinding;
083:
084: private MembershipModule membershipModule = null;
085: private UserModule userModule = null;
086:
087: private String jndiName = null;
088:
089: /**
090: *
091: *
092: */
093: public ApprovePublishImpl() {
094:
095: }
096:
097: /**
098: *
099: */
100: public void startService() throws Exception {
101: super .startService();
102:
103: if (this .jndiName != null) {
104: jndiBinding = new JNDI.Binding(jndiName, this );
105: jndiBinding.bind();
106: }
107:
108: InputStream is = null;
109: JbpmContext jbpmContext = null;
110: try {
111: is = new ByteArrayInputStream(this .process.getBytes());
112: jbpmContext = this .workflowService.getJbpmConfiguration()
113: .createJbpmContext();
114:
115: Document document = DocumentBuilderFactory.newInstance()
116: .newDocumentBuilder().parse(is);
117:
118: Element root = document.getDocumentElement();
119: this .processName = root.getAttribute("name");
120: ProcessDefinition processDefinition = jbpmContext
121: .getGraphSession().findLatestProcessDefinition(
122: this .processName);
123: if (processDefinition == null) {
124: processDefinition = ProcessDefinition
125: .parseXmlString(this .process);
126: jbpmContext.deployProcessDefinition(processDefinition);
127: } else {
128: // A process definition already exists....should deploy a new version
129: // of the definition if overwrite is true
130: ProcessDefinition fromConfig = ProcessDefinition
131: .parseXmlString(this .process);
132: if (this .overwrite) {
133: // If the two of them are not same, create a new version
134: // of this process definition
135: jbpmContext.deployProcessDefinition(fromConfig);
136: }
137: }
138: } catch (Exception e) {
139: //
140: this .stopService();
141:
142: //
143: throw e;
144: } finally {
145: IOTools.safeClose(is);
146: IOTools.safeClose(jbpmContext);
147: }
148:
149: //process managers that can serve as approvers/rejecters
150: StringTokenizer st = new StringTokenizer(this .managerRoles, ",");
151: this .managers = new String[st.countTokens()];
152: this .managerSet = new HashSet();
153: for (int i = 0; i < managers.length; i++) {
154: this .managers[i] = st.nextToken();
155: this .managerSet.add(this .managers[i]);
156: }
157:
158: this .membershipModule = (MembershipModule) identityServiceController
159: .getIdentityContext().getObject(
160: IdentityContext.TYPE_MEMBERSHIP_MODULE);
161:
162: this .userModule = (UserModule) identityServiceController
163: .getIdentityContext().getObject(
164: IdentityContext.TYPE_USER_MODULE);
165: }
166:
167: /**
168: *
169: */
170: public void stopService() throws Exception {
171: super .stopService();
172:
173: if (jndiBinding != null) {
174: jndiBinding.unbind();
175: jndiBinding = null;
176: }
177: }
178:
179: /** @return */
180: public WorkflowService getWorkflowService() {
181: return this .workflowService;
182: }
183:
184: /** @param workflowService */
185: public void setWorkflowService(WorkflowService workflowService) {
186: this .workflowService = workflowService;
187: }
188:
189: /** @return */
190: public String getProcess() {
191: return this .process;
192: }
193:
194: /** @param process */
195: public void setProcess(String process) {
196: this .process = process;
197: }
198:
199: /** @return the managerRoles */
200: public String getManagerRoles() {
201: return managerRoles;
202: }
203:
204: /** @param managerRoles the manager managerRoles to set */
205: public void setManagerRoles(String managerRoles) {
206: this .managerRoles = managerRoles;
207: }
208:
209: /** @return the overwrite */
210: public boolean isOverwrite() {
211: return overwrite;
212: }
213:
214: /** @param overwrite the overwrite to set */
215: public void setOverwrite(boolean overwrite) {
216: this .overwrite = overwrite;
217: }
218:
219: /** @return */
220: public String getJNDIName() {
221: return this .jndiName;
222: }
223:
224: /** @param jndiName */
225: public void setJNDIName(String jndiName) {
226: this .jndiName = jndiName;
227: }
228:
229: /**
230: *
231: */
232: public IdentityServiceController getIdentityServiceController() {
233: return identityServiceController;
234: }
235:
236: /** @param identityServiceController */
237: public void setIdentityServiceController(
238: IdentityServiceController identityServiceController) {
239: this .identityServiceController = identityServiceController;
240: }
241:
242: /** @return the body */
243: public String getBody() {
244: return body;
245: }
246:
247: /** @param body the body to set */
248: public void setBody(String body) {
249: this .body = body;
250: }
251:
252: /** @return the from */
253: public String getFrom() {
254: return from;
255: }
256:
257: /** @param from the from to set */
258: public void setFrom(String from) {
259: this .from = from;
260: }
261:
262: /** @return the subject */
263: public String getSubject() {
264: return subject;
265: }
266:
267: /** @param subject the subject to set */
268: public void setSubject(String subject) {
269: this .subject = subject;
270: }
271:
272: //----------ApprovePublish Implementation------------------------------------------------------------------
273: /**
274: * Called when content is added to the CMS, and needs to be approved by the managers before it can be published to go
275: * live
276: *
277: * @param content
278: * @return returns the process id of the workflow process set in motion
279: */
280: public long requestApproval(Content content)
281: throws WorkflowException {
282: long processId = 0;
283: JbpmContext jbpmContext = null;
284: ProcessInstance processInstance = null;
285: boolean success = false;
286: try {
287: jbpmContext = this .workflowService.getJbpmConfiguration()
288: .createJbpmContext();
289:
290: //The next line creates one execution of the process definition.
291: // After construction, the process execution has one main path
292: // of execution (=the root token) that is positioned in the
293: // start-state.
294: processInstance = jbpmContext
295: .newProcessInstance(this .processName);
296:
297: //After construction, the process execution has one main path
298: // of execution (=the root token).
299: Token token = processInstance.getRootToken();
300:
301: //set the process variables
302: processInstance.getContextInstance().setVariable("content",
303: content);
304: processInstance.getContextInstance().setVariable(
305: "managers", this .managers);
306: processInstance.getContextInstance().setVariable("from",
307: this .from);
308: processInstance.getContextInstance().setVariable("subject",
309: this .subject);
310: processInstance.getContextInstance().setVariable("body",
311: this .body);
312:
313: //start the workflow, starts the cms publish approval workflow
314: //this creates a task to approve/reject a cms publish for the Admins
315: token.signal();
316:
317: //mark as a successfull process initiation
318: success = true;
319: } catch (Exception e) {
320: success = false;
321: throw new WorkflowException(e);
322: } finally {
323: if (processInstance != null && success) {
324: jbpmContext.save(processInstance);
325: processId = processInstance.getId();
326: }
327: IOTools.safeClose(jbpmContext);
328: }
329: return processId;
330: }
331:
332: /**
333: * Called when a manager either approves or rejects the publishing of a specific content to go live
334: *
335: * @param processId
336: * @param manager userId of the manager
337: * @param approved true if approved, false if rejected
338: */
339: public void processManagerResponse(long processId, String manager,
340: boolean approved) throws WorkflowException {
341: JbpmContext jbpmContext = null;
342: ProcessInstance processInstance = null;
343: boolean isManager = false;
344: try {
345: jbpmContext = this .workflowService.getJbpmConfiguration()
346: .createJbpmContext();
347:
348: //Now, we search for all process instances of this process definition.
349: processInstance = jbpmContext
350: .loadProcessInstance(processId);
351:
352: if (processInstance.hasEnded()) {
353: log.debug("This process has already ended...");
354: return;
355: }
356:
357: processInstance.getContextInstance().setVariable(
358: "approved", new Boolean(approved));
359:
360: Collection allTasks = processInstance.getTaskMgmtInstance()
361: .getTaskInstances();
362: if (allTasks != null) {
363: for (Iterator itr = allTasks.iterator(); itr.hasNext();) {
364: TaskInstance cour = (TaskInstance) itr.next();
365: if (this .isManager(manager, cour.getActorId())) {
366: isManager = true;
367: log.debug("Manager=" + cour.getActorId() + "("
368: + processId + ")");
369:
370: //check and make sure this task instance is not marked for deletion
371: if (cour.getVariable(processInstance.getId()
372: + ":" + cour.getId()) != null) {
373: continue;
374: }
375:
376: if (!approved) {
377: cour.start();
378: cour.end("rejection");
379: break;
380: } else {
381: cour.start();
382: cour.end("approval");
383: break;
384: }
385: }
386: }
387: }
388: } catch (Exception e) {
389: throw new WorkflowException(e);
390: } finally {
391: if (processInstance != null) {
392: jbpmContext.save(processInstance);
393: }
394:
395: if (!isManager) {
396: WorkflowException we = new WorkflowException(
397: "You are not authorized to Approve/Deny content publish requests");
398: throw we;
399: }
400: IOTools.safeClose(jbpmContext);
401: }
402: }
403:
404: /**
405: * Called when a manager either approves the publishing of a specific content to go live, but at the same time
406: * adds his/her own modifications to the original content
407: *
408: * @param processId
409: * @param manager userId of the manager
410: * @param approved true if approved, false if rejected
411: */
412: public void processManagerResponse(long processId, String manager,
413: String modifiedContent) throws WorkflowException {
414: JbpmContext jbpmContext = null;
415: ProcessInstance processInstance = null;
416: boolean isManager = false;
417: try {
418: jbpmContext = this .workflowService.getJbpmConfiguration()
419: .createJbpmContext();
420:
421: //Now, we search for all process instances of this process definition.
422: processInstance = jbpmContext
423: .loadProcessInstance(processId);
424:
425: if (processInstance.hasEnded()) {
426: log.debug("This process has already ended...");
427: return;
428: }
429:
430: processInstance.getContextInstance().setVariable(
431: "approved", new Boolean(true));
432: processInstance.getContextInstance().setVariable(
433: "modifiedContent", modifiedContent);
434:
435: Collection allTasks = processInstance.getTaskMgmtInstance()
436: .getTaskInstances();
437: if (allTasks != null) {
438: for (Iterator itr = allTasks.iterator(); itr.hasNext();) {
439: TaskInstance cour = (TaskInstance) itr.next();
440: if (this .isManager(manager, cour.getActorId())) {
441: isManager = true;
442: log.debug("Manager=" + cour.getActorId() + "("
443: + processId + ")");
444:
445: //check and make sure this task instance is not marked for deletion
446: if (cour.getVariable(processInstance.getId()
447: + ":" + cour.getId()) != null) {
448: continue;
449: }
450:
451: cour.start();
452: cour.end("approval");
453: break;
454: }
455: }
456: }
457: } catch (Exception e) {
458: throw new WorkflowException(e);
459: } finally {
460: if (processInstance != null) {
461: jbpmContext.save(processInstance);
462: }
463:
464: if (!isManager) {
465: WorkflowException we = new WorkflowException(
466: "You are not authorized to Approve/Deny content publish requests");
467: throw we;
468: }
469: IOTools.safeClose(jbpmContext);
470: }
471: }
472:
473: /**
474: * Retrieves a queue of unapproved content associated with the specified file in the CMS
475: *
476: * @param filePath
477: * @return
478: * @throws WorkflowException
479: */
480: public Collection getPendingQueue(String filePath)
481: throws WorkflowException {
482: Collection pendingQueue = new ArrayList();
483: JbpmContext jbpmContext = null;
484: try {
485: jbpmContext = this .workflowService.getJbpmConfiguration()
486: .createJbpmContext();
487:
488: GraphSession graphSession = jbpmContext.getGraphSession();
489: ProcessDefinition processDef = graphSession
490: .findLatestProcessDefinition(this .processName);
491: List processInstances = graphSession
492: .findProcessInstances(processDef.getId());
493:
494: if (processInstances != null) {
495: for (int i = 0; i < processInstances.size(); i++) {
496: ProcessInstance cour = (ProcessInstance) processInstances
497: .get(i);
498: //iterate through a list of currently pending approval tasks
499: if (!cour.hasEnded()) {
500: Content content = (Content) cour
501: .getContextInstance().getVariable(
502: "content");
503:
504: //apply proper criteria to extract pending content only for the specified file
505: if (content != null) {
506: int lastIndex = content.getPath()
507: .lastIndexOf('/');
508: String criteriaPath = content.getPath()
509: .substring(0, lastIndex);
510:
511: if (criteriaPath.trim().equals(
512: filePath.trim())) {
513: content.setProcessId(String
514: .valueOf(cour.getId()));
515: pendingQueue.add(content);
516: }
517: }
518: }
519: }
520: }
521: } finally {
522: IOTools.safeClose(jbpmContext);
523: }
524: return pendingQueue;
525: }
526:
527: /**
528: * Retrieves a queue of unapproved content for everything in the CMS.
529: *
530: * @return
531: * @throws WorkflowException
532: */
533: public Collection getAllPendingInQueue() throws WorkflowException {
534: Collection pendingQueue = new ArrayList();
535: JbpmContext jbpmContext = null;
536: try {
537: jbpmContext = this .workflowService.getJbpmConfiguration()
538: .createJbpmContext();
539:
540: GraphSession graphSession = jbpmContext.getGraphSession();
541: ProcessDefinition processDef = graphSession
542: .findLatestProcessDefinition(this .processName);
543: List processInstances = graphSession
544: .findProcessInstances(processDef.getId());
545:
546: if (processInstances != null) {
547: for (int i = 0; i < processInstances.size(); i++) {
548: ProcessInstance cour = (ProcessInstance) processInstances
549: .get(i);
550: //iterate through a list of currently pending approval tasks
551: if (!cour.hasEnded()) {
552: Content content = (Content) cour
553: .getContextInstance().getVariable(
554: "content");
555:
556: //apply proper criteria to extract pending content only for the specified file
557: if (content != null) {
558: content.setProcessId(String.valueOf(cour
559: .getId()));
560: pendingQueue.add(content);
561: }
562: }
563: }
564: }
565: } finally {
566: IOTools.safeClose(jbpmContext);
567: }
568: return pendingQueue;
569: }
570:
571: /** @return */
572: public Set getManagers() {
573: return this .managerSet;
574: }
575:
576: //----------------------------------------------------------------------------------------------------------------
577: /**
578: * checks to see if the user trying to approve/deny a publish request belongs to the approved list of managers
579: *
580: * @param user
581: * @param managerRole
582: * @return
583: */
584: private boolean isManager(String user, String managerRole)
585: throws Exception {
586: boolean isManager = false;
587:
588: Set userRoles = this .membershipModule.getRoles(this .userModule
589: .findUserByUserName(user));
590: if (userRoles != null) {
591: for (Iterator itr = userRoles.iterator(); itr.hasNext();) {
592: Role cour = (Role) itr.next();
593: if (cour.getName().equalsIgnoreCase(managerRole)) {
594: //user is allowed to be a manager for this workflow
595: isManager = true;
596: break;
597: }
598: }
599: }
600:
601: return isManager;
602: }
603: }
|