1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.module.workflow.jcr;
35
36 import info.magnolia.cms.core.HierarchyManager;
37 import info.magnolia.cms.core.ItemType;
38 import info.magnolia.cms.core.search.Query;
39 import info.magnolia.cms.core.search.QueryManager;
40 import info.magnolia.cms.core.search.QueryResult;
41 import info.magnolia.cms.security.AccessDeniedException;
42 import info.magnolia.cms.util.ContentUtil;
43 import info.magnolia.context.LifeTimeJCRSessionUtil;
44 import info.magnolia.context.MgnlContext;
45 import info.magnolia.context.MgnlContext.Op;
46 import info.magnolia.module.workflow.WorkflowConstants;
47
48 import java.io.InputStream;
49 import java.util.ArrayList;
50 import java.util.Iterator;
51 import java.util.Map;
52 import java.util.NoSuchElementException;
53
54 import javax.jcr.Node;
55 import javax.jcr.PathNotFoundException;
56 import javax.jcr.RepositoryException;
57
58 import openwfe.org.ApplicationContext;
59 import openwfe.org.ServiceException;
60 import openwfe.org.engine.expool.PoolException;
61 import openwfe.org.engine.expressions.FlowExpression;
62 import openwfe.org.engine.expressions.FlowExpressionId;
63 import openwfe.org.engine.expressions.raw.RawExpression;
64 import openwfe.org.engine.impl.expool.AbstractExpressionStore;
65 import openwfe.org.util.beancoder.XmlBeanCoder;
66 import openwfe.org.xml.XmlUtils;
67
68 import org.apache.jackrabbit.commons.iterator.FilteringNodeIterator;
69 import org.apache.jackrabbit.commons.predicate.Predicate;
70 import org.jdom.Document;
71 import org.jdom.input.SAXBuilder;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74
75
76
77
78
79
80
81
82
83 public class JCRExpressionStore extends AbstractExpressionStore {
84 private static final Logger log = LoggerFactory.getLogger(JCRExpressionStore.class);
85
86 private static final String ENGINE_ID = "ee";
87
88 private boolean useLifeTimeJCRSession = true;
89
90 private boolean cleanUp = false;
91
92 public JCRExpressionStore(boolean useLifeTimeJCRSession, boolean cleanUp) {
93 super();
94 this.useLifeTimeJCRSession = useLifeTimeJCRSession;
95 this.cleanUp = cleanUp;
96 }
97
98 @Override
99 public void init(final String serviceName, final ApplicationContext context, final Map serviceParams) throws ServiceException {
100 super.init(serviceName, context, serviceParams);
101 }
102
103
104
105
106 @Override
107 public synchronized void storeExpression(final FlowExpression fe) throws PoolException {
108 boolean release = !useLifeTimeJCRSession && !MgnlContext.hasInstance();
109 HierarchyManager hm = null;
110 try {
111 hm = getHierarchyManager();
112 final Node cExpression = findOrCreateExpression(fe, hm);
113
114 log.debug("storeExpression() handle is " + cExpression.getPath());
115
116 MgnlContext.doInSystemContext(new Op<Void, RepositoryException>() {
117 @Override
118 public Void exec() throws RepositoryException {
119
120 String value = fe.getId().toParseableString();
121
122 cExpression.setProperty(WorkflowConstants.NODEDATA_ID, value);
123 return null;
124 }
125 }, true);
126
127 serializeExpressionAsXml(cExpression, fe);
128
129 hm.save();
130 } catch (Exception e) {
131 log.error("storeExpression() store exception failed", e);
132 try {
133 if (hm != null && hm.hasPendingChanges()) {
134 hm.refresh(true);
135 }
136 } catch (RepositoryException e1) {
137 log.error("Corrupted HM during WKF access", e);
138 }
139 throw new PoolException("storeExpression() store exception failed", e);
140 } finally {
141 if (release) {
142 MgnlContext.release();
143 }
144 }
145 }
146
147
148
149
150 @Override
151 public synchronized void unstoreExpression(final FlowExpression fe) throws PoolException {
152 boolean release = !useLifeTimeJCRSession && !MgnlContext.hasInstance();
153 try {
154 final HierarchyManager hm = getHierarchyManager();
155 final Node cExpression = findOrCreateExpression(fe, hm);
156
157 if (cExpression != null) {
158 if (cleanUp) {
159 MgnlContext.doInSystemContext(new Op<Void, RepositoryException>() {
160 @Override
161 public Void exec() throws RepositoryException {
162 deleteAndRemoveEmptyParents(cExpression, 1);
163 return null;
164 }
165 }, true);
166 } else {
167 cExpression.remove();
168 }
169 hm.save();
170 } else {
171 log.info("unstoreExpression() " + "didn't find content node for fe " + fe.getId().toParseableString());
172 }
173 } catch (Exception e) {
174 log.error("unstoreExpression() unstore exception failed", e);
175 throw new PoolException("unstoreExpression() unstore exception failed", e);
176 } finally {
177 if (release) {
178 MgnlContext.release();
179 }
180 }
181 }
182
183 public static void deleteAndRemoveEmptyParents(Node node, int level) throws PathNotFoundException, RepositoryException,
184 AccessDeniedException {
185 Node parent = null;
186 if (node.getDepth() != 0) {
187 parent = node.getParent();
188 }
189 node.remove();
190 if (parent != null && parent.getDepth() > level && new FilteringNodeIterator(parent.getNodes(), new Predicate() {
191
192 @Override
193 public boolean evaluate(Object obj) {
194 if (obj instanceof Node) {
195 Node content = (Node) obj;
196 try {
197 return !content.getName().startsWith("jcr:") && !content.isNodeType(ItemType.NT_METADATA);
198 } catch (RepositoryException e) {
199 return false;
200 }
201 }
202 return false;
203 }
204 }).hasNext() == false) {
205 deleteAndRemoveEmptyParents(parent, level);
206 }
207 }
208
209
210
211
212 @Override
213 public synchronized Iterator contentIterator(final Class assignClass) {
214 try {
215 return new StoreIterator(assignClass);
216 } catch (final Throwable t) {
217 log.error("contentIterator() failed to set up an iterator", t);
218 }
219
220
221
222
223 return new ArrayList(0).iterator();
224 }
225
226
227
228
229 @Override
230 public synchronized FlowExpression loadExpression(final FlowExpressionId fei) throws PoolException {
231 try {
232 Node cExpression = findExpression(fei, getHierarchyManager());
233
234 if (cExpression != null) {
235 final FlowExpression expression = deserializeExpressionAsXml(cExpression);
236 if (expression != null) {
237 expression.setApplicationContext(getContext());
238 return expression;
239 }
240 }
241 } catch (final Exception e) {
242 log.error("loadExpression() failed for " + fei.asStringId(), e);
243
244 throw new PoolException("loadExpression() failed for " + fei.asStringId(), e);
245 }
246
247
248 log.info("Expected expression " + fei.asStringId() + " was not found in the repository.");
249
250 throw new PoolException("loadExpression() " + "didn't find expression " + fei.asStringId() + " in the repository");
251 }
252
253
254
255
256 @Override
257 public int size() {
258 try {
259 final QueryManager qm = MgnlContext.getSystemContext().getQueryManager(WorkflowConstants.WORKSPACE_EXPRESSION);
260 Query q = qm.createQuery(WorkflowConstants.STORE_ITERATOR_QUERY, Query.SQL);
261 QueryResult qr = q.execute();
262
263 return qr.getContent().size();
264 } catch (final Exception e) {
265 log.error("size() failed", e);
266 return -1;
267 }
268 }
269
270 private void serializeExpressionAsXml(final Node c, FlowExpression fe) throws Exception {
271 final org.jdom.Document doc = XmlBeanCoder.xmlEncode(fe);
272 final String s = XmlUtils.toString(doc, null);
273
274 MgnlContext.doInSystemContext(new Op<Void, RepositoryException>() {
275 @Override
276 public Void exec() throws RepositoryException {
277 c.setProperty(WorkflowConstants.NODEDATA_VALUE, s);
278 return null;
279 }
280 }, true);
281 }
282
283 public final String toXPathFriendlyString(final FlowExpressionId fei) {
284 final StringBuffer buffer = new StringBuffer();
285 final String engineId = fei.getEngineId();
286
287 buffer.append(WorkflowConstants.SLASH);
288 buffer.append(engineId);
289
290
291 if (engineId.equals(ENGINE_ID)) {
292 return buffer.toString();
293 }
294
295 buffer.append(WorkflowConstants.SLASH);
296 buffer.append(fei.getWorkflowDefinitionName());
297
298 buffer.append(WorkflowConstants.SLASH);
299 buffer.append(fei.getWorkflowInstanceId());
300
301 buffer.append(WorkflowConstants.SLASH);
302 buffer.append(fei.getExpressionId());
303 buffer.append("__");
304 buffer.append(fei.getExpressionName());
305
306 return buffer.toString();
307 }
308
309 private Node findOrCreateExpression(final FlowExpression fe, HierarchyManager hm) throws Exception {
310 Node content = findExpression(fe.getId(), hm);
311 if (content == null) {
312 content = ContentUtil.createPath(hm, toXPathFriendlyString(fe.getId()), ItemType.EXPRESSION).getJCRNode();
313 }
314 return content;
315 }
316
317 private Node findExpression(final FlowExpressionId fei, HierarchyManager hm) throws Exception {
318 if (log.isDebugEnabled()) {
319 log.debug("findExpression() looking for " + fei.toParseableString());
320 }
321
322 final String path = toXPathFriendlyString(fei);
323
324 if (hm.isExist(path)) {
325 return hm.getContent(path).getJCRNode();
326 } else {
327 return null;
328 }
329 }
330
331 private FlowExpression deserializeExpressionAsXml(final Node c) throws Exception {
332 if (!c.hasProperty(WorkflowConstants.NODEDATA_VALUE)) {
333 return null;
334 }
335 final InputStream is = c.getProperty(WorkflowConstants.NODEDATA_VALUE).getStream();
336
337 final SAXBuilder builder = new SAXBuilder();
338 final Document doc = builder.build(is);
339 return (FlowExpression) XmlBeanCoder.xmlDecode(doc);
340 }
341
342 protected HierarchyManager getHierarchyManager() {
343 HierarchyManager hm;
344 if (useLifeTimeJCRSession) {
345 hm = LifeTimeJCRSessionUtil.getHierarchyManager(WorkflowConstants.WORKSPACE_EXPRESSION);
346 } else {
347 hm = MgnlContext.getSystemContext().getHierarchyManager(WorkflowConstants.WORKSPACE_EXPRESSION);
348 }
349 try {
350 if (hm.hasPendingChanges()) {
351
352
353
354 log.warn("The workflow expression session has pending changes while " + (useLifeTimeJCRSession ? "" : "not ") + "using Life Time session. Will clean the session",
355 new Exception());
356 hm.refresh(true);
357 }
358 } catch (RepositoryException e) {
359
360 log.error("Can't check/refresh worflow expression session.", e);
361 }
362 return hm;
363 }
364
365
366
367
368
369
370
371
372 protected final class StoreIterator implements Iterator {
373 private final Class assignClass;
374 private Iterator rootIterator = null;
375 private FlowExpression next = null;
376
377 public StoreIterator(final Class assignClass) throws Exception {
378 super();
379
380 this.assignClass = assignClass;
381
382 javax.jcr.query.QueryManager qm = LifeTimeJCRSessionUtil.getHierarchyManager(WorkflowConstants.WORKSPACE_EXPRESSION).getWorkspace().getQueryManager();
383
384 final javax.jcr.query.Query query = qm.createQuery(WorkflowConstants.STORE_ITERATOR_QUERY, Query.SQL);
385
386 final javax.jcr.query.QueryResult qr = query.execute();
387
388 if (log.isDebugEnabled()) {
389 log.debug("() query found " + qr.getNodes().getSize() + " elements");
390 }
391
392 this.rootIterator = qr.getNodes();
393
394 this.next = fetchNext();
395 }
396
397 @Override
398 public boolean hasNext() {
399 return this.next != null;
400 }
401
402 public FlowExpression fetchNext() {
403 if (!this.rootIterator.hasNext()) {
404 return null;
405 }
406
407 FlowExpression fe = null;
408 do {
409 fe = fetchMaybeNext();
410
411 if (fe != null) {
412 if (isAssignableFromClass(fe, this.assignClass)) {
413 return fe;
414 } else {
415 fe = null;
416 }
417 }
418
419 if (!this.rootIterator.hasNext()) {
420
421 return null;
422 }
423 } while (fe == null);
424 return null;
425 }
426
427 private FlowExpression fetchMaybeNext() {
428 if (!this.rootIterator.hasNext()) {
429 return null;
430 }
431
432 final Node content = (Node) this.rootIterator.next();
433 try {
434 final FlowExpression fe = deserializeExpressionAsXml(content);
435
436 if (fe == null) {
437 return null;
438 }
439
440 fe.setApplicationContext(getContext());
441
442
443 return fe;
444 } catch (final Exception e) {
445 log.error("fetchNext() problem", e);
446 return null;
447 }
448 }
449
450 @Override
451 public Object next() throws java.util.NoSuchElementException {
452 final FlowExpression current = this.next;
453
454 if (current == null) {
455 throw new NoSuchElementException();
456 }
457
458 this.next = fetchNext();
459
460 if (log.isDebugEnabled()) {
461 log.debug("next() is " + (next != null ? next.getId().toString() : "'null'"));
462 }
463
464 return current;
465 }
466
467 @Override
468 public void remove() {
469 throw new UnsupportedOperationException();
470 }
471
472
473
474 private boolean isAssignableFromClass(final FlowExpression fe, final Class expClass) {
475 if (expClass == null) {
476 return true;
477 }
478
479
480 if (fe instanceof RawExpression) {
481 Class c = fe.getExpressionClass();
482 if (c == null) {
483
484
485
486 log.warn("Skipping expression " + fe.getId() + " (" + ((RawExpression) fe).getDefinitionName() + ")");
487 return false;
488 }
489 }
490
491 Class c = fe.getClass();
492 return expClass.isAssignableFrom(c);
493 }
494 }
495
496 }