001: /*
002: * Copyright (c) 2002-2003 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.workflow.spi.hibernate3;
006:
007: import com.opensymphony.module.propertyset.PropertySet;
008:
009: import com.opensymphony.workflow.QueryNotSupportedException;
010: import com.opensymphony.workflow.StoreException;
011: import com.opensymphony.workflow.query.FieldExpression;
012: import com.opensymphony.workflow.query.NestedExpression;
013: import com.opensymphony.workflow.query.WorkflowExpressionQuery;
014: import com.opensymphony.workflow.query.WorkflowQuery;
015: import com.opensymphony.workflow.spi.Step;
016: import com.opensymphony.workflow.spi.WorkflowEntry;
017: import com.opensymphony.workflow.spi.WorkflowStore;
018: import com.opensymphony.workflow.spi.hibernate.HibernateCurrentStep;
019: import com.opensymphony.workflow.spi.hibernate.HibernateHistoryStep;
020: import com.opensymphony.workflow.spi.hibernate.HibernateStep;
021: import com.opensymphony.workflow.spi.hibernate.HibernateWorkflowEntry;
022: import com.opensymphony.workflow.util.PropertySetDelegate;
023:
024: import org.hibernate.Criteria;
025: import org.hibernate.HibernateException;
026: import org.hibernate.Session;
027:
028: import org.hibernate.criterion.Criterion;
029: import org.hibernate.criterion.Expression;
030:
031: import java.util.ArrayList;
032: import java.util.Collection;
033: import java.util.Date;
034: import java.util.HashSet;
035: import java.util.Iterator;
036: import java.util.List;
037: import java.util.Set;
038:
039: /**
040: * @author Luca Masini
041: * @since 2005-9-23
042: *
043: */
044: public abstract class AbstractHibernateWorkflowStore implements
045: WorkflowStore {
046: //~ Instance fields ////////////////////////////////////////////////////////
047:
048: private PropertySetDelegate propertySetDelegate;
049: private String cacheRegion = null;
050: private boolean cacheable = false;
051:
052: //~ Methods ////////////////////////////////////////////////////////////////
053:
054: // ~ Getter/Setter ////////////////////////////////////////////////////////////////
055: public void setCacheRegion(String cacheRegion) {
056: this .cacheRegion = cacheRegion;
057: }
058:
059: public void setCacheable(boolean cacheable) {
060: this .cacheable = cacheable;
061: }
062:
063: public void setEntryState(final long entryId, final int state)
064: throws StoreException {
065: loadEntry(entryId).setState(state);
066: }
067:
068: public PropertySet getPropertySet(long entryId)
069: throws StoreException {
070: if (getPropertySetDelegate() == null) {
071: throw new StoreException(
072: "PropertySetDelegate is not properly configured");
073: }
074:
075: return getPropertySetDelegate().getPropertySet(entryId);
076: }
077:
078: public void setPropertySetDelegate(
079: PropertySetDelegate propertySetDelegate) {
080: this .propertySetDelegate = propertySetDelegate;
081: }
082:
083: public PropertySetDelegate getPropertySetDelegate() {
084: return propertySetDelegate;
085: }
086:
087: public Step createCurrentStep(final long entryId, final int stepId,
088: final String owner, final Date startDate,
089: final Date dueDate, final String status,
090: final long[] previousIds) throws StoreException {
091: final HibernateWorkflowEntry entry = loadEntry(entryId);
092: final HibernateCurrentStep step = new HibernateCurrentStep();
093:
094: step.setStepId(stepId);
095: step.setOwner(owner);
096: step.setStartDate(startDate);
097: step.setDueDate(dueDate);
098: step.setStatus(status);
099:
100: // This is for backward compatibility, but current Store doesn't
101: // persist this collection, nor is such property visibile outside
102: // OSWF internal classes
103: List previousSteps = new ArrayList(previousIds.length);
104:
105: for (int i = 0; i < previousIds.length; i++) {
106: HibernateCurrentStep previousStep = new HibernateCurrentStep();
107: previousSteps.add(previousStep);
108: }
109:
110: step.setPreviousSteps(previousSteps);
111:
112: entry.addCurrentSteps(step);
113:
114: // We need to save here because we soon will need the stepId
115: // that hibernate calculate on save or flush
116: save(step);
117:
118: return step;
119: }
120:
121: public WorkflowEntry createEntry(String workflowName)
122: throws StoreException {
123: final HibernateWorkflowEntry entry = new HibernateWorkflowEntry();
124: entry.setState(WorkflowEntry.CREATED);
125: entry.setWorkflowName(workflowName);
126: save(entry);
127:
128: return entry;
129: }
130:
131: public List findCurrentSteps(final long entryId)
132: throws StoreException {
133: // We are asking for current step list, so here we have an anti-lazy
134: // copy of the Hibernate array in memory. This also prevents problem
135: // in case the use is going with a pattern that span a session
136: // for method call
137: return new ArrayList(loadEntry(entryId).getCurrentSteps());
138: }
139:
140: public WorkflowEntry findEntry(long entryId) throws StoreException {
141: return loadEntry(entryId);
142: }
143:
144: public List findHistorySteps(final long entryId)
145: throws StoreException {
146: // We are asking for current step list, so here we have an anti-lazy
147: // copy of the Hibernate array in memory. This also prevents problem
148: // in case the use is going with a pattern that span a session
149: // for method call
150: return new ArrayList(loadEntry(entryId).getHistorySteps());
151: }
152:
153: public Step markFinished(Step step, int actionId, Date finishDate,
154: String status, String caller) throws StoreException {
155: final HibernateCurrentStep currentStep = (HibernateCurrentStep) step;
156:
157: currentStep.setActionId(actionId);
158: currentStep.setFinishDate(finishDate);
159: currentStep.setStatus(status);
160: currentStep.setCaller(caller);
161:
162: return currentStep;
163: }
164:
165: public void moveToHistory(final Step step) throws StoreException {
166: final HibernateCurrentStep currentStep = (HibernateCurrentStep) step;
167: final HibernateWorkflowEntry entry = currentStep.getEntry();
168: final HibernateHistoryStep hStep = new HibernateHistoryStep(
169: currentStep);
170:
171: entry.removeCurrentSteps(currentStep);
172: delete(currentStep);
173: entry.addHistorySteps(hStep);
174:
175: // We need to save here because we soon will need the stepId
176: // that hibernate calculate on save or flush
177: save(hStep);
178: }
179:
180: public List query(final WorkflowQuery query) throws StoreException {
181: return (List) execute(new InternalCallback() {
182: public Object doInHibernate(Session session)
183: throws HibernateException, StoreException {
184: Class entityClass;
185:
186: int qtype = query.getType();
187:
188: if (qtype == 0) { // then not set, so look in sub queries
189:
190: if (query.getLeft() != null) {
191: qtype = query.getLeft().getType();
192: }
193: }
194:
195: if (qtype == WorkflowQuery.CURRENT) {
196: entityClass = HibernateCurrentStep.class;
197: } else {
198: entityClass = HibernateHistoryStep.class;
199: }
200:
201: Criteria criteria = session.createCriteria(entityClass);
202: Criterion expression = buildExpression(query);
203: criteria.setCacheable(isCacheable());
204:
205: if (isCacheable()) {
206: criteria.setCacheRegion(getCacheRegion());
207: }
208:
209: criteria.add(expression);
210:
211: Set results = new HashSet();
212: Iterator iter = criteria.list().iterator();
213:
214: while (iter.hasNext()) {
215: HibernateStep step = (HibernateStep) iter.next();
216: results.add(new Long(step.getEntryId()));
217: }
218:
219: return new ArrayList(results);
220: }
221: });
222: }
223:
224: /*
225: * (non-Javadoc)
226: *
227: * @see com.opensymphony.workflow.spi.WorkflowStore#query(com.opensymphony.workflow.query.WorkflowExpressionQuery)
228: */
229: public List query(final WorkflowExpressionQuery query)
230: throws StoreException {
231: return (List) execute(new InternalCallback() {
232: public Object doInHibernate(Session session)
233: throws HibernateException {
234: com.opensymphony.workflow.query.Expression expression = query
235: .getExpression();
236:
237: Criterion expr;
238:
239: Class entityClass = getQueryClass(expression, null);
240:
241: if (expression.isNested()) {
242: expr = buildNested((NestedExpression) expression);
243: } else {
244: expr = queryComparison((FieldExpression) expression);
245: }
246:
247: Criteria criteria = session.createCriteria(entityClass);
248: criteria.setCacheable(isCacheable());
249:
250: if (isCacheable()) {
251: criteria.setCacheRegion(getCacheRegion());
252: }
253:
254: criteria.add(expr);
255:
256: Set results = new HashSet();
257:
258: Iterator iter = criteria.list().iterator();
259:
260: while (iter.hasNext()) {
261: Object next = iter.next();
262: Object item;
263:
264: if (next instanceof HibernateStep) {
265: HibernateStep step = (HibernateStep) next;
266: item = new Long(step.getEntryId());
267: } else {
268: WorkflowEntry entry = (WorkflowEntry) next;
269: item = new Long(entry.getId());
270: }
271:
272: results.add(item);
273: }
274:
275: return new ArrayList(results);
276: }
277: });
278: }
279:
280: // Companion method of InternalCallback class
281: protected abstract Object execute(InternalCallback action)
282: throws StoreException;
283:
284: protected String getCacheRegion() {
285: return cacheRegion;
286: }
287:
288: protected boolean isCacheable() {
289: return cacheable;
290: }
291:
292: protected Criterion getExpression(final WorkflowQuery query)
293: throws StoreException {
294: return (Criterion) execute(new InternalCallback() {
295: public Object doInHibernate(Session session)
296: throws HibernateException {
297: int operator = query.getOperator();
298:
299: switch (operator) {
300: case WorkflowQuery.EQUALS:
301: return Expression.eq(
302: getFieldName(query.getField()), query
303: .getValue());
304:
305: case WorkflowQuery.NOT_EQUALS:
306: return Expression.not(Expression.like(
307: getFieldName(query.getField()), query
308: .getValue()));
309:
310: case WorkflowQuery.GT:
311: return Expression.gt(
312: getFieldName(query.getField()), query
313: .getValue());
314:
315: case WorkflowQuery.LT:
316: return Expression.lt(
317: getFieldName(query.getField()), query
318: .getValue());
319:
320: default:
321: return Expression.eq(
322: getFieldName(query.getField()), query
323: .getValue());
324: }
325: }
326: });
327: }
328:
329: protected void delete(final Object entry) throws StoreException {
330: execute(new InternalCallback() {
331: public Object doInHibernate(Session session)
332: throws HibernateException {
333: session.delete(entry);
334:
335: return null;
336: }
337: });
338: }
339:
340: // ~ DAO Methods ////////////////////////////////////////////////////////////////
341: protected HibernateWorkflowEntry loadEntry(final long entryId)
342: throws StoreException {
343: return (HibernateWorkflowEntry) execute(new InternalCallback() {
344: public Object doInHibernate(Session session)
345: throws HibernateException {
346: return session.load(HibernateWorkflowEntry.class,
347: new Long(entryId));
348: }
349: });
350: }
351:
352: protected void save(final Object entry) throws StoreException {
353: execute(new InternalCallback() {
354: public Object doInHibernate(Session session)
355: throws HibernateException {
356: session.save(entry);
357:
358: return null;
359: }
360: });
361: }
362:
363: private String getFieldName(int field) {
364: switch (field) {
365: case FieldExpression.ACTION: // actionId
366: return "actionId";
367:
368: case FieldExpression.CALLER:
369: return "caller";
370:
371: case FieldExpression.FINISH_DATE:
372: return "finishDate";
373:
374: case FieldExpression.OWNER:
375: return "owner";
376:
377: case FieldExpression.START_DATE:
378: return "startDate";
379:
380: case FieldExpression.STEP: // stepId
381: return "stepId";
382:
383: case FieldExpression.STATUS:
384: return "status";
385:
386: case FieldExpression.STATE:
387: return "state";
388:
389: case FieldExpression.NAME:
390: return "workflowName";
391:
392: case FieldExpression.DUE_DATE:
393: return "dueDate";
394:
395: default:
396: return "1";
397: }
398: }
399:
400: private Class getQueryClass(
401: com.opensymphony.workflow.query.Expression expr,
402: Collection classesCache) {
403: if (classesCache == null) {
404: classesCache = new HashSet();
405: }
406:
407: if (expr instanceof FieldExpression) {
408: FieldExpression fieldExpression = (FieldExpression) expr;
409:
410: switch (fieldExpression.getContext()) {
411: case FieldExpression.CURRENT_STEPS:
412: classesCache.add(HibernateCurrentStep.class);
413:
414: break;
415:
416: case FieldExpression.HISTORY_STEPS:
417: classesCache.add(HibernateHistoryStep.class);
418:
419: break;
420:
421: case FieldExpression.ENTRY:
422: classesCache.add(HibernateWorkflowEntry.class);
423:
424: break;
425:
426: default:
427: throw new QueryNotSupportedException(
428: "Query for unsupported context "
429: + fieldExpression.getContext());
430: }
431: } else {
432: NestedExpression nestedExpression = (NestedExpression) expr;
433:
434: for (int i = 0; i < nestedExpression.getExpressionCount(); i++) {
435: com.opensymphony.workflow.query.Expression expression = nestedExpression
436: .getExpression(i);
437:
438: if (expression.isNested()) {
439: classesCache.add(getQueryClass(nestedExpression
440: .getExpression(i), classesCache));
441: } else {
442: classesCache.add(getQueryClass(expression,
443: classesCache));
444: }
445: }
446: }
447:
448: if (classesCache.size() > 1) {
449: throw new QueryNotSupportedException(
450: "Store does not support nested queries of different types (types found:"
451: + classesCache + ")");
452: }
453:
454: return (Class) classesCache.iterator().next();
455: }
456:
457: private Criterion buildExpression(WorkflowQuery query)
458: throws StoreException {
459: if (query.getLeft() == null) {
460: if (query.getRight() == null) {
461: return getExpression(query); // leaf node
462: } else {
463: throw new StoreException(
464: "Invalid WorkflowQuery object. QueryLeft is null but QueryRight is not.");
465: }
466: } else {
467: if (query.getRight() == null) {
468: throw new StoreException(
469: "Invalid WorkflowQuery object. QueryLeft is not null but QueryRight is.");
470: }
471:
472: int operator = query.getOperator();
473: WorkflowQuery left = query.getLeft();
474: WorkflowQuery right = query.getRight();
475:
476: switch (operator) {
477: case WorkflowQuery.AND:
478: return Expression.and(buildExpression(left),
479: buildExpression(right));
480:
481: case WorkflowQuery.OR:
482: return Expression.or(buildExpression(left),
483: buildExpression(right));
484:
485: case WorkflowQuery.XOR:
486: throw new QueryNotSupportedException(
487: "XOR Operator in Queries not supported by "
488: + this .getClass().getName());
489:
490: default:
491: throw new QueryNotSupportedException("Operator '"
492: + operator + "' is not supported by "
493: + this .getClass().getName());
494: }
495: }
496: }
497:
498: private Criterion buildNested(NestedExpression nestedExpression) {
499: Criterion full = null;
500:
501: for (int i = 0; i < nestedExpression.getExpressionCount(); i++) {
502: Criterion expr;
503: com.opensymphony.workflow.query.Expression expression = nestedExpression
504: .getExpression(i);
505:
506: if (expression.isNested()) {
507: expr = buildNested((NestedExpression) nestedExpression
508: .getExpression(i));
509: } else {
510: FieldExpression sub = (FieldExpression) nestedExpression
511: .getExpression(i);
512: expr = queryComparison(sub);
513:
514: if (sub.isNegate()) {
515: expr = Expression.not(expr);
516: }
517: }
518:
519: if (full == null) {
520: full = expr;
521: } else {
522: switch (nestedExpression.getExpressionOperator()) {
523: case NestedExpression.AND:
524: full = Expression.and(full, expr);
525:
526: break;
527:
528: case NestedExpression.OR:
529: full = Expression.or(full, expr);
530: }
531: }
532: }
533:
534: return full;
535: }
536:
537: private Criterion queryComparison(FieldExpression expression) {
538: int operator = expression.getOperator();
539:
540: switch (operator) {
541: case FieldExpression.EQUALS:
542: return Expression.eq(getFieldName(expression.getField()),
543: expression.getValue());
544:
545: case FieldExpression.NOT_EQUALS:
546: return Expression.not(Expression.like(
547: getFieldName(expression.getField()), expression
548: .getValue()));
549:
550: case FieldExpression.GT:
551: return Expression.gt(getFieldName(expression.getField()),
552: expression.getValue());
553:
554: case FieldExpression.LT:
555: return Expression.lt(getFieldName(expression.getField()),
556: expression.getValue());
557:
558: default:
559: return Expression.eq(getFieldName(expression.getField()),
560: expression.getValue());
561: }
562: }
563:
564: //~ Inner Interfaces ///////////////////////////////////////////////////////
565:
566: // ~ Internal Interfaces /////////////////////////////////////////////////////
567: // Template method pattern to delegate implementation of Session
568: // management to subclasses
569: protected interface InternalCallback {
570: public Object doInHibernate(Session session)
571: throws HibernateException, StoreException;
572: }
573: }
|