View Javadoc

1   /**
2    * This file Copyright (c) 2003-2010 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.workflow.jcr;
35  
36  import info.magnolia.beancoder.MgnlNode;
37  import info.magnolia.cms.core.Content;
38  import info.magnolia.cms.core.HierarchyManager;
39  import info.magnolia.cms.core.ItemType;
40  import info.magnolia.cms.core.search.Query;
41  import info.magnolia.cms.core.search.QueryManager;
42  import info.magnolia.cms.core.search.QueryResult;
43  import info.magnolia.cms.security.AccessDeniedException;
44  import info.magnolia.cms.util.ContentUtil;
45  import info.magnolia.context.LifeTimeJCRSessionUtil;
46  import info.magnolia.context.MgnlContext;
47  import info.magnolia.module.workflow.WorkflowConstants;
48  import info.magnolia.module.workflow.beancoder.OwfeJcrBeanCoder;
49  
50  import java.util.ArrayList;
51  import java.util.Iterator;
52  import java.util.List;
53  
54  import javax.jcr.RepositoryException;
55  import javax.jcr.ValueFactory;
56  
57  import openwfe.org.engine.expressions.FlowExpressionId;
58  import openwfe.org.engine.workitem.InFlowWorkItem;
59  import openwfe.org.engine.workitem.StringAttribute;
60  import openwfe.org.util.beancoder.BeanCoderException;
61  import openwfe.org.worklist.store.StoreException;
62  
63  import org.apache.commons.lang.StringUtils;
64  import org.slf4j.Logger;
65  import org.slf4j.LoggerFactory;
66  
67  
68  /**
69   * The Magnolia-specific workflow participant.
70   *
71   * @author Jackie Ju
72   * @author Philipp Bracher
73   * @author Nicolas Modrzyk
74   * @author John Mettraux
75   */
76  public class JCRWorkItemStore {
77      private final static Logger log = LoggerFactory.getLogger(JCRWorkItemStore.class.getName());
78  
79      private static final String BACKUP_REL = "backup";
80      private static final String BACKUP = "/" + BACKUP_REL;
81  
82      private boolean shouldBackupWorkItems = false;
83  
84      private boolean useLifeTimeJCRSession = true;
85  
86      private boolean cleanUp = false;
87  
88      public JCRWorkItemStore(boolean useLifeTimeJCRSession, boolean cleanUp,
89              boolean shouldBackupWorkItems) {
90          this.useLifeTimeJCRSession = useLifeTimeJCRSession;
91          this.cleanUp = cleanUp;
92          this.shouldBackupWorkItems = shouldBackupWorkItems;
93      }
94  
95      public JCRWorkItemStore() throws Exception {
96          HierarchyManager hm = getHierarchyManager();
97  
98          if (shouldBackupWorkItems) {
99              // ensure the backup directory is there.
100             if (!hm.isExist(BACKUP)) {
101                 ContentUtil.createPath(hm, BACKUP, ItemType.CONTENT);
102                 hm.save();
103                 log.info("Created " + BACKUP + " in workflow store.");
104             }
105         }
106     }
107 
108     /**
109      * @return
110      */
111     protected HierarchyManager getHierarchyManager() {
112         if(useLifeTimeJCRSession){
113             return LifeTimeJCRSessionUtil.getHierarchyManager(WorkflowConstants.WORKSPACE_STORE);
114         }
115         else{
116             return MgnlContext.getSystemContext().getHierarchyManager(WorkflowConstants.WORKSPACE_STORE);
117         }
118     }
119 
120     /**
121      * Deletes or moves a workItem to the backup folder.
122      */
123     public synchronized void removeWorkItem(FlowExpressionId fei) throws StoreException {
124         try {
125             HierarchyManager hm = getHierarchyManager();
126             Content ct = getWorkItemById(fei);
127             if (ct != null) {
128                 // TODO : this behaviour could be hidden/wrapped in a special HierarchyManager
129                 if (!shouldBackupWorkItems) {
130                     ContentUtil.deleteAndRemoveEmptyParents(ct,1);
131                 } else {
132                     final ValueFactory vf = ct.getJCRNode().getSession().getValueFactory();
133                     ct.setNodeData("isBackup", vf.createValue(true));
134                     final Content parent = ct.getParent();
135                     final String pathInBackup = BACKUP + parent.getHandle();
136                     ContentUtil.createPath(hm, pathInBackup, ItemType.WORKITEM);
137                     hm.save();
138                     hm.moveTo(ct.getHandle(), BACKUP + ct.getHandle());
139                     // TODO : MAGNOLIA-1225 : we should only save here, once move uses session instead of workspace
140                 }
141 
142                 hm.save();
143                 log.debug("work item removed or moved to /backup");
144             }
145 
146         } catch (Exception e) {
147             log.error("exception when unstoring workitem:" + e, e);
148         }
149     }
150 
151     /**
152      * retrieve work item by
153      * @param storeName TODO : this parameter is not used ...
154      * @param fei
155      * @return
156      * @throws StoreException
157      */
158     public InFlowWorkItem retrieveWorkItem(final String storeName, final FlowExpressionId fei) throws StoreException {
159         if (log.isDebugEnabled()) {
160             log.debug("starting retrieve work item. this = " + this);
161             log.debug("retrieve work item for ID = " + fei.toParseableString());
162         }
163 
164         Content ct = getWorkItemById(fei);
165 
166         if (ct == null) {
167             throw new StoreException("cannot find workitem " + fei);
168         }
169 
170         try {
171             return loadWorkItem(ct);
172         }
173         catch (Exception e) {
174             throw new StoreException("load work item form xml failed", e);
175         }
176     }
177 
178     /**
179      * load a work item from a JCR content
180      * @param ct the content node
181      * @return
182      * @throws Exception
183      */
184     public InFlowWorkItem loadWorkItem(Content ct) throws Exception {
185         OwfeJcrBeanCoder coder = new OwfeJcrBeanCoder(null, new MgnlNode(ct.getContent(WorkflowConstants.NODEDATA_VALUE)));
186         return (InFlowWorkItem) coder.decode();
187     }
188 
189     /**
190      * retrieve a work item by participant name
191      * @param participant the full participant name (for example, user-superuser)
192      */
193     public Content getWorkItemByParticipant(String participant) {
194         String queryString = "//*[@participant=\"" + participant + "\"]";
195         if (log.isDebugEnabled()) {
196             log.debug("xpath query string = " + queryString);
197         }
198         List list = doQuery(queryString);
199         if (list != null && list.size() > 0) {
200             return (Content) list.get(0);
201         }
202 
203         return null;
204     }
205 
206     /**
207      * get work item by id
208      * @param fei
209      */
210     public Content getWorkItemById(FlowExpressionId fei) {
211         String path = createPathFromId(fei);
212         try {
213             return getHierarchyManager().getContent(path);
214         }
215         catch (Exception e) {
216             log.error("get work item by id failed, path = " + path, e);
217         }
218         return null;
219     }
220 
221     /**
222      * check whether the specified work item exists
223      * @param fei expression id of work item
224      * @return true if exist, false if not
225      */
226     public boolean hasWorkItem(FlowExpressionId fei) throws AccessDeniedException, RepositoryException {
227         String path = createPathFromId(fei);
228         if (StringUtils.isNotEmpty(path) && StringUtils.indexOf(path, "/") != 0) {
229             path = "/" + path;
230         }
231         return getHierarchyManager().isExist(path);
232     }
233 
234     /**
235      * check if the content contains the right work Item with same id
236      * @param ct JCR content
237      * @param eid id of work item
238      */
239     public boolean checkContentWithEID(Content ct, FlowExpressionId eid) {
240         String cid = ct.getNodeData(WorkflowConstants.NODEDATA_ID).getString();
241         if (log.isDebugEnabled()) {
242             log.debug("checkContentWithEID: ID = " + cid);
243         }
244         FlowExpressionId id = FlowExpressionId.fromParseableString(cid);
245         return id.equals(eid);
246     }
247 
248     /**
249      * convert the name to valid path
250      * @param id
251      */
252     public final String convertPath(String id) {
253         return StringUtils.replace(
254                 StringUtils.replace(id, WorkflowConstants.BAR, StringUtils.EMPTY),
255                 WorkflowConstants.COLON,
256                 WorkflowConstants.DOT);
257     }
258 
259     /**
260      * create the jcr node path for work Item by its id
261      * @param eid
262      */
263     public String createPathFromId(FlowExpressionId eid) {
264         String wlInstId = eid.getWorkflowInstanceId();
265         // TODO someone who knows the code better should have a look
266         String groupString = StringUtils.right(StringUtils.substringBefore(wlInstId, "."), 3);
267         int groupNumber = Integer.parseInt(groupString) % 100;
268         StringBuffer buffer = new StringBuffer(eid.getWorkflowDefinitionName());
269         buffer.append(WorkflowConstants.SLASH);
270         buffer.append(eid.getWorkflowDefinitionRevision());
271         buffer.append(WorkflowConstants.SLASH);
272         buffer.append(groupNumber);
273         buffer.append(WorkflowConstants.SLASH);
274         buffer.append(eid.getWorkflowInstanceId());
275         buffer.append(WorkflowConstants.SLASH);
276         buffer.append(eid.getExpressionName());
277         buffer.append(WorkflowConstants.SLASH);
278         buffer.append(eid.getExpressionId());
279 
280         return convertPath(buffer.toString());
281     }
282 
283     /**
284      * @param arg0 TODO : this parameter is not used ...
285      * @param wi   the work item to be stored
286      */
287     public synchronized void storeWorkItem(String arg0, InFlowWorkItem wi) throws StoreException {
288         try {
289             HierarchyManager hm = getHierarchyManager();
290             // delete it if already exist
291             if (hasWorkItem(wi.getId())) {
292                 // do not use removeWorkItem() since it persist changes immedietely
293                 hm.delete(createPathFromId(wi.getId()));
294             }
295 
296             // create path from work item id
297             String path = createPathFromId(wi.getId());
298             if (log.isDebugEnabled()) {
299                 log.debug("storing workitem with path = " + path);
300             }
301 
302             Content newc = ContentUtil.createPath(hm,path, ItemType.WORKITEM);
303 
304             ValueFactory vf = newc.getJCRNode().getSession().getValueFactory();
305             String sId = wi.getLastExpressionId().toParseableString();
306 
307             newc.createNodeData(WorkflowConstants.NODEDATA_ID, vf.createValue(sId));
308             newc.createNodeData(WorkflowConstants.NODEDATA_PARTICIPANT, vf.createValue(wi.getParticipantName()));
309 
310             StringAttribute assignTo = (StringAttribute) wi.getAttribute(WorkflowConstants.ATTRIBUTE_ASSIGN_TO);
311             if (assignTo != null) {
312                 String s = assignTo.toString();
313                 if (s.length() > 0) {
314                     newc.createNodeData(WorkflowConstants.ATTRIBUTE_ASSIGN_TO, vf.createValue(s));
315                 }
316             }
317 
318             // convert to xml string
319             encodeWorkItemToNode(wi, newc);
320             hm.save();
321 
322             if (log.isDebugEnabled()) {
323                 log.debug("store work item ok. ");
324             }
325         }
326         catch (Exception e) {
327             log.error("store work item failed", e);
328             throw new StoreException(e.toString());
329         }
330     }
331 
332     protected void encodeWorkItemToNode(InFlowWorkItem wi, Content newc) throws BeanCoderException {
333         OwfeJcrBeanCoder coder = new OwfeJcrBeanCoder(null, new MgnlNode(newc), WorkflowConstants.NODEDATA_VALUE);
334         coder.encode(wi);
335     }
336 
337     /**
338      * execute the xPath Query
339      */
340     public List doQuery(String queryString) {
341         return doQuery(queryString, Query.XPATH);
342     }
343 
344     public List doQuery(String queryString, String language) {
345         ArrayList list = new ArrayList();
346         if (log.isDebugEnabled()) {
347             log.debug("xpath query string: " + queryString);
348         }
349         try {
350             final QueryManager queryManager = MgnlContext.getSystemContext().getQueryManager(
351                     WorkflowConstants.WORKSPACE_STORE);
352             final Query q = queryManager.createQuery(queryString, language);
353 
354             QueryResult result = q.execute();
355             if (result == null) {
356                 log.info("query result was null");
357                 return null;
358             }
359 
360             Iterator it = result.getContent(WorkflowConstants.NODENAME_WORKITEM).iterator();
361             while (it.hasNext()) {
362                 Content ct = (Content) it.next();
363 
364                 // check for stale data.
365                 try {
366                     if (!getHierarchyManager().isExist(ct.getHandle())) {
367                         if (log.isDebugEnabled()) {
368                             log.debug(ct.getHandle() + " does not exist anymore.");
369                         }
370                         continue;
371                     }
372                 }
373                 catch (Exception e) {
374                     log.error("SKipping strange node");
375                 }
376 
377                 String title = ct.getTitle();
378                 String sname = ct.getName();
379 
380                 if (log.isDebugEnabled()) {
381                     log.debug("title=" + title);
382                     log.debug("name=" + sname);
383                 }
384 
385                 InFlowWorkItem wi = loadWorkItem(ct);
386                 if (wi == null) {
387                     log.error("can not load found workitem");
388                     continue;
389                 }
390                 if (log.isDebugEnabled()) {
391                     log.debug("added workitem to return list ok");
392                 }
393                 list.add(wi);
394             }
395         }
396         catch (Exception e) {
397             log.error("query flow failed", e);
398             return null;
399         }
400         return list;
401 
402     }
403 
404 }