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.cms.core.Content;
37  import info.magnolia.cms.core.HierarchyManager;
38  import info.magnolia.cms.core.ItemType;
39  import info.magnolia.cms.core.search.Query;
40  import info.magnolia.cms.core.search.QueryManager;
41  import info.magnolia.cms.core.search.QueryResult;
42  import info.magnolia.cms.util.ContentUtil;
43  import info.magnolia.context.LifeTimeJCRSessionUtil;
44  import info.magnolia.context.MgnlContext;
45  import info.magnolia.module.workflow.WorkflowConstants;
46  
47  import java.io.InputStream;
48  import java.util.ArrayList;
49  import java.util.Iterator;
50  import java.util.Map;
51  import java.util.NoSuchElementException;
52  
53  import javax.jcr.RepositoryException;
54  import javax.jcr.ValueFactory;
55  
56  import openwfe.org.ApplicationContext;
57  import openwfe.org.ServiceException;
58  import openwfe.org.engine.expool.PoolException;
59  import openwfe.org.engine.expressions.FlowExpression;
60  import openwfe.org.engine.expressions.FlowExpressionId;
61  import openwfe.org.engine.expressions.raw.RawExpression;
62  import openwfe.org.engine.impl.expool.AbstractExpressionStore;
63  import openwfe.org.util.beancoder.XmlBeanCoder;
64  import openwfe.org.xml.XmlUtils;
65  
66  import org.jdom.Document;
67  import org.jdom.input.SAXBuilder;
68  import org.slf4j.Logger;
69  import org.slf4j.LoggerFactory;
70  
71  /**
72   * The JCR implementation of the expression store.
73   * 
74   * @author Jackie Ju
75   * @author Nicolas Modrzyk
76   * @author John Mettraux
77   * @author gjoseph
78   */
79  public class JCRExpressionStore extends AbstractExpressionStore {
80      private static final Logger log = LoggerFactory.getLogger(JCRExpressionStore.class);
81  
82      private static final String ENGINE_ID = "ee";
83  
84      private boolean useLifeTimeJCRSession = true;
85  
86      private boolean cleanUp = false;
87  
88      public JCRExpressionStore(boolean useLifeTimeJCRSession, boolean cleanUp) {
89          super();
90          this.useLifeTimeJCRSession = useLifeTimeJCRSession;
91          this.cleanUp = cleanUp;
92      }
93  
94      public void init(final String serviceName, final ApplicationContext context, final Map serviceParams) throws ServiceException {
95          super.init(serviceName, context, serviceParams);
96      }
97  
98      /**
99       * Stores one expresion
100      */
101     public synchronized void storeExpression(final FlowExpression fe) throws PoolException {
102         boolean release = !useLifeTimeJCRSession && !MgnlContext.hasInstance();
103         HierarchyManager hm = null;
104         try {
105             hm = getHierarchyManager();
106             final Content cExpression = findOrCreateExpression(fe, hm);
107 
108             log.debug("storeExpression() handle is " + cExpression.getHandle());
109 
110             // set expressionId as attribte id
111             ValueFactory vf = cExpression.getJCRNode().getSession().getValueFactory();
112             String value = fe.getId().toParseableString();
113 
114             cExpression.createNodeData(WorkflowConstants.NODEDATA_ID, vf.createValue(value));
115 
116             // serializeExpressionWithBeanCoder(ct, fe);
117             serializeExpressionAsXml(cExpression, fe);
118 
119             hm.save();
120         } catch (Exception e) {
121             log.error("storeExpression() store exception failed", e);
122             try {
123                 if (hm.hasPendingChanges()) {
124                     hm.refresh(true);
125                 }
126             } catch (RepositoryException e1) {
127                 log.error("Corrupted HM during WKF access", e);
128             }
129             throw new PoolException("storeExpression() store exception failed", e);
130         } finally {
131             if (release) {
132                 MgnlContext.release();
133             }
134         }
135     }
136 
137     /**
138      * Removes the expression from the JCR storage.
139      */
140     public synchronized void unstoreExpression(final FlowExpression fe) throws PoolException {
141         boolean release = !useLifeTimeJCRSession && !MgnlContext.hasInstance();
142         try {
143             final HierarchyManager hm = getHierarchyManager();
144             final Content cExpression = findOrCreateExpression(fe, hm);
145 
146             if (cExpression != null) {
147                 if (cleanUp) {
148                     ContentUtil.deleteAndRemoveEmptyParents(cExpression, 1);
149                 } else {
150                     cExpression.delete();
151                 }
152                 hm.save();
153             } else {
154                 log.info("unstoreExpression() " + "didn't find content node for fe " + fe.getId().toParseableString());
155             }
156         } catch (Exception e) {
157             log.error("unstoreExpression() unstore exception failed", e);
158             throw new PoolException("unstoreExpression() unstore exception failed", e);
159         } finally {
160             if (release) {
161                 MgnlContext.release();
162             }
163         }
164     }
165 
166     /**
167      * Returns an iterator on the content of that expression store.
168      */
169     public synchronized Iterator contentIterator(final Class assignClass) {
170         try {
171             return new StoreIterator(assignClass);
172         } catch (final Throwable t) {
173             log.error("contentIterator() failed to set up an iterator", t);
174         }
175 
176         // TODO : does this need Iterator need to be modifiable? otherwise just
177         // return Collections.emptyList()
178         // return null;
179         return new ArrayList(0).iterator();
180     }
181 
182     /**
183      * Loads an expression given its id.
184      */
185     public synchronized FlowExpression loadExpression(final FlowExpressionId fei) throws PoolException {
186         try {
187             Content cExpression = findExpression(fei, getHierarchyManager());
188 
189             if (cExpression != null) {
190                 final FlowExpression expression = deserializeExpressionAsXml(cExpression);
191                 if (expression != null) {
192                     expression.setApplicationContext(getContext());
193                     return expression;
194                 }
195             }
196         } catch (final Exception e) {
197             log.error("loadExpression() failed for " + fei.asStringId(), e);
198 
199             throw new PoolException("loadExpression() failed for " + fei.asStringId(), e);
200         }
201 
202         // this is normal after clean installation or manual cleanup of the expressions workspace
203         log.info("Expected expression " + fei.asStringId() + " was not found in the repository.");
204 
205         throw new PoolException("loadExpression() " + "didn't find expression " + fei.asStringId() + " in the repository");
206     }
207 
208     /**
209      * Returns the number of expressions currently stored in that store.
210      */
211     public int size() {
212         try {
213             final QueryManager qm = MgnlContext.getSystemContext().getQueryManager(WorkflowConstants.WORKSPACE_EXPRESSION);
214             Query q = qm.createQuery(WorkflowConstants.STORE_ITERATOR_QUERY, Query.SQL);
215             QueryResult qr = q.execute();
216 
217             return qr.getContent().size();
218         } catch (final Exception e) {
219             log.error("size() failed", e);
220             return -1;
221         }
222     }
223 
224     private void serializeExpressionAsXml(Content c, FlowExpression fe) throws Exception {
225         final org.jdom.Document doc = XmlBeanCoder.xmlEncode(fe);
226         String s = XmlUtils.toString(doc, null);
227         ValueFactory vf = c.getJCRNode().getSession().getValueFactory();
228         c.createNodeData(WorkflowConstants.NODEDATA_VALUE, vf.createValue(s));
229     }
230 
231     public final String toXPathFriendlyString(final FlowExpressionId fei) {
232         final StringBuffer buffer = new StringBuffer();
233         final String engineId = fei.getEngineId();
234 
235         buffer.append(WorkflowConstants.SLASH);
236         buffer.append(engineId);
237 
238         // engine storage
239         if (engineId.equals(ENGINE_ID)) {
240             return buffer.toString();
241         }
242 
243         buffer.append(WorkflowConstants.SLASH);
244         buffer.append(fei.getWorkflowDefinitionName());
245 
246         buffer.append(WorkflowConstants.SLASH);
247         buffer.append(fei.getWorkflowInstanceId());
248 
249         buffer.append(WorkflowConstants.SLASH);
250         buffer.append(fei.getExpressionId());
251         buffer.append("__");
252         buffer.append(fei.getExpressionName());
253 
254         return buffer.toString();
255     }
256 
257     private Content findOrCreateExpression(final FlowExpression fe, HierarchyManager hm) throws Exception {
258         Content content = findExpression(fe.getId(), hm);
259         if (content == null) {
260             content = ContentUtil.createPath(hm, toXPathFriendlyString(fe.getId()), ItemType.EXPRESSION);
261         }
262         return content;
263     }
264 
265     private Content findExpression(final FlowExpressionId fei, HierarchyManager hm) throws Exception {
266         if (log.isDebugEnabled()) {
267             log.debug("findExpression() looking for " + fei.toParseableString());
268         }
269 
270         final String path = toXPathFriendlyString(fei);
271 
272         if (hm.isExist(path)) {
273             return hm.getContent(path);
274         } else {
275             return null;
276         }
277     }
278 
279     private FlowExpression deserializeExpressionAsXml(final Content c) throws Exception {
280         final InputStream is = c.getNodeData(WorkflowConstants.NODEDATA_VALUE).getStream();
281 
282         if (is == null) {
283             return null;
284         }
285 
286         final SAXBuilder builder = new SAXBuilder();
287         final Document doc = builder.build(is);
288         return (FlowExpression) XmlBeanCoder.xmlDecode(doc);
289     }
290 
291     protected HierarchyManager getHierarchyManager() {
292         HierarchyManager hm;
293         if (useLifeTimeJCRSession) {
294             hm = LifeTimeJCRSessionUtil.getHierarchyManager(WorkflowConstants.WORKSPACE_EXPRESSION);
295         } else {
296             hm = MgnlContext.getSystemContext().getHierarchyManager(WorkflowConstants.WORKSPACE_EXPRESSION);
297         }
298         try {
299             if (hm.hasPendingChanges()) {
300                 // If this happens it might be related to MAGNOLIA-2172
301                 // the methods of the expression store are synchronized so this
302                 // should not happen!
303                 log.warn("The workflow expression session has pending changes while " + (useLifeTimeJCRSession ? "" : "not ") + "using Life Time session. Will clean the session",
304                         new Exception());
305                 hm.refresh(true);
306             }
307         } catch (RepositoryException e) {
308             // should really not happen
309             log.error("Can't check/refresh worflow expression session.", e);
310         }
311         return hm;
312     }
313 
314     /**
315      * 'lightweight' storeIterator. The previous version were stuffing all the
316      * expression within a collection and returning an iterator on it.
317      * <p>
318      * The remaining question is : what's behind Magnolia's Content.iterator()
319      * method ?
320      */
321     protected final class StoreIterator implements Iterator {
322         private final Class assignClass;
323         private Iterator rootIterator = null;
324         private FlowExpression next = null;
325 
326         public StoreIterator(final Class assignClass) throws Exception {
327             super();
328 
329             this.assignClass = assignClass;
330 
331             final QueryManager qm = LifeTimeJCRSessionUtil.getQueryManager(WorkflowConstants.WORKSPACE_EXPRESSION);
332 
333             final Query query = qm.createQuery(WorkflowConstants.STORE_ITERATOR_QUERY, Query.SQL);
334 
335             final QueryResult qr = query.execute();
336 
337             if (log.isDebugEnabled()) {
338                 log.debug("() query found " + qr.getContent("expression").size() + " elements");
339             }
340 
341             this.rootIterator = qr.getContent("expression").iterator();
342 
343             this.next = fetchNext();
344         }
345 
346         public boolean hasNext() {
347             return (this.next != null);
348         }
349 
350         public FlowExpression fetchNext() {
351             if (!this.rootIterator.hasNext()) {
352                 return null;
353             }
354 
355             final Content content = (Content) this.rootIterator.next();
356             try {
357                 final FlowExpression fe = deserializeExpressionAsXml(content);
358 
359                 if (fe == null) {
360                     return fetchNext();
361                 }
362 
363                 fe.setApplicationContext(getContext());
364 
365                 if (!isAssignableFromClass(fe, this.assignClass)) {
366                     return fetchNext();
367                 }
368 
369                 return fe;
370             } catch (final Exception e) {
371                 log.error("fetchNext() problem", e);
372                 return null;
373             }
374         }
375 
376         public Object next() throws java.util.NoSuchElementException {
377             final FlowExpression current = this.next;
378 
379             if (current == null) {
380                 throw new NoSuchElementException();
381             }
382 
383             this.next = fetchNext();
384 
385             if (log.isDebugEnabled()) {
386                 log.debug("next() is  " + (next != null ? next.getId().toString() : "'null'"));
387             }
388 
389             return current;
390         }
391 
392         public void remove() {
393             throw new UnsupportedOperationException();
394         }
395 
396         // TODO : this was copied from ExpoolUtils, adding a fix for
397         // MAGNOLIA-1131
398         private boolean isAssignableFromClass(final FlowExpression fe, final Class expClass) {
399             if (expClass == null) {
400                 return true;
401             }
402 
403             Class c = fe.getClass();
404 
405             if (fe instanceof RawExpression) {
406                 c = fe.getExpressionClass();
407                 if (c == null) {
408                     // TODO : fe.getDefinitionName() does not return the xml's
409                     // root name as I expected ... (but its name attribute
410                     // instead)
411                     log.warn("Skipping expression " + fe.getId() + " (" + ((RawExpression) fe).getDefinitionName() + ")");
412                     return false;
413                 }
414             }
415 
416             return expClass.isAssignableFrom(c);
417         }
418     }
419 
420 }