0001: /*
0002: * argun 1.0
0003: * Web 2.0 delivery framework
0004: * Copyright (C) 2007 Hammurapi Group
0005: *
0006: * This program is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU Lesser General Public
0008: * License as published by the Free Software Foundation; either
0009: * version 2 of the License, or (at your option) any later version.
0010: *
0011: * This program is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * Lesser General Public License for more details.
0015: *
0016: * You should have received a copy of the GNU Lesser General Public
0017: * License along with this library; if not, write to the Free Software
0018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0019: *
0020: * URL: http://www.hammurapi.biz
0021: * e-Mail: support@hammurapi.biz
0022: */
0023: package biz.hammurapi.web.interaction;
0024:
0025: import gnu.trove.TIntObjectHashMap;
0026: import gnu.trove.TObjectProcedure;
0027:
0028: import java.sql.Connection;
0029: import java.sql.ResultSet;
0030: import java.sql.SQLException;
0031: import java.util.ArrayList;
0032: import java.util.Collection;
0033: import java.util.Collections;
0034: import java.util.Comparator;
0035: import java.util.Enumeration;
0036: import java.util.HashMap;
0037: import java.util.HashSet;
0038: import java.util.Iterator;
0039: import java.util.List;
0040: import java.util.Map;
0041: import java.util.Set;
0042: import java.util.StringTokenizer;
0043:
0044: import org.w3c.dom.Element;
0045:
0046: import biz.hammurapi.config.Command;
0047: import biz.hammurapi.sql.DataAccessObject;
0048: import biz.hammurapi.sql.IdentityGenerator;
0049: import biz.hammurapi.sql.IdentityManager;
0050: import biz.hammurapi.sql.IdentityRetriever;
0051: import biz.hammurapi.sql.RowProcessor;
0052: import biz.hammurapi.sql.SQLProcessor;
0053: import biz.hammurapi.util.CollectionVisitable;
0054: import biz.hammurapi.util.Visitable;
0055: import biz.hammurapi.util.Visitor;
0056: import biz.hammurapi.web.HammurapiWebException;
0057: import biz.hammurapi.web.HammurapiWebRuntimeException;
0058: import biz.hammurapi.web.interaction.InteractionFactory.InteractionDefinition;
0059: import biz.hammurapi.web.interaction.InteractionFactory.InteractionDefinition.StepDefinition;
0060: import biz.hammurapi.web.interaction.InteractionFactory.InteractionDefinition.TransitionDefinition;
0061: import biz.hammurapi.web.interaction.sql.InteractionEngine;
0062: import biz.hammurapi.web.interaction.sql.InteractionInstanceImpl;
0063: import biz.hammurapi.web.interaction.sql.InteractionStepInstance;
0064: import biz.hammurapi.web.interaction.sql.InteractionStepInstanceImpl;
0065: import biz.hammurapi.web.interaction.sql.InteractionTransitionInstance;
0066: import biz.hammurapi.web.interaction.sql.InteractionTransitionInstanceImpl;
0067: import biz.hammurapi.web.properties.PersistentPropertySet;
0068: import biz.hammurapi.web.properties.PropertySet;
0069: import biz.hammurapi.web.properties.PropertySetFactory;
0070: import bsh.Interpreter;
0071:
0072: /**
0073: * Represents interaction instance.
0074: * @author Pavel Vlasov
0075: */
0076: public class InteractionInstance extends InteractionInstanceImpl
0077: implements DataAccessObject, Visitable {
0078:
0079: private static final String REWOUND = "rewound";
0080:
0081: private class PropertyValue {
0082: int affinity;
0083: Object value;
0084: }
0085:
0086: /**
0087: * Indicates that steps and transitions information shall be reloaded.
0088: */
0089: public void invalidate() {
0090: steps = null;
0091: transitions = null;
0092: }
0093:
0094: private gnu.trove.TIntObjectHashMap steps;
0095: private gnu.trove.TIntObjectHashMap transitions;
0096:
0097: /**
0098: * Step instance
0099: * @author Pavel Vlasov
0100: */
0101: public class StepInstance extends InteractionStepInstanceImpl
0102: implements Visitable, Comparable {
0103:
0104: public StepInstance(boolean force) {
0105: super (force);
0106: }
0107:
0108: public StepInstance(ResultSet rs) throws SQLException {
0109: super (rs);
0110: }
0111:
0112: public InteractionDefinition.StepDefinition getDefinition()
0113: throws SQLException {
0114: return InteractionInstance.this .getDefinition()
0115: .getStepDefinition(getStepId());
0116: }
0117:
0118: /**
0119: * Rewinds this step instance and its successors. All
0120: * successive steps change status to "rewound".
0121: * @throws SQLException
0122: */
0123: public void rewind() throws SQLException {
0124: if (!REWOUND.equals(getStatus())) {
0125: setStatus(REWOUND);
0126: update();
0127: Iterator it = getSuccessors().iterator();
0128: while (it.hasNext()) {
0129: ((StepInstance) it.next()).rewind();
0130: }
0131: }
0132: }
0133:
0134: private List inputs = new ArrayList();
0135:
0136: void addInput(TransitionInstance input) {
0137: inputs.add(input);
0138: }
0139:
0140: /**
0141: * @return Collection of input transition instances.
0142: */
0143: public Collection getInputTransitions() {
0144: return inputs;
0145: }
0146:
0147: /**
0148: * @return TransactionInstance if this step has single input, null otherwise.
0149: */
0150: public TransitionInstance getSingleInputTransition() {
0151: return (TransitionInstance) (inputs.size() == 1 ? inputs
0152: .get(0) : null);
0153: }
0154:
0155: private Collection outputs = new ArrayList();
0156:
0157: void addOutput(TransitionInstance output) {
0158: outputs.add(output);
0159: }
0160:
0161: /**
0162: * @return Collection of output transition instances
0163: */
0164: public Collection getOutputTransitions() {
0165: return outputs;
0166: }
0167:
0168: private Collection predecessors = new ArrayList();
0169:
0170: void addPredecessor(StepInstance predecessor) {
0171: predecessors.add(predecessor);
0172: }
0173:
0174: /**
0175: * @return Step instances which transitioned to this step.
0176: */
0177: public Collection getPredecessors() {
0178: return predecessors;
0179: }
0180:
0181: /**
0182: * Traverses predecessors and returns the first found with given definition name.
0183: * @param name
0184: * @return
0185: * @throws SQLException
0186: */
0187: public StepInstance getPredecessor(String name)
0188: throws SQLException {
0189: return getPredecessor(name, new HashSet());
0190: }
0191:
0192: private StepInstance getPredecessor(String name, Set stepSet)
0193: throws SQLException {
0194: if (name.equals(getDefinition().getName())) {
0195: return this ;
0196: }
0197:
0198: if (stepSet.add(new Integer(getId()))) {
0199: Iterator pit = predecessors.iterator();
0200: while (pit.hasNext()) {
0201: StepInstance predecessor = (StepInstance) pit
0202: .next();
0203: StepInstance ret = predecessor.getPredecessor(name);
0204: if (ret != null) {
0205: return ret;
0206: }
0207: }
0208: }
0209:
0210: return null;
0211: }
0212:
0213: private Collection successors = new ArrayList();
0214:
0215: /**
0216: * @return Step instances which step transitions to.
0217: */
0218: public Collection getSuccessors() {
0219: return successors;
0220: }
0221:
0222: void addSuccessor(StepInstance successor) {
0223: successors.add(successor);
0224: }
0225:
0226: /**
0227: * Executes action code.
0228: * @param context evaluation context.
0229: * @return If 'false' then interaction instance does not get created.
0230: * @throws HammurapiWebException
0231: */
0232: public DynaBind action(Map context)
0233: throws HammurapiWebException {
0234: try {
0235: String code = getDefinition().getActionCode();
0236: if (code != null && code.trim().length() != 0) {
0237: Interpreter interpreter = InteractionFactory
0238: .createInterpreter(context);
0239: interpreter.set("stepInstance", this );
0240: interpreter.set("SKIP_BIND", DynaBind.SKIP_BIND);
0241:
0242: // TODO - Capture out and err somewhere.
0243: Object ret = interpreter.eval(code);
0244: if (ret instanceof DynaBind) {
0245: return (DynaBind) ret;
0246: }
0247: }
0248: return null;
0249: } catch (Exception e) {
0250: throw new HammurapiWebException(
0251: "Evaluation of interaction step " + getId()
0252: + " action code failed: " + e, e);
0253: }
0254: }
0255:
0256: private PropertySet properties;
0257:
0258: /**
0259: * Creates property set or retrieves it from the database.
0260: * @throws SQLException
0261: */
0262: public synchronized PropertySet getProperties()
0263: throws SQLException {
0264: if (properties == null) {
0265: PropertySetFactory propertySetFactory = getFactory()
0266: .getPropertySetFactory();
0267: if (getPropertySet() == null) {
0268: properties = propertySetFactory.create(false);
0269: if (properties instanceof PersistentPropertySet) {
0270: setPropertySet(new Integer(
0271: ((PersistentPropertySet) properties)
0272: .getId()));
0273: update();
0274: }
0275: } else {
0276: properties = propertySetFactory
0277: .find(getPropertySet().intValue());
0278: }
0279: }
0280: return properties;
0281: }
0282:
0283: /**
0284: * Convenience method to retrieve interaction instance properties.
0285: * @param name
0286: * @param cascade - If true then this method cascades to
0287: * @return
0288: * @throws SQLException
0289: */
0290: public Object getProperty(String name) {
0291: return getProperty(name, false);
0292: }
0293:
0294: /**
0295: * Convenience method to retrieve interaction instance properties.
0296: * @param name
0297: * @param cascade - If true then this method cascades to
0298: * @return
0299: * @throws SQLException
0300: */
0301: public Object getProperty(String name, boolean cascade) {
0302: try {
0303: if (cascade) {
0304: PropertyValue pv = getCascadeProperty(name);
0305: return pv == null ? null : pv.value;
0306: }
0307: return getPropertySet() == null ? null
0308: : getProperties().get(name);
0309: } catch (SQLException e) {
0310: throw new HammurapiWebRuntimeException(e);
0311: }
0312: }
0313:
0314: PropertyValue getCascadeProperty(String name)
0315: throws SQLException {
0316: Object value = getPropertySet() == null ? null
0317: : getProperties().get(name);
0318: if (value != null) {
0319: PropertyValue ret = new PropertyValue();
0320: ret.value = value;
0321: return ret;
0322: }
0323:
0324: if (inputs.isEmpty()) {
0325: PropertyValue ret = new PropertyValue();
0326: ret.value = InteractionInstance.this .getProperty(name);
0327: if (ret.value != null) {
0328: ++ret.affinity;
0329: return ret;
0330: }
0331: return null;
0332: } else {
0333: Iterator it = inputs.iterator();
0334: PropertyValue candidate = null;
0335: while (it.hasNext()) {
0336: TransitionInstance input = (TransitionInstance) it
0337: .next();
0338: PropertyValue ret = input.getCascadeProperty(name);
0339: if (ret != null) {
0340: ++ret.affinity;
0341: if (candidate == null
0342: || candidate.affinity > ret.affinity) {
0343: candidate = ret;
0344: }
0345: }
0346: }
0347: return candidate;
0348: }
0349: }
0350:
0351: public void toDom(Element holder) {
0352: try {
0353: if (getPropertySet() != null) {
0354: setAttribute("properties", getProperties());
0355: }
0356: setAttribute("definition", getDefinition());
0357: } catch (SQLException e) {
0358: throw new HammurapiWebRuntimeException(e);
0359: }
0360:
0361: super .toDom(holder);
0362: }
0363:
0364: /**
0365: * Updates step information in the database
0366: */
0367: public void update() throws SQLException {
0368: setOwnerId(InteractionInstance.this .getId());
0369: update(processor, "INTERACTION_STEP_INSTANCE");
0370: }
0371:
0372: private List children = new ArrayList();
0373:
0374: void addChild(StepInstance child) {
0375: children.add(child);
0376: }
0377:
0378: void sortChildren() {
0379: Collections.sort(children);
0380: }
0381:
0382: public boolean accept(Visitor visitor) {
0383: if (visitor.visit(this )) {
0384: new CollectionVisitable(children, false)
0385: .accept(visitor);
0386: return true;
0387: }
0388:
0389: return false;
0390: }
0391:
0392: public int compareTo(Object arg) {
0393: if (arg == this ) {
0394: return 0;
0395: }
0396: if (arg == null) {
0397: return -1;
0398: }
0399: if (arg instanceof StepInstance) {
0400: return getId() - ((StepInstance) arg).getId();
0401: }
0402:
0403: return hashCode() - arg.hashCode();
0404: }
0405:
0406: /**
0407: * @return Owner interaction instance
0408: */
0409: public InteractionInstance getOwner() {
0410: return InteractionInstance.this ;
0411: }
0412: }
0413:
0414: public class TransitionInstance extends
0415: InteractionTransitionInstanceImpl {
0416:
0417: public TransitionInstance(boolean force) {
0418: super (force);
0419: }
0420:
0421: public TransitionInstance(ResultSet rs) throws SQLException {
0422: super (rs);
0423: }
0424:
0425: public InteractionDefinition.TransitionDefinition getDefinition()
0426: throws SQLException {
0427: return InteractionInstance.this .getDefinition()
0428: .getTransitionDefinition(getTransitionId());
0429: }
0430:
0431: public StepInstance getSource() {
0432: return (StepInstance) getSteps().get(getSourceId());
0433: }
0434:
0435: /**
0436: * Executes action code.
0437: * @param context evaluation context.
0438: * @return If 'false' then interaction instance does not get created.
0439: * @throws HammurapiWebException
0440: */
0441: public void action(Map context) throws HammurapiWebException {
0442: try {
0443: String code = getDefinition().getActionCode();
0444: if (code != null && code.trim().length() != 0) {
0445: Interpreter interpreter = InteractionFactory
0446: .createInterpreter(context);
0447: interpreter.set("transitionInstance", this );
0448:
0449: // TODO - Capture out and err somewhere.
0450: interpreter.eval(code);
0451: }
0452: } catch (Exception e) {
0453: throw new HammurapiWebException(
0454: "Evaluation of interaction transition "
0455: + getId() + " action code failed: " + e,
0456: e);
0457: }
0458: }
0459:
0460: private PropertySet properties;
0461:
0462: /**
0463: * Creates property set or retrieves it from the database.
0464: * @throws SQLException
0465: */
0466: public synchronized PropertySet getProperties()
0467: throws SQLException {
0468: if (properties == null) {
0469: PropertySetFactory propertySetFactory = getFactory()
0470: .getPropertySetFactory();
0471: if (getPropertySet() == null) {
0472: properties = propertySetFactory.create(false);
0473: if (properties instanceof PersistentPropertySet) {
0474: setPropertySet(new Integer(
0475: ((PersistentPropertySet) properties)
0476: .getId()));
0477: update();
0478: }
0479: } else {
0480: properties = propertySetFactory
0481: .find(getPropertySet().intValue());
0482: }
0483: }
0484: return properties;
0485: }
0486:
0487: /**
0488: * Convenience method to retrieve interaction instance properties.
0489: * @param name
0490: * @param cascade - If true then this method cascades to
0491: * @return
0492: * @throws SQLException
0493: */
0494: public Object getProperty(String name) {
0495: return getProperty(name, false);
0496: }
0497:
0498: /**
0499: * Convenience method to retrieve interaction instance properties.
0500: * @param name
0501: * @param cascade - If true then this method cascades to
0502: * @return
0503: * @throws SQLException
0504: */
0505: public Object getProperty(String name, boolean cascade) {
0506: try {
0507: if (cascade) {
0508: PropertyValue pv = getCascadeProperty(name);
0509: return pv == null ? null : pv.value;
0510: }
0511: return getPropertySet() == null ? null
0512: : getProperties().get(name);
0513: } catch (SQLException e) {
0514: throw new HammurapiWebRuntimeException(e);
0515: }
0516: }
0517:
0518: PropertyValue getCascadeProperty(String name)
0519: throws SQLException {
0520: Object value = getPropertySet() == null ? null
0521: : getProperties().get(name);
0522: if (value != null) {
0523: PropertyValue ret = new PropertyValue();
0524: ret.value = value;
0525: return ret;
0526: }
0527:
0528: PropertyValue ret = getSource().getCascadeProperty(name);
0529: if (ret != null) {
0530: ++ret.affinity;
0531: }
0532: return ret;
0533: }
0534:
0535: public void toDom(Element holder) {
0536: try {
0537: if (getPropertySet() != null) {
0538: setAttribute("properties", getProperties());
0539: }
0540: setAttribute("definition", getDefinition());
0541: } catch (SQLException e) {
0542: throw new HammurapiWebRuntimeException(e);
0543: }
0544: super .toDom(holder);
0545: }
0546:
0547: /**
0548: * Updates step information in the database
0549: */
0550: public void update() throws SQLException {
0551: update(processor, "INTERACTION_TRANSITION_INSTANCE");
0552: }
0553:
0554: /**
0555: * @return Owner interaction instance
0556: */
0557: public InteractionInstance getOwner() {
0558: return InteractionInstance.this ;
0559: }
0560: }
0561:
0562: private SQLProcessor processor;
0563:
0564: /**
0565: * Default constructor
0566: *
0567: */
0568: public InteractionInstance() {
0569: super ();
0570: }
0571:
0572: public InteractionInstance(boolean force) {
0573: super (force);
0574: }
0575:
0576: public InteractionInstance(ResultSet rs) throws SQLException {
0577: super (rs);
0578: }
0579:
0580: public InteractionFactory.InteractionDefinition getDefinition()
0581: throws SQLException {
0582: return factory.getInteraction(getInteractionId());
0583: }
0584:
0585: /**
0586: * Loads child objects
0587: */
0588: public void setSQLProcessor(SQLProcessor processor)
0589: throws SQLException {
0590: this .processor = processor;
0591: }
0592:
0593: private int seqId;
0594:
0595: /**
0596: * @return Next sequential step id.
0597: */
0598: public int getNextSeqId() {
0599: return ++seqId;
0600: }
0601:
0602: /**
0603: * Returns first step in given status
0604: * @param status
0605: * @return
0606: */
0607: public StepInstance getStepByStatus(final String status) {
0608: return getStep(new String[] { status });
0609: }
0610:
0611: /**
0612: * Returns single step with given step definition name.
0613: * @param defName
0614: * @return
0615: * @throws SQLException
0616: */
0617: public StepInstance getStepByDefinitionName(String defName)
0618: throws SQLException {
0619: Object[] allSteps = getSteps().getValues();
0620: for (int i = 0; i < allSteps.length; ++i) {
0621: StepInstance step = (StepInstance) allSteps[i];
0622: if (defName.equals(step.getDefinition().getName())) {
0623: return step;
0624: }
0625: }
0626: return null;
0627: }
0628:
0629: /**
0630: * Returns steps with given step definition name and status.
0631: * This method can be used in guards of transitions which shall navigate from multiple instances of one step to a single instance
0632: * of another.
0633: * @param defName
0634: * @return
0635: * @throws SQLException
0636: */
0637: public Collection getStepsByDefinitionNameAndStatus(String defName,
0638: String status) throws SQLException {
0639: Collection ret = new ArrayList();
0640: Object[] allSteps = getSteps().getValues();
0641: for (int i = 0; i < allSteps.length; ++i) {
0642: StepInstance step = (StepInstance) allSteps[i];
0643: if (defName.equals(step.getDefinition().getName())
0644: && status.equals(step.getStatus())) {
0645: ret.add(step);
0646: }
0647: }
0648: return ret;
0649: }
0650:
0651: /**
0652: * Returns single step with given step definition name.
0653: * @param defName
0654: * @return
0655: * @throws SQLException
0656: */
0657: public Collection getStepsByDefinitionName(String defName)
0658: throws SQLException {
0659: Collection ret = new ArrayList();
0660: Object[] allSteps = getSteps().getValues();
0661: for (int i = 0; i < allSteps.length; ++i) {
0662: StepInstance step = (StepInstance) allSteps[i];
0663: if (defName.equals(step.getDefinition().getName())) {
0664: ret.add(step);
0665: }
0666: }
0667: return ret;
0668: }
0669:
0670: /**
0671: * Traverses steps tree. The tree is constructed from root steps with steps activated by the root steps as
0672: * children of respective steps.
0673: * @param status Array
0674: * @return First available step with status equal to one of elements in the status array.
0675: */
0676: public StepInstance getStep(final String[] status) {
0677: final StepInstance[] ret = { null };
0678: accept(new Visitor() {
0679:
0680: public boolean visit(Object obj) {
0681: if (ret[0] == null) {
0682: if (obj instanceof StepInstance) {
0683: StepInstance si = (StepInstance) obj;
0684: for (int i = 0; i < status.length; ++i) {
0685: if (status[i].equals((si).getStatus())) {
0686: ret[0] = si;
0687: return false;
0688: }
0689: }
0690: }
0691: return true;
0692: } else {
0693: return false;
0694: }
0695: }
0696:
0697: });
0698: return ret[0];
0699: }
0700:
0701: private void loadStepsAndTransitions() throws SQLException {
0702: if (transitions == null || steps == null) {
0703: transitions = new TIntObjectHashMap();
0704: steps = new TIntObjectHashMap();
0705:
0706: InteractionEngine engine = new InteractionEngine(
0707: this .processor);
0708:
0709: engine.processInteractionTransitionInstances(getId(),
0710: new RowProcessor() {
0711:
0712: public boolean process(ResultSet rs)
0713: throws SQLException {
0714: TransitionInstance ti = new TransitionInstance(
0715: rs);
0716: transitions.put(ti.getId(), ti);
0717: return true;
0718: }
0719:
0720: });
0721:
0722: engine.processInteractionStepInstanceByOwner(getId(),
0723: new RowProcessor() {
0724:
0725: public boolean process(ResultSet rs)
0726: throws SQLException {
0727: StepInstance si = new StepInstance(rs);
0728: steps.put(si.getId(), si);
0729: seqId = Math.max(seqId, si.getSeqId());
0730:
0731: String inputs = si.getInputs();
0732: if (inputs != null
0733: && inputs.trim().length() > 0) {
0734: StringTokenizer st = new StringTokenizer(
0735: inputs, ", ");
0736: while (st.hasMoreTokens()) {
0737: int tid = Integer.parseInt(st
0738: .nextToken().trim());
0739: TransitionInstance input = getTransition(tid);
0740: si.addInput(input);
0741: }
0742: }
0743:
0744: return true;
0745: }
0746:
0747: });
0748:
0749: Object[] allSteps = steps.getValues();
0750: for (int i = 0; i < allSteps.length; ++i) {
0751: StepInstance si = (StepInstance) allSteps[i];
0752: if (si.getActivatorId() != null) {
0753: ((StepInstance) steps.get(si.getActivatorId()
0754: .intValue())).addChild(si);
0755: }
0756:
0757: Iterator iit = si.getInputTransitions().iterator();
0758: while (iit.hasNext()) {
0759: StepInstance predecessor = ((TransitionInstance) iit
0760: .next()).getSource();
0761: predecessor.addSuccessor(si);
0762: si.addPredecessor(predecessor);
0763: }
0764: }
0765:
0766: Object[] allTransitions = transitions.getValues();
0767: for (int i = 0; i < allTransitions.length; ++i) {
0768: TransitionInstance ti = (TransitionInstance) allTransitions[i];
0769: ti.getSource().addOutput(ti);
0770: }
0771:
0772: for (int i = 0; i < allSteps.length; ++i) {
0773: ((StepInstance) allSteps[i]).sortChildren();
0774: }
0775: }
0776: }
0777:
0778: private InteractionFactory factory;
0779:
0780: public InteractionFactory getFactory() {
0781: return factory;
0782: }
0783:
0784: public void setFactory(InteractionFactory factory) {
0785: this .factory = factory;
0786: }
0787:
0788: /**
0789: * Updates step information in the database
0790: */
0791: public void update() throws SQLException {
0792: update(processor, "INTERACTION_INSTANCE");
0793: if (steps != null) {
0794: steps.forEachValue(new TObjectProcedure() {
0795:
0796: public boolean execute(Object value) {
0797: StepInstance si = (StepInstance) value;
0798: if (si.isModified()) {
0799: try {
0800: si.update();
0801: } catch (SQLException e) {
0802: throw new HammurapiWebRuntimeException(
0803: "Failed to update step instance: "
0804: + e, e);
0805: }
0806: }
0807: return true;
0808: }
0809:
0810: });
0811: }
0812:
0813: if (transitions != null) {
0814: transitions.forEachValue(new TObjectProcedure() {
0815:
0816: public boolean execute(Object value) {
0817: TransitionInstance ti = (TransitionInstance) value;
0818: if (ti.isModified()) {
0819: try {
0820: ti.update();
0821: } catch (SQLException e) {
0822: throw new HammurapiWebRuntimeException(
0823: "Failed to update transition instance: "
0824: + e, e);
0825: }
0826: }
0827: return true;
0828: }
0829:
0830: });
0831: }
0832: }
0833:
0834: public void delete() throws SQLException {
0835: new InteractionEngine(processor)
0836: .deleteInteractionInstance(getId());
0837: }
0838:
0839: private PropertySet properties;
0840:
0841: /**
0842: * Creates property set or retrieves it from the database.
0843: * @throws SQLException
0844: */
0845: public synchronized PropertySet getProperties() throws SQLException {
0846: if (properties == null) {
0847: PropertySetFactory propertySetFactory = getFactory()
0848: .getPropertySetFactory();
0849: if (getPropertySet() == null) {
0850: properties = propertySetFactory.create(false);
0851: if (properties instanceof PersistentPropertySet) {
0852: setPropertySet(new Integer(
0853: ((PersistentPropertySet) properties)
0854: .getId()));
0855: update();
0856: }
0857: } else {
0858: properties = propertySetFactory.find(getPropertySet()
0859: .intValue());
0860: }
0861: }
0862: return properties;
0863: }
0864:
0865: public StepInstance getStep(int id) {
0866: return (StepInstance) getSteps().get(id);
0867: }
0868:
0869: public TransitionInstance getTransition(int id) throws SQLException {
0870: return (TransitionInstance) getTransitions().get(id);
0871: }
0872:
0873: /**
0874: * Executes after code.
0875: * @param context evaluation context.
0876: * @return If 'false' then interaction instance does not get created.
0877: * @throws HammurapiWebException
0878: */
0879: public void after(Map context) throws HammurapiWebException {
0880: try {
0881: String code = getDefinition().getAfterCode();
0882: if (code != null && code.trim().length() != 0) {
0883: Interpreter interpreter = InteractionFactory
0884: .createInterpreter(context);
0885: interpreter.set("interactionInstance", this );
0886: // TODO - Capture out and err somewhere
0887: interpreter.eval(code);
0888: }
0889: } catch (Exception e) {
0890: throw new HammurapiWebException(
0891: "Evaluation of interaction instance " + getId()
0892: + " after code failed: " + e, e);
0893: }
0894: }
0895:
0896: public boolean accept(Visitor visitor) {
0897: if (visitor.visit(this )) {
0898: List roots = new ArrayList();
0899: Object[] allSteps = getSteps().getValues();
0900: for (int i = 0; i < allSteps.length; ++i) {
0901: StepInstance si = (StepInstance) allSteps[i];
0902: if (si.getActivatorId() == null) {
0903: roots.add(si);
0904: }
0905: }
0906:
0907: Collections.sort(roots);
0908: new CollectionVisitable(roots, false).accept(visitor);
0909: return true;
0910: }
0911:
0912: return false;
0913: }
0914:
0915: /**
0916: * Marks step as completed, evaluates transitions from it and new step instances.
0917: * @param stepId Control step Id. If this parameter is not equal to internal step id then interaction rewinds
0918: * to this stepId if step with this ID is processed or throws exception if step with this id is not found
0919: * or is not in "completed" status.
0920: * @throws SQLException
0921: */
0922: public void completed(Integer stepId, final Map context,
0923: String userId) throws SQLException, HammurapiWebException {
0924: final StepInstance this Step = (StepInstance) (stepId == null ? getStepByStatus("processing")
0925: : getSteps().get(stepId.intValue()));
0926: if (this Step == null) {
0927: return; // No step to complete, just go to the next step.
0928: }
0929:
0930: if ("completed".equals(this Step.getStatus())) {
0931: Iterator it = this Step.getSuccessors().iterator();
0932: while (it.hasNext()) {
0933: ((StepInstance) it.next()).rewind();
0934: }
0935: } else if ("pending".equals(this Step.getStatus())) {
0936: throw new HammurapiWebException(
0937: "Invalid step ID: cannot complete pending step");
0938: }
0939:
0940: // Update step
0941: this Step.setStatus("completed");
0942: this Step.setFinishTime(new Long(System.currentTimeMillis()));
0943: this Step.setUserId(userId);
0944: this Step.update();
0945:
0946: Collection newTransitions = new ArrayList();
0947:
0948: // Evaluate transitions & join
0949: Iterator oit = this Step.getDefinition().getOutputs().iterator();
0950: while (oit.hasNext()) {
0951: TransitionDefinition tDef = (TransitionDefinition) oit
0952: .next();
0953: Map conditionContext = new HashMap();
0954: if (context != null) {
0955: conditionContext.putAll(context);
0956: }
0957: conditionContext.put("source", this Step);
0958: conditionContext.put("outputs", newTransitions);
0959:
0960: // Evaluate guards
0961: Object obj = tDef.condition(conditionContext);
0962: Iterator tit = tokenize(obj).iterator();
0963: while (tit.hasNext()) {
0964: Object token = tit.next();
0965: // create records, execute actions, create list of new transition instances
0966: IdentityManager identityManager = getFactory()
0967: .getIdentityManager();
0968: Connection con = processor.getConnection();
0969: SQLProcessor lp = new SQLProcessor(con, null);
0970: InteractionTransitionInstance iti = new InteractionTransitionInstanceImpl(
0971: true);
0972: iti.setTransitionId(tDef.getId());
0973: iti.setSourceId(this Step.getId());
0974: if (token != null && !(token instanceof Boolean)) {
0975: PersistentPropertySet ps = (PersistentPropertySet) getFactory()
0976: .getPropertySetFactory().create(false);
0977: if (token instanceof PropertySet) {
0978: ps.setAll((PropertySet) token);
0979: } else {
0980: ps.set("token", token);
0981: }
0982: iti.setPropertySet(new Integer(ps.getId()));
0983: }
0984: if (identityManager instanceof IdentityGenerator) {
0985: iti.setId(((IdentityGenerator) identityManager)
0986: .generate(con,
0987: "INTERACTION_TRANSITION_INSTANCE"));
0988: }
0989:
0990: new InteractionEngine(lp)
0991: .insertInteractionTransitionInstance(iti);
0992:
0993: if (identityManager instanceof IdentityRetriever) {
0994: iti.setId(((IdentityRetriever) identityManager)
0995: .retrieve(con));
0996: processor.releaseConnection(con);
0997: }
0998: newTransitions.add(iti);
0999: }
1000: }
1001:
1002: invalidate(); // To reload transitions into interaction instance.
1003:
1004: // For each transition target get other inputs, iterate over them and join with new instances of transitions
1005: Iterator it = newTransitions.iterator();
1006: while (it.hasNext()) { // On new transitions
1007: InteractionTransitionInstance iti = (InteractionTransitionInstance) it
1008: .next();
1009: TransitionInstance transitionInstance = getTransition(iti
1010: .getId());
1011: TransitionDefinition definition = transitionInstance
1012: .getDefinition();
1013: final StepDefinition targetDefinition = definition
1014: .getTargetDefinition();
1015: TIntObjectHashMap joinMap = new TIntObjectHashMap(); // definition ID -> Collection of instances for merging. First instance is always null.
1016: Iterator iit = targetDefinition.getInputs().iterator();
1017: while (iit.hasNext()) {
1018: TransitionDefinition input = (TransitionDefinition) iit
1019: .next();
1020: if (input.getId() != definition.getId()) {
1021: Collection inputInstances = new ArrayList();
1022: joinMap.put(input.getId(), inputInstances);
1023: inputInstances.add(null); // Merging with null
1024: // Find all instances of given definition
1025: Object[] allTransitions = getTransitions()
1026: .getValues();
1027: for (int i = 0; i < allTransitions.length; ++i) {
1028: if (((TransitionInstance) allTransitions[i])
1029: .getDefinition().getId() == input
1030: .getId()) {
1031: inputInstances.add(allTransitions[i]);
1032: }
1033: }
1034: }
1035: }
1036:
1037: // Now we have a map of "other" inputs and we have to join inputs and present join maps to guards
1038: Command onJoin = new Command() {
1039:
1040: public void execute(Object joinCollection) {
1041: try {
1042: // Execute guard, instantiate step(s)
1043: Map guardContext = new HashMap();
1044: if (context != null) {
1045: guardContext.putAll(context);
1046: }
1047: guardContext.put("inputs", joinCollection);
1048: guardContext.put("interactionInstance",
1049: InteractionInstance.this );
1050: Iterator git = InteractionInstance.tokenize(
1051: targetDefinition.guard(guardContext))
1052: .iterator();
1053: while (git.hasNext()) {
1054: Object token = git.next();
1055: InteractionStepInstanceImpl isii = new InteractionStepInstanceImpl(
1056: true);
1057: isii.setOwnerId(getId());
1058: isii.setStatus("pending");
1059: isii.setStepId(targetDefinition.getId());
1060: isii.setActivatorId(new Integer(this Step
1061: .getId()));
1062:
1063: StringBuffer ib = new StringBuffer();
1064: Iterator iit = ((Collection) joinCollection)
1065: .iterator();
1066: while (iit.hasNext()) {
1067: ib
1068: .append(((InteractionTransitionInstance) iit
1069: .next()).getId());
1070: if (iit.hasNext()) {
1071: ib.append(",");
1072: }
1073: }
1074: isii.setInputs(ib.toString());
1075:
1076: // Find rewound steps with the same definition and order them by creation time
1077: List rewoundSteps = new ArrayList();
1078: Object[] allSteps = getSteps().getValues();
1079: for (int i = 0; i < allSteps.length; ++i) {
1080: StepInstance rsi = (StepInstance) allSteps[i];
1081: if (rsi.getStepId() == isii.getStepId()
1082: && REWOUND.equals(rsi
1083: .getStatus())) {
1084: rewoundSteps.add(rsi);
1085: }
1086: }
1087: Collections.sort(rewoundSteps,
1088: new Comparator() {
1089:
1090: public int compare(Object arg0,
1091: Object arg1) {
1092: Long st0 = ((StepInstance) arg0)
1093: .getStartTime();
1094: Long st1 = ((StepInstance) arg1)
1095: .getStartTime();
1096: if (st0 == null) {
1097: return st1 == null ? 0
1098: : 1;
1099: }
1100:
1101: return st1 == null ? -1
1102: : -st0
1103: .compareTo(st1);
1104: }
1105:
1106: });
1107:
1108: if (token == null
1109: || token instanceof Boolean) { // Nothing to match, take the latest rewound
1110: if (!rewoundSteps.isEmpty()) {
1111: StepInstance rsi = (StepInstance) rewoundSteps
1112: .get(0);
1113: if (rsi.getPropertySet() != null) {
1114: PropertySetFactory psf = getFactory()
1115: .getPropertySetFactory();
1116: PropertySet stepProperties = psf
1117: .create(false);
1118: if (stepProperties instanceof PersistentPropertySet) {
1119: isii
1120: .setPropertySet(new Integer(
1121: ((PersistentPropertySet) stepProperties)
1122: .getId()));
1123: }
1124: stepProperties.setAll(rsi
1125: .getProperties());
1126: }
1127: }
1128: } else {
1129: PropertySetFactory psf = getFactory()
1130: .getPropertySetFactory();
1131: PropertySet stepProperties = psf
1132: .create(false);
1133: if (stepProperties instanceof PersistentPropertySet) {
1134: isii
1135: .setPropertySet(new Integer(
1136: ((PersistentPropertySet) stepProperties)
1137: .getId()));
1138: }
1139:
1140: if (token instanceof PropertySet) {
1141: stepProperties
1142: .setAll((PropertySet) token);
1143: } else {
1144: stepProperties.set("token", token);
1145: }
1146:
1147: // Find rewound step to copy properties from
1148: Iterator rit = rewoundSteps.iterator();
1149: while (rit.hasNext()) {
1150: StepInstance rsi = (StepInstance) rit
1151: .next();
1152: if (rsi.getPropertySet() != null
1153: && rsi
1154: .getProperties()
1155: .containsAll(
1156: stepProperties)) { // Match
1157: stepProperties.setAll(rsi
1158: .getProperties());
1159: break;
1160: }
1161: }
1162: }
1163: new InteractionEngine(processor)
1164: .insertInteractionStepInstance(isii);
1165: }
1166: } catch (SQLException e) {
1167: throw new HammurapiWebRuntimeException(
1168: "Cannot create step instance: " + e, e);
1169: } catch (HammurapiWebException e) {
1170: throw new HammurapiWebRuntimeException(
1171: "Cannot create step instance: " + e, e);
1172: }
1173: }
1174: };
1175:
1176: join(transitionInstance, joinMap.getValues(), 0, null,
1177: onJoin);
1178: }
1179:
1180: invalidate(); // To load new step instances and transitions.
1181: }
1182:
1183: /**
1184: * Creates collection of joined transitions. Null values are omitted.
1185: * @param newTransition New transition to join with existing transitions
1186: * @param collectionsToJoin Collecions of existing input transitions to join with the new transition
1187: * @param idx Index of current collection
1188: * @param joinArray Receptor array to put transitions to be joined
1189: */
1190: private void join(Object newTransition, Object[] collectionsToJoin,
1191: int idx, Object[] joinArray, Command onJoin) {
1192:
1193: if (idx == 0) {
1194: joinArray = new Object[collectionsToJoin.length + 1];
1195: joinArray[0] = newTransition;
1196: }
1197:
1198: if (idx == collectionsToJoin.length) {
1199: Collection joinc = new ArrayList();
1200: for (int i = 0; i < joinArray.length; ++i) {
1201: if (joinArray[i] != null) {
1202: joinc.add(joinArray[i]);
1203: }
1204: onJoin.execute(joinc);
1205: }
1206: } else {
1207: Iterator it = ((Collection) collectionsToJoin[idx])
1208: .iterator();
1209: while (it.hasNext()) {
1210: joinArray[idx + 1] = it.next();
1211: join(newTransition, collectionsToJoin, idx + 1,
1212: joinArray, onJoin);
1213: }
1214: }
1215: }
1216:
1217: /**
1218: * Converts object into collection of tokens according to the following rules:
1219: * a) If object is Iterator then each next() return value becomes a token. b) If object is Enumeration then each
1220: * nextElement() return value becomes a token. c) If object is Boolean.FALSE then it is discarded d) Otherwise object per se becomes a token.
1221: * @param obj
1222: * @return
1223: */
1224: public static Collection tokenize(Object obj) {
1225: Collection tokens = new ArrayList();
1226: if (obj instanceof Iterator) {
1227: Iterator tit = (Iterator) obj;
1228: while (tit.hasNext()) {
1229: tokens.add(tit.next());
1230: }
1231: } else if (obj instanceof Enumeration) {
1232: Enumeration tenum = (Enumeration) obj;
1233: while (tenum.hasMoreElements()) {
1234: tokens.add(tenum.nextElement());
1235: }
1236: } else if (!Boolean.FALSE.equals(obj)) {
1237: tokens.add(obj);
1238: }
1239: return tokens;
1240: }
1241:
1242: /**
1243: * @return Step instance of parent interaction which initiated this interaction
1244: * @throws SQLException
1245: */
1246: public StepInstance getOwnerStep() throws SQLException {
1247: if (getStepId() == null) {
1248: return null;
1249: }
1250:
1251: InteractionStepInstance ownerStep = new InteractionEngine(
1252: processor).getInteractionStepInstance(getStepId()
1253: .intValue());
1254: InteractionInstance ownerInteraction = getFactory()
1255: .getInteractionInstance(ownerStep.getOwnerId());
1256: return ownerInteraction.getStep(ownerStep.getId());
1257: }
1258:
1259: private gnu.trove.TIntObjectHashMap getSteps() {
1260: try {
1261: loadStepsAndTransitions();
1262: } catch (SQLException e) {
1263: throw new HammurapiWebRuntimeException(
1264: "Cannot load interaction instance steps: " + e, e);
1265: }
1266: return steps;
1267: }
1268:
1269: private gnu.trove.TIntObjectHashMap getTransitions() {
1270: try {
1271: loadStepsAndTransitions();
1272: } catch (SQLException e) {
1273: throw new HammurapiWebRuntimeException(
1274: "Cannot load interaction instance transitions: "
1275: + e, e);
1276: }
1277: return transitions;
1278: }
1279:
1280: public void toDom(Element holder) {
1281: try {
1282: if (getPropertySet() != null) {
1283: setAttribute("properties", getProperties());
1284: }
1285: setAttribute("definition", getDefinition());
1286: } catch (SQLException e) {
1287: throw new HammurapiWebRuntimeException(e);
1288: }
1289: setAttribute("steps", getSteps().getValues());
1290: setAttribute("transitions", getTransitions().getValues());
1291: super .toDom(holder);
1292: }
1293:
1294: /**
1295: * Convenience method to retrieve interaction instance properties.
1296: * @param name
1297: * @return
1298: * @throws SQLException
1299: */
1300: public Object getProperty(String name) {
1301: try {
1302: return getPropertySet() == null ? null : getProperties()
1303: .get(name);
1304: } catch (SQLException e) {
1305: throw new HammurapiWebRuntimeException(e);
1306: }
1307: }
1308: }
|