public
Description: Shade is a state machine (or workflow) engine for ColdFusion business objects.
Homepage:
Clone URL: git://github.com/ryanwood/shade.git
Added persistence object for saving which creates more accurate events
ryanwood (author)
Mon Oct 13 06:52:34 -0700 2008
commit  0bdacba1386989f5809b763877b67038f0bf609a
tree    fad87858025708f2f463ba9f617b29a9ab69e189
parent  4c22d15ff9d74cfaf15153438ed0b4bb6ca7af08
...
29
30
31
 
 
32
33
34
...
36
37
38
39
 
40
41
42
...
29
30
31
32
33
34
35
36
...
38
39
40
 
41
42
43
44
0
@@ -29,6 +29,8 @@
0
   
0
   <cffunction name="fire" access="public" output="false">
0
     <cfargument name="obj" type="any" required="true" />
0
+    <cfargument name="persistenceObject" required="false" />
0
+    <cfargument name="persistenceMethod" required="false" />
0
     <cfset var i = 1 />
0
     <cfset var transitions = instance.transitions.getTransitionsFromState(arguments.obj.getCurrentState()) />
0
     <cfset transition = 0 />
0
@@ -36,7 +38,7 @@
0
     <cfset transitions.reset() />
0
     <cfloop condition="transitions.hasNext()">
0
       <cfset transition = transitions.next() />
0
-      <cfif transition.perform(obj)>
0
+      <cfif transition.perform(obj, arguments.persistenceObject, arguments.persistenceMethod)>
0
         <cfreturn true />
0
       </cfif>
0
     </cfloop>  
...
19
20
21
 
 
 
 
 
22
23
24
...
35
36
37
 
38
39
40
...
19
20
21
22
23
24
25
26
27
28
29
...
40
41
42
43
44
45
46
0
@@ -19,6 +19,11 @@
0
     <cfset invokeCallback('beforeAction', arguments.obj) />
0
     <cfreturn invokeCallback('before#getName()#Action', arguments.obj) />
0
   </cffunction>
0
+
0
+  <cffunction name="fail" returntype="void" access="public" output="false">
0
+    <cfargument name="obj" required="true" />
0
+    <cfset invokeCallback('failedAction', arguments.obj) />
0
+  </cffunction>
0
   
0
   <cffunction name="after" returntype="void" access="public" output="false">
0
     <cfargument name="obj" required="true" />    
0
@@ -35,6 +40,7 @@
0
   <cffunction name="invokeCallback" returntype="boolean" access="private" output="false">
0
     <cfargument name="callback" type="string" required="true" />
0
     <cfargument name="obj" required="true" />
0
+    <cftrace text="#callback#: #obj.getMyState()#" />
0
     <cfif structKeyExists(arguments.obj, arguments.callback)>
0
       <cfinvoke component="#arguments.obj#" method="#arguments.callback#" returnvariable="result" />
0
       <cfif isDefined('result') and isBoolean(result)>
...
3
4
5
6
 
7
8
 
 
9
10
11
12
13
14
15
 
 
 
16
17
18
...
44
45
46
47
 
48
49
50
...
3
4
5
 
6
7
8
9
10
11
12
13
14
15
16
 
17
18
19
20
21
22
...
48
49
50
 
51
52
53
54
0
@@ -3,16 +3,20 @@
0
   <cfset instance = structNew() />
0
   
0
   <cffunction name="init" access="public" output="false">
0
-    <cfargument name="OriginalObject" required="true" />
0
+    <cfargument name="originalObject" required="true" />
0
     <cfargument name="initialState" type="string" required="true" />
0
     <cfargument name="stateMethod" type="string" required="false" default="state" />
0
+    <cfargument name="persistenceObject" required="false" default="" hint="a service that persist the business object" />
0
+    <cfargument name="persistenceMethod" required="false" default="save" />
0
     <cfscript>
0
       setOriginalObject(arguments.OriginalObject);
0
       instance.states = structNew();
0
       instance.transitionTable = structNew();
0
       instance.eventTable = structNew();
0
       instance.stateMethod = arguments.stateMethod;      
0
-      instance.initialState = arguments.initialState;
0
+      instance.initialState = arguments.initialState;      
0
+      instance.persistenceObject = arguments.persistenceObject;
0
+      instance.persistenceMethod = arguments.persistenceMethod;
0
             
0
       configureState();
