View Javadoc

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