View Javadoc

1   /**
2    * This file Copyright (c) 2003-2012 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     protected HierarchyManager getHierarchyManager() {
109         if(useLifeTimeJCRSession){
110             return LifeTimeJCRSessionUtil.getHierarchyManager(WorkflowConstants.WORKSPACE_STORE);
111         }
112         else{
113             return MgnlContext.getSystemContext().getHierarchyManager(WorkflowConstants.WORKSPACE_STORE);
114         }
115     }
116 
117     /**
118      * Deletes or moves a workItem to the backup folder.
119      */
120     public synchronized void removeWorkItem(FlowExpressionId fei) throws StoreException {
121         try {
122             HierarchyManager hm = getHierarchyManager();
123             Content ct = getWorkItemById(fei);
124             if (ct != null) {
125                 // TODO : this behaviour could be hidden/wrapped in a special HierarchyManager
126                 if (!shouldBackupWorkItems) {
127                     ContentUtil.deleteAndRemoveEmptyParents(ct,1);
128                 } else {
129                     final ValueFactory vf = ct.getJCRNode().getSession().getValueFactory();
130                     ct.setNodeData("isBackup", vf.createValue(true));
131                     final Content parent = ct.getParent();
132                     final String pathInBackup = BACKUP + parent.getHandle();
133                     ContentUtil.createPath(hm, pathInBackup, ItemType.WORKITEM);
134                     hm.save();
135                     hm.moveTo(ct.getHandle(), BACKUP + ct.getHandle());
136                     // TODO : MAGNOLIA-1225 : we should only save here, once move uses session instead of workspace
137                 }
138 
139                 hm.save();
140                 log.debug("work item removed or moved to /backup");
141             }
142 
143         } catch (Exception e) {
144             log.error("exception when unstoring workitem:" + e, e);
145         }
146     }
147 
148     /**
149      * retrieve work item by FlowExpressionId.
150      *
151      * @param storeName TODO : this parameter is not used ...
152      * @param fei
153      * @return
154      * @throws StoreException
155      */
156     public InFlowWorkItem retrieveWorkItem(final String storeName, final FlowExpressionId fei) throws StoreException {
157         if (log.isDebugEnabled()) {
158             log.debug("starting retrieve work item. this = " + this);
159             log.debug("retrieve work item for ID = " + fei.toParseableString());
160         }
161 
162         Content ct = getWorkItemById(fei);
163 
164         if (ct == null) {
165             throw new StoreException("cannot find workitem " + fei);
166         }
167 
168         try {
169             return loadWorkItem(ct);
170         }
171         catch (Exception e) {
172             throw new StoreException("load work item form xml failed", e);
173         }
174     }
175 
176     /**
177      * load a work item from a JCR content.
178      * @param ct the content node
179      * @return
180      * @throws Exception
181      */
182     public InFlowWorkItem loadWorkItem(Content ct) throws Exception {
183         OwfeJcrBeanCoder coder = new OwfeJcrBeanCoder(null, new MgnlNode(ct.getContent(WorkflowConstants.NODEDATA_VALUE)));
184         return (InFlowWorkItem) coder.decode();
185     }
186 
187     /**
188      * retrieve a work item by participant name.
189      * @param participant the full participant name (for example, user-superuser)
190      */
191     public Content getWorkItemByParticipant(String participant) {
192         String queryString = "//*[@participant=\"" + participant + "\"]";
193         if (log.isDebugEnabled()) {
194             log.debug("xpath query string = " + queryString);
195         }
196         List list = doQuery(queryString);
197         if (list != null && list.size() > 0) {
198             return (Content) list.get(0);
199         }
200 
201         return null;
202     }
203 
204     /**
205      * get work item by id.
206      * @param fei
207      */
208     public Content getWorkItemById(FlowExpressionId fei) {
209         String path = createPathFromId(fei);
210         try {
211             return getHierarchyManager().getContent(path);
212         }
213         catch (Exception e) {
214             log.error("get work item by id failed, path = " + path, e);
215         }
216         return null;
217     }
218 
219     /**
220      * check whether the specified work item exists.
221      * @param fei expression id of work item
222      * @return true if exist, false if not
223      */
224     public boolean hasWorkItem(FlowExpressionId fei) throws AccessDeniedException, RepositoryException {
225         String path = createPathFromId(fei);
226         if (StringUtils.isNotEmpty(path) && StringUtils.indexOf(path, "/") != 0) {
227             path = "/" + path;
228         }
229         return getHierarchyManager().isExist(path);
230     }
231 
232     /**
233      * check if the content contains the right work Item with same id.
234      * @param ct JCR content
235      * @param eid id of work item
236      */
237     public boolean checkContentWithEID(Content ct, FlowExpressionId eid) {
238         String cid = ct.getNodeData(WorkflowConstants.NODEDATA_ID).getString();
239         if (log.isDebugEnabled()) {
240             log.debug("checkContentWithEID: ID = " + cid);
241         }
242         FlowExpressionId id = FlowExpressionId.fromParseableString(cid);
243         return id.equals(eid);
244     }
245 
246     /**
247      * convert the name to valid path.
248      * @param id
249      */
250     public final String convertPath(String id) {
251         return StringUtils.replace(
252                 StringUtils.replace(id, WorkflowConstants.BAR, StringUtils.EMPTY),
253                 WorkflowConstants.COLON,
254                 WorkflowConstants.DOT);
255     }
256 
257     /**
258      * create the jcr node path for work Item by its id.
259      * @param eid
260      */
261     public String createPathFromId(FlowExpressionId eid) {
262         String wlInstId = eid.getWorkflowInstanceId();
263         // TODO someone who knows the code better should have a look
264         String groupString = StringUtils.right(StringUtils.substringBefore(wlInstId, "."), 3);
265         int groupNumber = Integer.parseInt(groupString) % 100;
266         StringBuffer buffer = new StringBuffer(eid.getWorkflowDefinitionName());
267         buffer.append(WorkflowConstants.SLASH);
268         buffer.append(eid.getWorkflowDefinitionRevision());
269         buffer.append(WorkflowConstants.SLASH);
270         buffer.append(groupNumber);
271         buffer.append(WorkflowConstants.SLASH);
272         buffer.append(eid.getWorkflowInstanceId());
273         buffer.append(WorkflowConstants.SLASH);
274         buffer.append(eid.getExpressionName());
275         buffer.append(WorkflowConstants.SLASH);
276         buffer.append(eid.getExpressionId());
277 
278         return convertPath(buffer.toString());
279     }
280 
281     /**
282      * Stores a workitem.
283      * @param arg0 TODO : this parameter is not used ...
284      * @param wi   the work item to be stored
285      */
286     public synchronized void storeWorkItem(String arg0, InFlowWorkItem wi) throws StoreException {
287         try {
288             HierarchyManager hm = getHierarchyManager();
289             // delete it if already exist
290             if (hasWorkItem(wi.getId())) {
291                 // do not use removeWorkItem() since it persist changes immedietely
292                 hm.delete(createPathFromId(wi.getId()));
293             }
294 
295             // create path from work item id
296             String path = createPathFromId(wi.getId());
297             if (log.isDebugEnabled()) {
298                 log.debug("storing workitem with path = " + path);
299             }
300 
301             Content newc = ContentUtil.createPath(hm,path, ItemType.WORKITEM);
302 
303             ValueFactory vf = newc.getJCRNode().getSession().getValueFactory();
304             String sId = wi.getLastExpressionId().toParseableString();
305 
306             newc.createNodeData(WorkflowConstants.NODEDATA_ID, vf.createValue(sId));
307             newc.createNodeData(WorkflowConstants.NODEDATA_PARTICIPANT, vf.createValue(wi.getParticipantName()));
308 
309             StringAttribute assignTo = (StringAttribute) wi.getAttribute(WorkflowConstants.ATTRIBUTE_ASSIGN_TO);
310             if (assignTo != null) {
311                 String s = assignTo.toString();
312                 if (s.length() > 0) {
313                     newc.createNodeData(WorkflowConstants.ATTRIBUTE_ASSIGN_TO, vf.createValue(s));
314                 }
315             }
316 
317             // convert to xml string
318             encodeWorkItemToNode(wi, newc);
319             hm.save();
320 
321             if (log.isDebugEnabled()) {
322                 log.debug("store work item ok. ");
323             }
324         }
325         catch (Exception e) {
326             log.error("store work item failed", e);
327             throw new StoreException(e.toString());
328         }
329     }
330 
331     protected void encodeWorkItemToNode(InFlowWorkItem wi, Content newc) throws BeanCoderException {
332         OwfeJcrBeanCoder coder = new OwfeJcrBeanCoder(null, new MgnlNode(newc), WorkflowConstants.NODEDATA_VALUE);
333         coder.encode(wi);
334     }
335 
336     /**
337      * execute the xPath Query.
338      */
339     public List doQuery(String queryString) {
340         return doQuery(queryString, Query.XPATH);
341     }
342 
343     public List doQuery(String queryString, String language) {
344         ArrayList list = new ArrayList();
345         if (log.isDebugEnabled()) {
346             log.debug("xpath query string: " + queryString);
347         }
348         try {
349             final QueryManager queryManager = MgnlContext.getSystemContext().getQueryManager(
350                     WorkflowConstants.WORKSPACE_STORE);
351             final Query q = queryManager.createQuery(queryString, language);
352 
353             QueryResult result = q.execute();
354             if (result == null) {
355                 log.info("query result was null");
356                 return null;
357             }
358 
359             Iterator it = result.getContent(WorkflowConstants.NODENAME_WORKITEM).iterator();
360             while (it.hasNext()) {
361                 Content ct = (Content) it.next();
362 
363                 // check for stale data.
364                 try {
365                     if (!getHierarchyManager().isExist(ct.getHandle())) {
366                         if (log.isDebugEnabled()) {
367                             log.debug(ct.getHandle() + " does not exist anymore.");
368                         }
369                         continue;
370                     }
371                 }
372                 catch (Exception e) {
373                     log.error("SKipping strange node");
374                 }
375 
376                 String title = ct.getTitle();
377                 String sname = ct.getName();
378 
379                 if (log.isDebugEnabled()) {
380                     log.debug("title=" + title);
381                     log.debug("name=" + sname);
382                 }
383 
384                 InFlowWorkItem wi = loadWorkItem(ct);
385                 if (wi == null) {
386                     log.error("can not load found workitem");
387                     continue;
388                 }
389                 if (log.isDebugEnabled()) {
390                     log.debug("added workitem to return list ok");
391                 }
392                 list.add(wi);
393             }
394         }
395         catch (Exception e) {
396             log.error("query flow failed", e);
397             return null;
398         }
399         return list;
400 
401     }
402 
403 }