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: import com.opensymphony.module.propertyset.PropertySetManager;
009: import com.opensymphony.module.propertyset.hibernate.DefaultHibernateConfigurationProvider;
010:
011: import com.opensymphony.util.TextUtils;
012:
013: import com.opensymphony.workflow.QueryNotSupportedException;
014: import com.opensymphony.workflow.StoreException;
015: import com.opensymphony.workflow.query.*;
016: import com.opensymphony.workflow.spi.Step;
017: import com.opensymphony.workflow.spi.WorkflowEntry;
018: import com.opensymphony.workflow.spi.WorkflowStore;
019:
020: import net.sf.hibernate.Criteria;
021: import net.sf.hibernate.Hibernate;
022: import net.sf.hibernate.HibernateException;
023: import net.sf.hibernate.Session;
024: import net.sf.hibernate.SessionFactory;
025: import net.sf.hibernate.Transaction;
026: import net.sf.hibernate.expression.Criterion;
027: import net.sf.hibernate.expression.Expression;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031:
032: import java.util.*;
033:
034: /**
035: * A workflow store backed by Hibernate for persistence. To use this with the standard
036: * persistence factory, pass to the DefaultConfiguration.persistenceArgs the SessionFactory to
037: * use:
038: * <code>DefaultConfiguration.persistenceArgs.put("sessionFactory", DatabaseHelper.getSessionFactory());</code>
039: * See the HibernateFunctionalWorkflowTestCase for more help.
040: *
041: */
042: public class HibernateWorkflowStore implements WorkflowStore {
043: //~ Static fields/initializers /////////////////////////////////////////////
044:
045: private static final Log log = LogFactory
046: .getLog(HibernateWorkflowStore.class);
047:
048: //~ Instance fields ////////////////////////////////////////////////////////
049:
050: Session session;
051: SessionFactory sessionFactory;
052:
053: //~ Constructors ///////////////////////////////////////////////////////////
054:
055: public HibernateWorkflowStore() {
056: }
057:
058: public HibernateWorkflowStore(SessionFactory sessionFactory)
059: throws StoreException {
060: this .sessionFactory = sessionFactory;
061:
062: try {
063: this .session = sessionFactory.openSession();
064: } catch (HibernateException he) {
065: log.error("constructor", he);
066: throw new StoreException("constructor", he);
067: }
068: }
069:
070: //~ Methods ////////////////////////////////////////////////////////////////
071:
072: public void setEntryState(long entryId, int state)
073: throws StoreException {
074: try {
075: HibernateWorkflowEntry entry = (HibernateWorkflowEntry) session
076: .find(
077: "FROM entry IN CLASS "
078: + HibernateWorkflowEntry.class
079: .getName()
080: + " WHERE entry.id = ?",
081: new Long(entryId), Hibernate.LONG).get(0);
082: entry.setState(state);
083: session.save(entry);
084: } catch (HibernateException e) {
085: log.error("An exception occured", e);
086:
087: return;
088: }
089: }
090:
091: public PropertySet getPropertySet(long entryId) {
092: HashMap args = new HashMap();
093: args.put("entityName", "OSWorkflowEntry");
094: args.put("entityId", new Long(entryId));
095:
096: DefaultHibernateConfigurationProvider configurationProvider = new DefaultHibernateConfigurationProvider();
097: configurationProvider.setSessionFactory(sessionFactory);
098:
099: args.put("configurationProvider", configurationProvider);
100:
101: return PropertySetManager.getInstance("hibernate", args);
102: }
103:
104: public Step createCurrentStep(long entryId, int stepId,
105: String owner, Date startDate, Date dueDate, String status,
106: long[] previousIds) throws StoreException {
107: HibernateCurrentStep step = new HibernateCurrentStep();
108: HibernateWorkflowEntry entry;
109:
110: Transaction tx;
111:
112: try {
113: tx = session.beginTransaction();
114: entry = (HibernateWorkflowEntry) session.find(
115: "FROM entry in CLASS "
116: + HibernateWorkflowEntry.class.getName()
117: + " WHERE entry.id = ?", new Long(entryId),
118: Hibernate.LONG).get(0);
119: } catch (HibernateException he) {
120: log.error("Looking for workflow entry " + entryId, he);
121: throw new StoreException("Looking for workflow entry "
122: + entryId, he);
123: }
124:
125: step.setEntry(entry);
126: step.setStepId(stepId);
127: step.setOwner(owner);
128: step.setStartDate(startDate);
129: step.setDueDate(dueDate);
130: step.setStatus(status);
131:
132: List stepIdList = new ArrayList(previousIds.length);
133:
134: for (int i = 0; i < previousIds.length; i++) {
135: long previousId = previousIds[i];
136: stepIdList.add(new Long(previousId));
137: }
138:
139: if (!stepIdList.isEmpty()) {
140: String stepIds = TextUtils.join(", ", stepIdList);
141:
142: try {
143: step
144: .setPreviousSteps(session
145: .find("FROM step in CLASS "
146: + HibernateCurrentStep.class
147: .getName()
148: + " WHERE step.id IN ("
149: + stepIds + ")"));
150: } catch (HibernateException he) {
151: log.error("Looking for step in " + stepIds, he);
152: throw new StoreException("Looking for step in "
153: + stepIds, he);
154: }
155: } else {
156: step.setPreviousSteps(Collections.EMPTY_LIST);
157: }
158:
159: if (entry.getCurrentSteps() == null) {
160: ArrayList cSteps = new ArrayList(1);
161: cSteps.add(step);
162: entry.setCurrentSteps(cSteps);
163: } else {
164: entry.getCurrentSteps().add(step);
165: }
166:
167: try {
168: session.save(entry);
169: tx.commit();
170:
171: //session.save(step);
172: return step;
173: } catch (HibernateException he) {
174: log.error("Saving new workflow entry", he);
175: throw new StoreException("Saving new workflow entry", he);
176: }
177: }
178:
179: public WorkflowEntry createEntry(String workflowName)
180: throws StoreException {
181: HibernateWorkflowEntry entry = new HibernateWorkflowEntry();
182: entry.setState(WorkflowEntry.CREATED);
183: entry.setWorkflowName(workflowName);
184:
185: Transaction tx;
186:
187: try {
188: tx = session.beginTransaction();
189: session.save(entry);
190: tx.commit();
191: } catch (HibernateException he) {
192: log.error("Saving new workflow entry", he);
193: throw new StoreException("Saving new workflow entry", he);
194: }
195:
196: return entry;
197: }
198:
199: public List findCurrentSteps(long entryId) throws StoreException {
200: HibernateWorkflowEntry entry;
201:
202: try {
203: entry = (HibernateWorkflowEntry) session.find(
204: "FROM entry in CLASS "
205: + HibernateWorkflowEntry.class.getName()
206: + " WHERE entry.id = ?", new Long(entryId),
207: Hibernate.LONG).get(0);
208: } catch (HibernateException he) {
209: log.error("Looking for entryId " + entryId, he);
210: throw new StoreException("Looking for entryId " + entryId,
211: he);
212: }
213:
214: try {
215: return session.find("FROM step IN CLASS "
216: + HibernateCurrentStep.class.getName()
217: + " WHERE step.entry = ?", entry, Hibernate
218: .entity(entry.getClass()));
219: } catch (HibernateException he) {
220: log.error("Looking for step id" + entry, he);
221: throw new StoreException("Looking for step id" + entry, he);
222: }
223: }
224:
225: public WorkflowEntry findEntry(long entryId) throws StoreException {
226: try {
227: List result = session.find("FROM entry IN CLASS "
228: + HibernateWorkflowEntry.class.getName()
229: + " WHERE entry.id = ?", new Long(entryId),
230: Hibernate.LONG);
231:
232: return (WorkflowEntry) result.get(0);
233: } catch (HibernateException he) {
234: log.error("Looking for entry " + entryId, he);
235: throw new StoreException("Loooking for entry " + entryId,
236: he);
237: }
238: }
239:
240: public List findHistorySteps(long entryId) throws StoreException {
241: HibernateWorkflowEntry entry;
242:
243: try {
244: entry = (HibernateWorkflowEntry) session.find(
245: "FROM entry in CLASS "
246: + HibernateWorkflowEntry.class.getName()
247: + " WHERE entry.id = ?", new Long(entryId),
248: Hibernate.LONG).get(0);
249: } catch (HibernateException he) {
250: log.error("Finding entry " + entryId, he);
251: throw new StoreException("Finding entry " + entryId, he);
252: }
253:
254: try {
255: return session.find("FROM step IN CLASS "
256: + HibernateHistoryStep.class.getName()
257: + " WHERE step.entry = ?", entry, Hibernate
258: .entity(entry.getClass()));
259: } catch (HibernateException he) {
260: log.error("Looking for step with entry " + entry, he);
261: throw new StoreException("Looking for step with entry "
262: + entry, he);
263: }
264: }
265:
266: public void init(Map props) throws StoreException {
267: try {
268: //if(1==2){
269: sessionFactory = (SessionFactory) props
270: .get("sessionFactory");
271: session = sessionFactory.openSession();
272:
273: //}
274: } catch (HibernateException he) {
275: log.error("Setting sessionFactory", he);
276: throw new StoreException("Setting sessionFactory", he);
277: }
278: }
279:
280: public Step markFinished(Step step, int actionId, Date finishDate,
281: String status, String caller) throws StoreException {
282: HibernateCurrentStep currentStep = (HibernateCurrentStep) step;
283:
284: currentStep.setActionId(actionId);
285: currentStep.setFinishDate(finishDate);
286: currentStep.setStatus(status);
287: currentStep.setCaller(caller);
288:
289: try {
290: Transaction tx = session.beginTransaction();
291: session.save(currentStep);
292: tx.commit();
293:
294: return currentStep;
295: } catch (HibernateException he) {
296: log
297: .error("Saving current step with action "
298: + actionId, he);
299: throw new StoreException("Saving current step with action "
300: + actionId, he);
301: }
302: }
303:
304: public void moveToHistory(Step step) throws StoreException {
305: HibernateWorkflowEntry entry;
306:
307: Transaction tx;
308:
309: try {
310: tx = session.beginTransaction();
311: entry = (HibernateWorkflowEntry) session.find(
312: "FROM entry IN CLASS "
313: + HibernateWorkflowEntry.class.getName()
314: + " WHERE entry.id = ?",
315: new Long(step.getEntryId()), Hibernate.LONG).get(0);
316: } catch (HibernateException he) {
317: log.error(
318: "Looking for workflow entry " + step.getEntryId(),
319: he);
320: throw new StoreException("Looking for workflow entry "
321: + step.getEntryId(), he);
322: }
323:
324: HibernateHistoryStep hStep = new HibernateHistoryStep(
325: (HibernateStep) step);
326:
327: entry.getCurrentSteps().remove(step);
328:
329: if (entry.getHistorySteps() == null) {
330: ArrayList hSteps = new ArrayList(1);
331: hSteps.add(hStep);
332: entry.setHistorySteps(hSteps);
333: } else {
334: entry.getHistorySteps().add(hStep);
335: }
336:
337: try {
338: session.save(hStep);
339: session.save(entry);
340: tx.commit();
341:
342: //session.delete(step);
343: //session.save(hStep, new Long(hStep.getId()));
344: } catch (HibernateException he) {
345: log.error("Saving workflow entry " + entry.getId(), he);
346: throw new StoreException("Saving workflow entry "
347: + entry.getId(), he);
348: }
349: }
350:
351: public List query(WorkflowExpressionQuery query)
352: throws StoreException {
353: com.opensymphony.workflow.query.Expression expression = query
354: .getExpression();
355:
356: Criterion expr;
357:
358: Class entityClass = getQueryClass(expression, null);
359:
360: if (expression.isNested()) {
361: expr = buildNested((NestedExpression) expression);
362: } else {
363: expr = queryComparison((FieldExpression) expression);
364: }
365:
366: //get results and send them back
367: Criteria criteria = session.createCriteria(entityClass);
368: criteria.add(expr);
369:
370: try {
371: Set results = new HashSet();
372:
373: Iterator iter = criteria.list().iterator();
374:
375: while (iter.hasNext()) {
376: Object next = iter.next();
377: Object item;
378:
379: if (next instanceof HibernateStep) {
380: HibernateStep step = (HibernateStep) next;
381: item = new Long(step.getEntryId());
382: } else {
383: WorkflowEntry entry = (WorkflowEntry) next;
384: item = new Long(entry.getId());
385: }
386:
387: results.add(item);
388: }
389:
390: return new ArrayList(results);
391: } catch (HibernateException e) {
392: throw new StoreException("Error executing query "
393: + expression, e);
394: }
395: }
396:
397: public List query(WorkflowQuery query) throws StoreException {
398: Class entityClass;
399:
400: int qtype = query.getType();
401:
402: if (qtype == 0) { // then not set, so look in sub queries
403:
404: if (query.getLeft() != null) {
405: qtype = query.getLeft().getType();
406: }
407: }
408:
409: if (qtype == WorkflowQuery.CURRENT) {
410: entityClass = HibernateCurrentStep.class;
411: } else {
412: entityClass = HibernateHistoryStep.class;
413: }
414:
415: Criteria criteria = session.createCriteria(entityClass);
416: Criterion expression = buildExpression(query);
417: criteria.add(expression);
418:
419: //get results and send them back
420: try {
421: Set results = new HashSet();
422: Iterator iter = criteria.list().iterator();
423:
424: while (iter.hasNext()) {
425: HibernateStep step = (HibernateStep) iter.next();
426: results.add(new Long(step.getEntryId()));
427: }
428:
429: return new ArrayList(results);
430: } catch (HibernateException e) {
431: throw new StoreException("Error executing query "
432: + expression, e);
433: }
434: }
435:
436: /**
437: * Returns an expression generated from this query
438: */
439: private Criterion getExpression(WorkflowQuery query) {
440: int operator = query.getOperator();
441:
442: switch (operator) {
443: case WorkflowQuery.EQUALS:
444: return Expression.eq(getFieldName(query.getField()), query
445: .getValue());
446:
447: case WorkflowQuery.NOT_EQUALS:
448: return Expression.not(Expression.like(getFieldName(query
449: .getField()), query.getValue()));
450:
451: case WorkflowQuery.GT:
452: return Expression.gt(getFieldName(query.getField()), query
453: .getValue());
454:
455: case WorkflowQuery.LT:
456: return Expression.lt(getFieldName(query.getField()), query
457: .getValue());
458:
459: default:
460: return Expression.eq(getFieldName(query.getField()), query
461: .getValue());
462: }
463: }
464:
465: /**
466: * returns the correct name of the field given or "1" if none is found
467: * which matches the input.
468: * @param field
469: * @return
470: */
471: private String getFieldName(int field) {
472: switch (field) {
473: case FieldExpression.ACTION: // actionId
474: return "actionId";
475:
476: case FieldExpression.CALLER:
477: return "caller";
478:
479: case FieldExpression.FINISH_DATE:
480: return "finishDate";
481:
482: case FieldExpression.OWNER:
483: return "owner";
484:
485: case FieldExpression.START_DATE:
486: return "startDate";
487:
488: case FieldExpression.STEP: // stepId
489: return "stepId";
490:
491: case FieldExpression.STATUS:
492: return "status";
493:
494: case FieldExpression.STATE:
495: return "state";
496:
497: case FieldExpression.NAME:
498: return "workflowName";
499:
500: case FieldExpression.DUE_DATE:
501: return "dueDate";
502:
503: default:
504: return "1";
505: }
506: }
507:
508: private Class getQueryClass(
509: com.opensymphony.workflow.query.Expression expr,
510: Collection classesCache) throws StoreException {
511: if (classesCache == null) {
512: classesCache = new HashSet();
513: }
514:
515: if (expr instanceof FieldExpression) {
516: FieldExpression fieldExpression = (FieldExpression) expr;
517:
518: switch (fieldExpression.getContext()) {
519: case FieldExpression.CURRENT_STEPS:
520: classesCache.add(HibernateCurrentStep.class);
521:
522: break;
523:
524: case FieldExpression.HISTORY_STEPS:
525: classesCache.add(HibernateHistoryStep.class);
526:
527: break;
528:
529: case FieldExpression.ENTRY:
530: classesCache.add(HibernateWorkflowEntry.class);
531:
532: break;
533:
534: default:
535: throw new QueryNotSupportedException(
536: "Query for unsupported context "
537: + fieldExpression.getContext());
538: }
539: } else {
540: NestedExpression nestedExpression = (NestedExpression) expr;
541:
542: for (int i = 0; i < nestedExpression.getExpressionCount(); i++) {
543: com.opensymphony.workflow.query.Expression expression = nestedExpression
544: .getExpression(i);
545:
546: if (expression.isNested()) {
547: classesCache.add(getQueryClass(nestedExpression
548: .getExpression(i), classesCache));
549: } else {
550: classesCache.add(getQueryClass(expression,
551: classesCache));
552: }
553: }
554: }
555:
556: if (classesCache.size() > 1) {
557: throw new QueryNotSupportedException(
558: "Store does not support nested queries of different types (types found:"
559: + classesCache + ")");
560: }
561:
562: return (Class) classesCache.iterator().next();
563: }
564:
565: /**
566: * Recursive method for building Expressions using Query objects.
567: */
568: private Criterion buildExpression(WorkflowQuery query)
569: throws StoreException {
570: if (query.getLeft() == null) {
571: if (query.getRight() == null) {
572: return getExpression(query); //leaf node
573: } else {
574: throw new StoreException(
575: "Invalid WorkflowQuery object. QueryLeft is null but QueryRight is not.");
576: }
577: } else {
578: if (query.getRight() == null) {
579: throw new StoreException(
580: "Invalid WorkflowQuery object. QueryLeft is not null but QueryRight is.");
581: }
582:
583: int operator = query.getOperator();
584: WorkflowQuery left = query.getLeft();
585: WorkflowQuery right = query.getRight();
586:
587: switch (operator) {
588: case WorkflowQuery.AND:
589: return Expression.and(buildExpression(left),
590: buildExpression(right));
591:
592: case WorkflowQuery.OR:
593: return Expression.or(buildExpression(left),
594: buildExpression(right));
595:
596: case WorkflowQuery.XOR:
597: throw new StoreException(
598: "XOR Operator in Queries not supported by "
599: + this .getClass().getName());
600:
601: default:
602: throw new StoreException("Operator '" + operator
603: + "' is not supported by "
604: + this .getClass().getName());
605: }
606: }
607: }
608:
609: private Criterion buildNested(NestedExpression nestedExpression)
610: throws StoreException {
611: Criterion full = null;
612:
613: for (int i = 0; i < nestedExpression.getExpressionCount(); i++) {
614: Criterion expr;
615: com.opensymphony.workflow.query.Expression expression = nestedExpression
616: .getExpression(i);
617:
618: if (expression.isNested()) {
619: expr = buildNested((NestedExpression) nestedExpression
620: .getExpression(i));
621: } else {
622: FieldExpression sub = (FieldExpression) nestedExpression
623: .getExpression(i);
624: expr = queryComparison(sub);
625:
626: if (sub.isNegate()) {
627: expr = Expression.not(expr);
628: }
629: }
630:
631: if (full == null) {
632: full = expr;
633: } else {
634: switch (nestedExpression.getExpressionOperator()) {
635: case NestedExpression.AND:
636: full = Expression.and(full, expr);
637:
638: break;
639:
640: case NestedExpression.OR:
641: full = Expression.or(full, expr);
642: }
643: }
644: }
645:
646: return full;
647: }
648:
649: private Criterion queryComparison(FieldExpression expression) {
650: int operator = expression.getOperator();
651:
652: switch (operator) {
653: case FieldExpression.EQUALS:
654: return Expression.eq(getFieldName(expression.getField()),
655: expression.getValue());
656:
657: case FieldExpression.NOT_EQUALS:
658: return Expression.not(Expression.like(
659: getFieldName(expression.getField()), expression
660: .getValue()));
661:
662: case FieldExpression.GT:
663: return Expression.gt(getFieldName(expression.getField()),
664: expression.getValue());
665:
666: case FieldExpression.LT:
667: return Expression.lt(getFieldName(expression.getField()),
668: expression.getValue());
669:
670: default:
671: return Expression.eq(getFieldName(expression.getField()),
672: expression.getValue());
673: }
674: }
675: }
|