0
       if(getState() eq '') {
0
@@ -44,7 +48,7 @@
0
   <cffunction name="fireEvent" access="public" output="false">
0
     <cfargument name="name" type="string" required="true" />
0
     <cfif structKeyExists(instance.eventTable, arguments.name)>
0
-      <cfreturn instance.eventTable[arguments.name].fire(this) />
0
+      <cfreturn instance.eventTable[arguments.name].fire(this, instance.persistenceObject, instance.persistenceMethod) />
0
     </cfif>
0
     <cfreturn false />
0
   </cffunction>
...
1
 
2
3
4
...
36
37
38
 
 
 
39
40
41
...
49
50
51
52
 
 
53
 
 
 
 
 
 
 
54
55
56
...
69
70
71
 
 
 
 
 
 
 
 
 
 
 
 
 
72
73
74
...
 
1
2
3
4
...
36
37
38
39
40
41
42
43
44
...
52
53
54
 
55
56
57
58
59
60
61
62
63
64
65
66
67
...
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
0
@@ -1,4 +1,4 @@
0
-<cfcomponent displayname="Transition" output="false">
0
+<cfcomponent displayname="StateTransition" output="false">
0
   
0
   <cfset instance = structNew() />
0
   
0
@@ -36,6 +36,9 @@
0
   
0
   <cffunction name="perform" access="public" output="false">
0
     <cfargument name="obj" required="true" />
0
+    <cfargument name="persistenceObject" required="false" />
0
+    <cfargument name="persistenceMethod" required="false" default="save" />
0
+    
0
     <cfscript>
0
       var local = structNew();
0
     
0
@@ -49,8 +52,16 @@
0
       local.oldState = local.states[obj.getCurrentState()];
0
       
0
       if(not local.isLoopback) {
0
-        if(local.nextState.before(obj)) {  
0
+        if(local.nextState.before(obj)) {
0
+          // Set the state
0
           obj.setInternalState(getToState());
0
+          // Do the update if we have an updater reference
0
+          if(structKeyExists(arguments, 'persistenceObject') and isObject(arguments.persistenceObject)) {
0
+            if(not persist(arguments.persistenceObject, arguments.persistenceMethod, obj)) {
0
+              local.nextState.fail(obj);
0
+              return false;
0
+            }
0
+          }
0
           local.nextState.after(obj);  
0
           local.oldState.exit(obj);  
0
         } else {
0
@@ -69,5 +80,18 @@
0
   <cffunction name="getToState" access="public" returntype="string" output="false">
0
     <cfreturn instance.to />
0
   </cffunction>
0
+
0
+  <cffunction name="persist" access="private" returntype="boolean" output="false">
0
+    <cfargument name="component" required="true" />
0
+    <cfargument name="method" required="true" />
0
+    <cfargument name="obj" required="true" />
0
+    <cfinvoke component="#arguments.component#" method="#arguments.method#" returnvariable="success">
0
+      <cfinvokeargument name="1" value="#arguments.obj#" />
0
+    </cfinvoke>
0
+    <!--- If the method returns void, assume it succeeded --->
0
+    <cfparam name="success" default="true" type="boolean" />
0
+    <cfreturn success />
0
+  </cffunction>  
0
+  
0
   
0
 </cfcomponent>
0
\ No newline at end of file
...
72
73
74
75
 
76
77
78
...
97
98
99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
101
102
...
72
73
74
 
75
76
77
78
...
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
0
@@ -72,7 +72,7 @@
0
     <cfset instance.NeedingAttentionEnter = arguments.value />
0
   </cffunction>  
0
   
0
-    <cffunction name="getNeedingAttentionAfter" access="public" returntype="string" output="false">
0
+  <cffunction name="getNeedingAttentionAfter" access="public" returntype="string" output="false">
0
     <cfreturn instance.NeedingAttentionAfter />
0
   </cffunction>
0
   
0
@@ -97,5 +97,23 @@
0
   <cffunction name="getBeforeActionCount" access="public" returntype="Numeric" output="false">
0
     <cfreturn instance.beforeActionCount />
0
   </cffunction>  
0
+  
0
+  <cffunction name="getFailed" access="public" returntype="boolean" output="false">
0
+    <cfreturn instance.Failed />
0
+  </cffunction>
0
+  
0
+  <cffunction name="setFailed" access="public" returntype="void" output="false">
0
+    <cfargument name="Failed" type="boolean" required="true" />
0
+    <cfset instance.Failed = arguments.Failed />
0
+  </cffunction>
0
+
0
+  <cffunction name="getFailedForAwaitingResponse" access="public" returntype="boolean" output="false">
0
+    <cfreturn instance.FailedForAwaitingResponse />
0
+  </cffunction>
0
+  
0
+  <cffunction name="setFailedForAwaitingResponse" access="public" returntype="void" output="false">
0
+    <cfargument name="FailedForAwaitingResponse" type="boolean" required="true" />
0
+    <cfset instance.FailedForAwaitingResponse = arguments.FailedForAwaitingResponse />
0
+  </cffunction>
0
 
0
 </cfcomponent>
0
\ No newline at end of file
...
2
3
4
5
6
 
 
 
7
8
9
...
62
63
64
 
 
 
 
65
66
...
2
3
4
 
 
5
6
7
8
9
10
...
63
64
65
66
67
68
69
70
71
0
@@ -2,8 +2,9 @@
0
   <cfset instance = structNew() />
0
   
0
   <cffunction name="init" access="public" returntype="any" output="false">
0
-    <cfargument name="conversation" type="shade.test.Conversation" required="true" />    
0
-    <cfset super.init(arguments.conversation, 'needingAttention', 'MyState') />
0
+    <cfargument name="conversation" type="shade.test.Conversation" required="true" />
0
+    <cfargument name="persistenceObject" required="false" />  
0
+    <cfset super.init(arguments.conversation, 'needingAttention', 'MyState', arguments.persistenceObject, 'save') />
0
     <cfreturn this />
0
   </cffunction>
0
     
0
@@ -62,4 +63,8 @@
0
     <cfset getOriginalObject().incrementBeforeActionCount() />
0
   </cffunction>
0
 
0
+  <cffunction name="failedAction" access="public" returntype="void" output="false">
0
+    <cfset getOriginalObject().setFailed(true) />
0
+  </cffunction>
0
+
0
 </cfcomponent>
0
\ No newline at end of file
...
2
3
4
5
6
 
 
7
8
9
...
72
73
74
75
 
76
77
78
 
79
80
81
...
155
156
157
158
159
 
160
161
162
163
164
 
165
166
167
...
231
232
233
 
 
 
 
 
 
 
 
 
234
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
236
...
2
3
4
 
 
5
6
7
8
9
...
72
73
74
 
75
76
77
 
78
79
80
81
...
155
156
157
 
 
158
159
160
161
162
 
163
164
165
166
...
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
0
@@ -2,8 +2,8 @@
0
 
0
   <cffunction name="setup" returntype="void" access="public">
0
     <cfscript>
0
-      original = createObject("component", "shade.test.Conversation").init();
0
-      conversation = createObject( "component", "shade.test.ConversationState" ).init(original);
0
+      service = createObject("component", "shade.test.ConversationService").init();
0
+      conversation = service.new();
0
     </cfscript>
0
   </cffunction>
0
   
0
@@ -72,10 +72,10 @@
0
     <cfscript>
0
       conversation.view();
0
       assertTrue(conversation.isInState('read'));
0
-      assertTrue(conversation.isRead());
0
+      assertTrue(conversation.isRead());      
0
     </cfscript>
0
   </cffunction>
0
-
0
+  
0
   <cffunction name="testCanGoFromReadToClosedBecauseGuardPasses" returntype="void" access="public" output="false">
0
     <cfscript>
0
       conversation.setCanClose(true);
0
@@ -155,13 +155,12 @@
0
     <cfscript>
0
       conversation.setReadExit(false);
0
       conversation.view();
0
-      conversation.junk();
0
-      
0
+      conversation.junk();      
0
       assertTrue(conversation.getReadExit());
0
     </cfscript>
0
   </cffunction>
0
   
0
-  <cffunction name="testEntryAndExitNotRunOnLoopbackTransation" returntype="void" access="public" output="false">
0
+  <cffunction name="testEntryAndExitNotRunOnLoopbackTransition" returntype="void" access="public" output="false">
0
     <cfscript>
0
       conversation.view();
0
       conversation.setReadEnter(false);
0
@@ -231,5 +230,33 @@
0
       assertEquals(3, conversation.getBeforeActionCount());
0
     </cfscript>
0
   </cffunction>
0
+
0
+  <cffunction name="testRecordIsPersistedOnStateChange" returntype="void" access="public" output="false">
0
+    <cfscript>
0
+      assertFalse(service.recordSaved());
0
+      conversation.view();
0
+      assertTrue(service.recordSaved());
0
+      assertTrue(conversation.isRead());      
0
+    </cfscript>
0
+  </cffunction>  
0
   
0
+  <cffunction name="testAfterAndExitNotRunOnPersistenceFailed" returntype="void" access="public" output="false">
0
+    <cfscript>
0
+      service.setSaveShouldFail(true);      
0
+      conversation.setReadAfterFirstAction(false);
0
+      assertFalse(conversation.view());
0
+      assertFalse(service.recordSaved());
0
+      assertFalse(conversation.getReadAfterFirstAction());
0
+    </cfscript>
0
+  </cffunction>
0
+
0
+  <cffunction name="testFailedActionExecuted" returntype="void" access="public" output="false">
0
+    <cfscript>
0
+      service.setSaveShouldFail(true);
0
+      conversation.setFailed(false);      
0
+      assertFalse(conversation.view());    
0
+      assertTrue(conversation.getFailed());
0
+    </cfscript>
0
+  </cffunction>
0
+    
0
 </cfcomponent>
0
\ No newline at end of file

Comments