1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.scxml2;
18
19 import java.io.Serializable;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.UUID;
27
28 import javax.xml.parsers.DocumentBuilderFactory;
29 import javax.xml.parsers.ParserConfigurationException;
30
31 import org.apache.commons.scxml2.env.SimpleContext;
32 import org.apache.commons.scxml2.model.Data;
33 import org.apache.commons.scxml2.model.Datamodel;
34 import org.apache.commons.scxml2.model.EnterableState;
35 import org.apache.commons.scxml2.model.History;
36 import org.apache.commons.scxml2.model.ModelException;
37 import org.apache.commons.scxml2.model.SCXML;
38 import org.apache.commons.scxml2.model.TransitionalState;
39 import org.apache.commons.scxml2.semantics.ErrorConstants;
40 import org.w3c.dom.Document;
41 import org.w3c.dom.Element;
42 import org.w3c.dom.Node;
43
44
45
46
47
48
49 public class SCInstance implements Serializable {
50
51
52
53
54 private static final long serialVersionUID = 2L;
55
56
57
58
59 private static final String ERR_NO_STATE_MACHINE = "SCInstance: State machine not set";
60
61
62
63
64 private static final String ERR_NO_ERROR_REPORTER = "SCInstance: ErrorReporter not set";
65
66
67
68
69 private boolean initialized;
70
71
72
73
74 private SCXML stateMachine;
75
76
77
78
79 private final StateConfiguration stateConfiguration;
80
81
82
83
84 private final Status currentStatus;
85
86
87
88
89 private boolean running;
90
91
92
93
94 private transient SCXMLIOProcessor internalIOProcessor;
95
96
97
98
99 private transient Evaluator evaluator;
100
101
102
103
104 private transient ErrorReporter errorReporter = null;
105
106
107
108
109 private final Map<EnterableState, Context> contexts = new HashMap<EnterableState, Context>();
110
111
112
113
114 private final Map<History, Set<EnterableState>> histories = new HashMap<History, Set<EnterableState>>();
115
116
117
118
119 private Context rootContext;
120
121
122
123
124 private SCXMLSystemContext systemContext;
125
126
127
128
129 private Context globalContext;
130
131
132
133
134 private boolean singleContext;
135
136
137
138
139
140
141
142 protected SCInstance(final SCXMLIOProcessor internalIOProcessor, final Evaluator evaluator,
143 final ErrorReporter errorReporter) {
144 this.internalIOProcessor = internalIOProcessor;
145 this.evaluator = evaluator;
146 this.errorReporter = errorReporter;
147 this.stateConfiguration = new StateConfiguration();
148 this.currentStatus = new Status(stateConfiguration);
149 }
150
151
152
153
154
155
156 protected void initialize() throws ModelException {
157 running = false;
158 if (stateMachine == null) {
159 throw new ModelException(ERR_NO_STATE_MACHINE);
160 }
161 if (evaluator == null) {
162 evaluator = EvaluatorFactory.getEvaluator(stateMachine);
163 }
164 if (stateMachine.getDatamodelName() != null && !stateMachine.getDatamodelName().equals(evaluator.getSupportedDatamodel())) {
165 throw new ModelException("Incompatible SCXML document datamodel \""+stateMachine.getDatamodelName()+"\""
166 + " for evaluator "+evaluator.getClass().getName()+" supported datamodel \""+evaluator.getSupportedDatamodel()+"\"");
167 }
168 if (errorReporter == null) {
169 throw new ModelException(ERR_NO_ERROR_REPORTER);
170 }
171 systemContext = null;
172 globalContext = null;
173 contexts.clear();
174 histories.clear();
175 stateConfiguration.clear();
176
177
178 Datamodel rootdm = stateMachine.getDatamodel();
179 cloneDatamodel(rootdm, getGlobalContext(), evaluator, errorReporter);
180 initialized = true;
181 }
182
183
184
185
186
187
188
189 protected void detach() {
190 this.internalIOProcessor = null;
191 this.evaluator = null;
192 this.errorReporter = null;
193 }
194
195
196
197
198
199 protected void setInternalIOProcessor(SCXMLIOProcessor internalIOProcessor) {
200 this.internalIOProcessor = internalIOProcessor;
201 }
202
203
204
205
206
207
208
209
210
211 protected void setEvaluator(Evaluator evaluator, boolean reAttach) throws ModelException {
212 this.evaluator = evaluator;
213 if (initialized) {
214 if (!reAttach) {
215
216 initialize();
217 }
218 else if (evaluator == null) {
219 throw new ModelException("SCInstance: re-attached without Evaluator");
220 }
221 }
222 }
223
224
225
226
227 protected Evaluator getEvaluator() {
228 return evaluator;
229 }
230
231
232
233
234
235
236 protected void setErrorReporter(ErrorReporter errorReporter) throws ModelException {
237 if (errorReporter == null) {
238 throw new ModelException(ERR_NO_ERROR_REPORTER);
239 }
240 this.errorReporter = errorReporter;
241 }
242
243
244
245
246 public SCXML getStateMachine() {
247 return stateMachine;
248 }
249
250
251
252
253
254
255
256
257
258
259 protected void setStateMachine(SCXML stateMachine) throws ModelException {
260 if (stateMachine == null) {
261 throw new ModelException(ERR_NO_STATE_MACHINE);
262 }
263 this.stateMachine = stateMachine;
264 initialize();
265 }
266
267 public void setSingleContext(boolean singleContext) throws ModelException {
268 if (initialized) {
269 throw new ModelException("SCInstance: already initialized");
270 }
271 this.singleContext = singleContext;
272 }
273
274 public boolean isSingleContext() {
275 return singleContext;
276 }
277
278
279
280
281
282
283
284
285
286 protected void cloneDatamodel(final Datamodel datamodel, final Context ctx, final Evaluator evaluator,
287 final ErrorReporter errorReporter) {
288 if (datamodel == null || Evaluator.NULL_DATA_MODEL.equals(evaluator.getSupportedDatamodel())) {
289 return;
290 }
291 List<Data> data = datamodel.getData();
292 if (data == null) {
293 return;
294 }
295 for (Data datum : data) {
296 if (ctx.has(datum.getId())) {
297
298 continue;
299 }
300 Node datumNode = datum.getNode();
301 Node valueNode = null;
302 if (datumNode != null) {
303 valueNode = datumNode.cloneNode(true);
304 }
305
306 if (datum.getSrc() != null) {
307 ctx.setLocal(datum.getId(), valueNode);
308 } else if (datum.getExpr() != null) {
309 Object value;
310 try {
311 ctx.setLocal(Context.NAMESPACES_KEY, datum.getNamespaces());
312 value = evaluator.eval(ctx, datum.getExpr());
313 ctx.setLocal(Context.NAMESPACES_KEY, null);
314 } catch (SCXMLExpressionException see) {
315 if (internalIOProcessor != null) {
316 internalIOProcessor.addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT));
317 }
318 errorReporter.onError(ErrorConstants.EXPRESSION_ERROR, see.getMessage(), datum);
319 continue;
320 }
321 if (Evaluator.XPATH_DATA_MODEL.equals(evaluator.getSupportedDatamodel())) {
322 try {
323 Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
324
325 Element dataNode = document.createElement("data");
326 dataNode.setAttribute("id", datum.getId());
327 ctx.setLocal(datum.getId(), dataNode);
328 evaluator.evalAssign(ctx, "$" + datum.getId(), value, Evaluator.AssignType.REPLACE_CHILDREN, null);
329 }
330 catch (ParserConfigurationException pce) {
331 if (internalIOProcessor != null) {
332 internalIOProcessor.addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT));
333 }
334 errorReporter.onError(ErrorConstants.EXECUTION_ERROR, pce.getMessage(), datum);
335 }
336 catch (SCXMLExpressionException see) {
337 if (internalIOProcessor != null) {
338 internalIOProcessor.addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT));
339 }
340 errorReporter.onError(ErrorConstants.EXPRESSION_ERROR, see.getMessage(), datum);
341 }
342 }
343 else {
344 ctx.setLocal(datum.getId(), value);
345 }
346 } else {
347 ctx.setLocal(datum.getId(), valueNode);
348 }
349 }
350 }
351
352
353
354
355 public StateConfiguration getStateConfiguration() {
356 return stateConfiguration;
357 }
358
359
360
361
362 public Status getCurrentStatus() {
363 return currentStatus;
364 }
365
366
367
368
369 public boolean isRunning() {
370 return running;
371 }
372
373
374
375
376
377
378 protected void setRunning(final boolean running) throws IllegalStateException {
379 if (!this.running && running && currentStatus.isFinal()) {
380 throw new IllegalStateException("The state machine is in a Final state and cannot be set running again");
381 }
382 this.running = running;
383 }
384
385
386
387
388
389
390 public Context getRootContext() {
391 if (rootContext == null && evaluator != null) {
392 rootContext = Evaluator.NULL_DATA_MODEL.equals(evaluator.getSupportedDatamodel())
393 ? new SimpleContext() : evaluator.newContext(null);
394 }
395 return rootContext;
396 }
397
398
399
400
401
402 protected void setRootContext(final Context context) {
403 this.rootContext = context;
404
405 getRootContext();
406 if (systemContext != null) {
407
408 systemContext.setSystemContext(evaluator.newContext(rootContext));
409 }
410 }
411
412
413
414
415
416
417 public Context getSystemContext() {
418 if (systemContext == null) {
419
420 getRootContext();
421 if (rootContext != null) {
422 Context internalContext = Evaluator.NULL_DATA_MODEL.equals(evaluator.getSupportedDatamodel()) ?
423 new SimpleContext(systemContext) : evaluator.newContext(rootContext);
424 systemContext = new SCXMLSystemContext(internalContext);
425 systemContext.getContext().set(SCXMLSystemContext.SESSIONID_KEY, UUID.randomUUID().toString());
426 String _name = stateMachine != null && stateMachine.getName() != null ? stateMachine.getName() : "";
427 systemContext.getContext().set(SCXMLSystemContext.SCXML_NAME_KEY, _name);
428 systemContext.getPlatformVariables().put(SCXMLSystemContext.STATUS_KEY, currentStatus);
429 }
430 }
431 return systemContext != null ? systemContext.getContext() : null;
432 }
433
434
435
436
437 public Context getGlobalContext() {
438 if (globalContext == null) {
439
440 getSystemContext();
441 if (systemContext != null) {
442 globalContext = evaluator.newContext(systemContext);
443 }
444 }
445 return globalContext;
446 }
447
448
449
450
451
452
453
454 public Context getContext(final EnterableState state) {
455 Context context = contexts.get(state);
456 if (context == null) {
457 if (singleContext) {
458 context = getGlobalContext();
459 }
460 else {
461 EnterableState parent = state.getParent();
462 if (parent == null) {
463
464 context = evaluator.newContext(getGlobalContext());
465 } else {
466 context = evaluator.newContext(getContext(parent));
467 }
468 }
469 if (state instanceof TransitionalState) {
470 Datamodel datamodel = ((TransitionalState)state).getDatamodel();
471 cloneDatamodel(datamodel, context, evaluator, errorReporter);
472 }
473 contexts.put(state, context);
474 }
475 return context;
476 }
477
478
479
480
481
482
483
484
485
486 Context lookupContext(final EnterableState state) {
487 return contexts.get(state);
488 }
489
490
491
492
493
494
495
496
497
498 void setContext(final EnterableState state,
499 final Context context) {
500 contexts.put(state, context);
501 }
502
503
504
505
506
507
508
509 public Set<EnterableState> getLastConfiguration(final History history) {
510 Set<EnterableState> lastConfiguration = histories.get(history);
511 if (lastConfiguration == null) {
512 lastConfiguration = Collections.emptySet();
513 }
514 return lastConfiguration;
515 }
516
517
518
519
520
521
522
523 public void setLastConfiguration(final History history,
524 final Set<EnterableState> lc) {
525 histories.put(history, new HashSet<EnterableState>(lc));
526 }
527
528
529
530
531
532
533
534
535 public void resetConfiguration(final History history) {
536 histories.remove(history);
537 }
538 }
539