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