001: /*
002: * argun 1.0
003: * Web 2.0 delivery framework
004: * Copyright (C) 2007 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://www.hammurapi.biz
021: * e-Mail: support@hammurapi.biz
022: */
023: package biz.hammurapi.web.interaction;
024:
025: import gnu.trove.TIntObjectHashMap;
026:
027: import java.lang.ref.ReferenceQueue;
028: import java.lang.ref.SoftReference;
029: import java.sql.Connection;
030: import java.sql.ResultSet;
031: import java.sql.SQLException;
032: import java.util.ArrayList;
033: import java.util.Collection;
034: import java.util.Collections;
035: import java.util.Comparator;
036: import java.util.HashMap;
037: import java.util.Iterator;
038: import java.util.List;
039: import java.util.Map;
040:
041: import biz.hammurapi.config.Component;
042: import biz.hammurapi.config.ConfigurationException;
043: import biz.hammurapi.config.Context;
044: import biz.hammurapi.sql.DataAccessObject;
045: import biz.hammurapi.sql.IdentityGenerator;
046: import biz.hammurapi.sql.IdentityManager;
047: import biz.hammurapi.sql.IdentityRetriever;
048: import biz.hammurapi.sql.RowProcessor;
049: import biz.hammurapi.sql.SQLProcessor;
050: import biz.hammurapi.web.HammurapiWebException;
051: import biz.hammurapi.web.interaction.InteractionFactory.InteractionDefinition.StepDefinition;
052: import biz.hammurapi.web.interaction.sql.InteractionEngine;
053: import biz.hammurapi.web.interaction.sql.InteractionImpl;
054: import biz.hammurapi.web.interaction.sql.InteractionStepImpl;
055: import biz.hammurapi.web.interaction.sql.InteractionStepInstanceImpl;
056: import biz.hammurapi.web.interaction.sql.InteractionTransitionImpl;
057: import biz.hammurapi.web.properties.PersistentPropertySet;
058: import biz.hammurapi.web.properties.PropertySet;
059: import biz.hammurapi.web.properties.PropertySetFactory;
060: import bsh.EvalError;
061: import bsh.Interpreter;
062:
063: /**
064: * Creates and keeps cache of interaction instances.
065: * @author Pavel Vlasov
066: */
067: public class InteractionFactory implements DataAccessObject, Component {
068:
069: /**
070: * Interaction definition
071: * @author Pavel Vlasov
072: */
073: public static class InteractionDefinition extends InteractionImpl
074: implements DataAccessObject {
075: private gnu.trove.TIntObjectHashMap steps;
076: private gnu.trove.TIntObjectHashMap transitions;
077:
078: public InteractionDefinition(ResultSet rs) throws SQLException {
079: super (rs);
080: }
081:
082: public void setSQLProcessor(SQLProcessor processor)
083: throws SQLException {
084: // Loads steps and transitions
085: InteractionEngine engine = new InteractionEngine(processor);
086: steps = new TIntObjectHashMap();
087: engine.processInteractionStepByInteraction(getId(),
088: new RowProcessor() {
089:
090: public boolean process(ResultSet rs)
091: throws SQLException {
092: StepDefinition sd = new StepDefinition(rs);
093: steps.put(sd.getId(), sd);
094: return true;
095: }
096:
097: });
098:
099: transitions = new TIntObjectHashMap();
100: engine.processInteractionTransitions(getId(), getId(),
101: new RowProcessor() {
102:
103: public boolean process(ResultSet rs)
104: throws SQLException {
105: TransitionDefinition td = new TransitionDefinition(
106: rs);
107: transitions.put(td.getId(), td);
108:
109: StepDefinition sd = (StepDefinition) steps
110: .get(td.getFromStep());
111: td.setSourceDefinition(sd);
112: sd.addOutput(td);
113:
114: StepDefinition trd = (StepDefinition) steps
115: .get(td.getToStep());
116: td.setTargetDefinition(trd);
117: trd.addInput(td);
118:
119: return true;
120: }
121:
122: });
123:
124: Object[] allSteps = steps.getValues();
125: for (int i = 0; i < allSteps.length; ++i) {
126: ((StepDefinition) allSteps[i]).sortOutputs();
127: }
128: }
129:
130: /**
131: * Executes before code.
132: * @param context evaluation context.
133: * @return If 'false' then interaction instance does not get created.
134: * @throws HammurapiWebException
135: */
136: public boolean before(Map context) throws HammurapiWebException {
137: String code = getBeforeCode();
138: if (code == null || code.trim().length() == 0) {
139: return true;
140: } else {
141: try {
142: Interpreter interpreter = createInterpreter(context);
143: interpreter.set("interaction", this );
144:
145: // TODO - Capture out and err somewhere.
146: return !Boolean.FALSE
147: .equals(interpreter.eval(code));
148: } catch (Exception e) {
149: throw new HammurapiWebException(
150: "Evaluation of interaction " + getName()
151: + " [" + getId()
152: + "] before code failed: " + e, e);
153: }
154: }
155: }
156:
157: /**
158: * Interaction step definition
159: * @author Pavel Vlasov
160: */
161: public class StepDefinition extends InteractionStepImpl {
162:
163: public StepDefinition(boolean force) {
164: super (force);
165: }
166:
167: public StepDefinition(ResultSet rs) throws SQLException {
168: super (rs);
169: }
170:
171: private Collection inputs = new ArrayList();
172:
173: public Collection getInputs() {
174: return inputs;
175: }
176:
177: void addInput(TransitionDefinition td) {
178: inputs.add(td);
179: }
180:
181: private List outputs = new ArrayList();
182:
183: public Collection getOutputs() {
184: return outputs;
185: }
186:
187: void addOutput(TransitionDefinition td) {
188: outputs.add(td);
189: }
190:
191: void sortOutputs() {
192: Collections.sort(outputs, new Comparator() {
193:
194: public int compare(Object arg0, Object arg1) {
195: if (arg0 instanceof TransitionDefinition
196: && arg1 instanceof TransitionDefinition) {
197: return ((TransitionDefinition) arg0)
198: .getTransitionOrder()
199: - ((TransitionDefinition) arg1)
200: .getTransitionOrder();
201: }
202:
203: return arg0.hashCode() - arg1.hashCode();
204: }
205:
206: });
207: }
208:
209: public InteractionDefinition getInteractionDefinition() {
210: return InteractionDefinition.this ;
211: }
212:
213: /**
214: * Executes guard code.
215: * @param context evaluation context.
216: * @return If 'false' then interaction instance does not get created.
217: * @throws HammurapiWebException
218: */
219: public Object guard(Map context)
220: throws HammurapiWebException {
221: try {
222: String code = getGuardCode();
223: if (code == null || code.trim().length() == 0) {
224: return null;
225: } else {
226: Interpreter interpreter = createInterpreter(context);
227: interpreter.set("step", this );
228:
229: // TODO - Capture out and err somewhere
230: return interpreter.eval(code);
231: }
232: } catch (Exception e) {
233: throw new HammurapiWebException(
234: "Evaluation of interaction instance "
235: + getId() + " after code failed: "
236: + e, e);
237: }
238: }
239:
240: }
241:
242: /**
243: * Transition definition
244: * @author Pavel Vlasov
245: */
246: public class TransitionDefinition extends
247: InteractionTransitionImpl {
248:
249: public TransitionDefinition(boolean force) {
250: super (force);
251: }
252:
253: public TransitionDefinition(ResultSet rs)
254: throws SQLException {
255: super (rs);
256: }
257:
258: private StepDefinition sourceDefinition;
259: private StepDefinition targetDefinition;
260:
261: public StepDefinition getSourceDefinition() {
262: return sourceDefinition;
263: }
264:
265: public StepDefinition getTargetDefinition() {
266: return targetDefinition;
267: }
268:
269: void setSourceDefinition(StepDefinition sourceDefinition) {
270: this .sourceDefinition = sourceDefinition;
271: }
272:
273: void setTargetDefinition(StepDefinition targetDefinition) {
274: this .targetDefinition = targetDefinition;
275: }
276:
277: public InteractionDefinition getInteractionDefinition() {
278: return InteractionDefinition.this ;
279: }
280:
281: /**
282: * Executes guard code.
283: * @param context evaluation context.
284: * @return If 'false' then interaction instance does not get created.
285: * @throws HammurapiWebException
286: */
287: public Object condition(Map context)
288: throws HammurapiWebException {
289: try {
290: String code = getConditionCode();
291: if (code == null || code.trim().length() == 0) {
292: return null;
293: } else {
294: Interpreter interpreter = createInterpreter(context);
295: interpreter.set("transition", this );
296:
297: // TODO - Capture out and err somewhere
298: return interpreter.eval(code);
299: }
300: } catch (Exception e) {
301: throw new HammurapiWebException(
302: "Evaluation of interaction instance "
303: + getId() + " after code failed: "
304: + e, e);
305: }
306: }
307:
308: }
309:
310: StepDefinition getStepDefinition(int id) {
311: return (StepDefinition) steps.get(id);
312: }
313:
314: TransitionDefinition getTransitionDefinition(int id) {
315: return (TransitionDefinition) transitions.get(id);
316: }
317:
318: /**
319: * @return Steps which don't have incoming transitions.
320: */
321: public Collection getRootSteps() {
322: List rootSteps = new ArrayList();
323: Object[] allSteps = steps.getValues();
324: for (int i = 0; i < allSteps.length; ++i) {
325: if (((StepDefinition) allSteps[i]).getInputs()
326: .isEmpty()) {
327: rootSteps.add(allSteps[i]);
328: }
329: }
330: Collections.sort(rootSteps, new Comparator() {
331:
332: public int compare(Object arg0, Object arg1) {
333: return ((StepDefinition) arg0).getStepOrder()
334: - ((StepDefinition) arg1).getStepOrder();
335: }
336:
337: });
338: return rootSteps;
339: }
340: }
341:
342: private SQLProcessor processor;
343: private InteractionEngine engine;
344: private IdentityManager identityManager;
345:
346: public void setSQLProcessor(SQLProcessor processor)
347: throws SQLException {
348: this .processor = processor;
349: engine = new InteractionEngine(processor);
350: }
351:
352: private Map instanceMap = new HashMap();
353: private Map definitionMap = new HashMap();
354:
355: private ReferenceQueue instanceReferenceQueue = new ReferenceQueue();
356: private ReferenceQueue definitionReferenceQueue = new ReferenceQueue();
357:
358: private Object owner;
359:
360: private class InteractionReference extends SoftReference {
361:
362: private Integer id;
363:
364: public InteractionReference(InteractionInstance instance) {
365: super (instance, instanceReferenceQueue);
366: this .id = new Integer(instance.getId());
367: }
368:
369: public Integer getId() {
370: return id;
371: }
372: }
373:
374: private class InteractionDefinitionReference extends SoftReference {
375:
376: private Integer id;
377:
378: public InteractionDefinitionReference(
379: InteractionDefinition definition) {
380: super (definition, definitionReferenceQueue);
381: this .id = new Integer(definition.getId());
382: }
383:
384: public Integer getId() {
385: return id;
386: }
387: }
388:
389: private void purgeInstanceReferenceQueue() {
390: for (InteractionReference gced = (InteractionReference) instanceReferenceQueue
391: .poll(); gced != null; gced = (InteractionReference) instanceReferenceQueue
392: .poll()) {
393: instanceMap.remove(gced.getId());
394: }
395: }
396:
397: private void purgeDefinitionReferenceQueue() {
398: for (InteractionDefinitionReference gced = (InteractionDefinitionReference) definitionReferenceQueue
399: .poll(); gced != null; gced = (InteractionDefinitionReference) definitionReferenceQueue
400: .poll()) {
401: instanceMap.remove(gced.getId());
402: }
403: }
404:
405: /**
406: * Gets instance from cache or loads from the database.
407: * @param id
408: * @return InteractionInstance
409: * @throws SQLException
410: */
411: public InteractionDefinition getInteraction(int id)
412: throws SQLException {
413: synchronized (definitionMap) {
414: purgeDefinitionReferenceQueue();
415: InteractionDefinitionReference idr = (InteractionDefinitionReference) definitionMap
416: .get(new Integer(id));
417: if (idr == null || idr.get() == null) {
418: InteractionDefinition iD = (InteractionDefinition) engine
419: .getInteraction(id, InteractionDefinition.class);
420: iD.setInteractionImage(null);
421: idr = new InteractionDefinitionReference(iD);
422: definitionMap.put(idr.getId(), idr);
423: }
424: return (InteractionDefinition) idr.get();
425: }
426: }
427:
428: /**
429: * Gets instance from cache or loads from the database.
430: * @param id
431: * @return InteractionInstance
432: * @throws SQLException
433: */
434: public InteractionInstance getInteractionInstance(int id)
435: throws SQLException {
436: synchronized (instanceMap) {
437: purgeInstanceReferenceQueue();
438: InteractionReference ir = (InteractionReference) instanceMap
439: .get(new Integer(id));
440: if (ir == null || ir.get() == null) {
441: InteractionInstance ii = (InteractionInstance) engine
442: .getInteractionInstance(id,
443: InteractionInstance.class);
444: ii.setFactory(this );
445: ir = new InteractionReference(ii);
446: instanceMap.put(ir.getId(), ir);
447: }
448: InteractionInstance ret = (InteractionInstance) ir.get();
449: ret.invalidate();
450: return ret;
451: }
452: }
453:
454: /**
455: * Creates and returns interaction instance.
456: * @param definitionId ID of interaction definition.
457: * @param exitUrl Url to redirect to after completion of the interaction.
458: * @param context evaluation context
459: * @return New interaction instance or null if before code returned false.
460: * @throws SQLException
461: * @throws HammurapiWebException
462: */
463: public InteractionInstance createInteractionInstance(
464: int definitionId, String exitUrl, Map context, String user)
465: throws SQLException, HammurapiWebException {
466: InteractionDefinition definition = getInteraction(definitionId);
467: if (definition.before(context)) {
468: InteractionInstance ret = new InteractionInstance(true);
469: ret.setSQLProcessor(processor);
470: ret.setInteractionId(definitionId);
471: ret.setExitUrl(exitUrl);
472: ret.setFactory(this );
473: ret.setStatus("Processing");
474: ret.setStartTime(System.currentTimeMillis());
475: ret.setOwner(user);
476:
477: Connection con = processor.getConnection();
478: SQLProcessor lp = new SQLProcessor(con, null);
479: if (identityManager instanceof IdentityGenerator) {
480: ret.setId(((IdentityGenerator) identityManager)
481: .generate(con, "INTERACTION_INSTANCE"));
482: }
483:
484: new InteractionEngine(lp).insertInteractionInstance(ret);
485:
486: if (identityManager instanceof IdentityRetriever) {
487: ret.setId(((IdentityRetriever) identityManager)
488: .retrieve(con));
489: processor.releaseConnection(con);
490: }
491:
492: con.close();
493:
494: Iterator it = definition.getRootSteps().iterator();
495: while (it.hasNext()) {
496: Map guardContext = context == null ? new HashMap()
497: : new HashMap(context);
498: guardContext.put("interactionInstance", ret);
499: guardContext.put("inputs", Collections
500: .unmodifiableCollection(new ArrayList()));
501: StepDefinition sd = (StepDefinition) it.next();
502: Iterator git = InteractionInstance.tokenize(
503: sd.guard(guardContext)).iterator();
504: while (git.hasNext()) {
505: Object token = git.next();
506: InteractionStepInstanceImpl isii = new InteractionStepInstanceImpl(
507: true);
508: isii.setOwnerId(ret.getId());
509: isii.setStatus("pending");
510: isii.setStepId(sd.getId());
511: if (token != null && !(token instanceof Boolean)) {
512: PropertySetFactory psf = getPropertySetFactory();
513: PropertySet stepProperties = psf.create(false);
514: if (stepProperties instanceof PersistentPropertySet) {
515: isii
516: .setPropertySet(new Integer(
517: ((PersistentPropertySet) stepProperties)
518: .getId()));
519: }
520:
521: if (token instanceof PropertySet) {
522: stepProperties.setAll((PropertySet) token);
523: } else {
524: stepProperties.set("token", token);
525: }
526: }
527: new InteractionEngine(processor)
528: .insertInteractionStepInstance(isii);
529: }
530: }
531:
532: ret.invalidate();
533:
534: synchronized (instanceMap) {
535: purgeInstanceReferenceQueue();
536: InteractionReference ir = new InteractionReference(ret);
537: instanceMap.put(ir.getId(), ir);
538: }
539: return ret;
540: } else {
541: return null;
542: }
543: }
544:
545: public void setOwner(Object owner) {
546: this .owner = owner;
547: }
548:
549: private PropertySetFactory propertySetFactory;
550:
551: public PropertySetFactory getPropertySetFactory() {
552: return propertySetFactory;
553: }
554:
555: public void start() throws ConfigurationException {
556: identityManager = (IdentityManager) ((Context) owner)
557: .get("IdentityManager");
558: propertySetFactory = (PropertySetFactory) ((Context) owner)
559: .get("PropertySetFactory");
560: }
561:
562: public void stop() throws ConfigurationException {
563: // Nothing to do
564: }
565:
566: public IdentityManager getIdentityManager() {
567: return identityManager;
568: }
569:
570: public InteractionEngine getEngine() {
571: return engine;
572: }
573:
574: static Interpreter createInterpreter(Map context) throws EvalError {
575: Interpreter interpreter = new Interpreter();
576:
577: // Set external context entries
578: if (context != null) {
579: Iterator it = context.entrySet().iterator();
580: while (it.hasNext()) {
581: Map.Entry entry = (Map.Entry) it.next();
582: interpreter.set(entry.getKey().toString(), entry
583: .getValue());
584: }
585: }
586: return interpreter;
587: }
588:
589: void invalidateInteractions() {
590: definitionMap.clear();
591: }
592:
593: }
|