1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.scxml2.env.javascript;
19
20 import java.util.UUID;
21 import java.util.regex.Pattern;
22
23 import javax.script.Bindings;
24 import javax.script.ScriptContext;
25 import javax.script.ScriptEngine;
26 import javax.script.ScriptEngineManager;
27
28 import org.apache.commons.scxml2.Context;
29 import org.apache.commons.scxml2.Evaluator;
30 import org.apache.commons.scxml2.EvaluatorProvider;
31 import org.apache.commons.scxml2.SCXMLExpressionException;
32 import org.apache.commons.scxml2.XPathBuiltin;
33 import org.apache.commons.scxml2.env.EffectiveContextMap;
34 import org.apache.commons.scxml2.model.SCXML;
35
36
37
38
39
40
41
42
43
44
45
46
47
48 public class JSEvaluator implements Evaluator {
49
50
51
52
53 private static final String ASSIGN_VARIABLE_NAME = "a"+UUID.randomUUID().toString().replace('-','x');
54
55 public static final String SUPPORTED_DATA_MODEL = Evaluator.ECMASCRIPT_DATA_MODEL;
56
57 public static class JSEvaluatorProvider implements EvaluatorProvider {
58
59 @Override
60 public String getSupportedDatamodel() {
61 return SUPPORTED_DATA_MODEL;
62 }
63
64 @Override
65 public Evaluator getEvaluator() {
66 return new JSEvaluator();
67 }
68
69 @Override
70 public Evaluator getEvaluator(final SCXML document) {
71 return new JSEvaluator();
72 }
73 }
74
75
76 private static final String ERR_CTX_TYPE = "Error evaluating JavaScript "
77 + "expression, Context must be a org.apache.commons.scxml2.env.javascript.JSContext";
78
79
80 private static final Pattern IN_FN = Pattern.compile("In\\(");
81
82 private static final Pattern DATA_FN = Pattern.compile("Data\\(");
83
84 private static final Pattern LOCATION_FN = Pattern.compile("Location\\(");
85
86
87
88 private ScriptEngineManager factory;
89
90
91
92
93
94
95 public JSEvaluator() {
96 factory = new ScriptEngineManager();
97 }
98
99
100
101 @Override
102 public String getSupportedDatamodel() {
103 return SUPPORTED_DATA_MODEL;
104 }
105
106
107
108
109
110
111
112 @Override
113 public Context newContext(Context parent) {
114 return new JSContext(parent);
115 }
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131 @Override
132 public Object eval(Context context, String expression) throws SCXMLExpressionException {
133 if (expression == null) {
134 return null;
135 }
136
137 if (!(context instanceof JSContext)) {
138 throw new SCXMLExpressionException(ERR_CTX_TYPE);
139 }
140
141 try {
142 JSContext effectiveContext = getEffectiveContext((JSContext) context);
143
144
145 ScriptEngine engine = factory.getEngineByName("JavaScript");
146 Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
147
148
149 String jsExpression = IN_FN.matcher(expression).replaceAll("_builtin.In(");
150 jsExpression = DATA_FN.matcher(jsExpression).replaceAll("_builtin.Data(");
151 jsExpression = LOCATION_FN.matcher(jsExpression).replaceAll("_builtin.Location(");
152
153
154 JSBindings jsBindings = new JSBindings(effectiveContext, bindings);
155 jsBindings.put("_builtin", new JSFunctions(effectiveContext));
156
157 Object ret = engine.eval(jsExpression, jsBindings);
158
159
160 copyGlobalBindingsToContext(jsBindings, (JSContext) effectiveContext);
161
162 return ret;
163
164 } catch (Exception x) {
165 throw new SCXMLExpressionException("Error evaluating ['" + expression + "'] " + x);
166 }
167 }
168
169
170
171
172
173
174
175
176
177
178
179
180
181 @Override
182 public Boolean evalCond(Context context, String expression) throws SCXMLExpressionException {
183 final Object result = eval(context, expression);
184
185 if (result == null) {
186 return Boolean.FALSE;
187 }
188
189 if (result instanceof Boolean) {
190 return (Boolean)result;
191 }
192
193 throw new SCXMLExpressionException("Invalid boolean expression: " + expression);
194 }
195
196
197
198
199
200
201
202
203
204
205
206
207
208 @Override
209 public Object evalLocation(Context context, String expression) throws SCXMLExpressionException {
210 if (expression == null) {
211 return null;
212 } else if (context.has(expression)) {
213 return expression;
214 }
215
216 return eval(context, expression);
217 }
218
219
220
221
222 public void evalAssign(final Context ctx, final String location, final Object data, final AssignType type,
223 final String attr) throws SCXMLExpressionException {
224
225 Object loc = evalLocation(ctx, location);
226
227 if (loc != null) {
228 if (XPathBuiltin.isXPathLocation(ctx, loc)) {
229 XPathBuiltin.assign(ctx, loc, data, type, attr);
230 } else {
231 StringBuilder sb = new StringBuilder(location).append("=").append(ASSIGN_VARIABLE_NAME);
232
233 try {
234 ctx.getVars().put(ASSIGN_VARIABLE_NAME, data);
235 eval(ctx, sb.toString());
236 } finally {
237 ctx.getVars().remove(ASSIGN_VARIABLE_NAME);
238 }
239 }
240 } else {
241 throw new SCXMLExpressionException("evalAssign - cannot resolve location: '" + location + "'");
242 }
243 }
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259 @Override
260 public Object evalScript(Context ctx, String script) throws SCXMLExpressionException {
261 return eval(ctx, script);
262 }
263
264
265
266
267
268
269
270
271
272
273 protected JSContext getEffectiveContext(final JSContext nodeCtx) {
274 return new JSContext(nodeCtx, new EffectiveContextMap(nodeCtx));
275 }
276
277
278
279
280
281
282
283
284 private void copyGlobalBindingsToContext(final JSBindings jsBindings, final JSContext jsContext) {
285 Bindings globalBindings = jsBindings.getGlobalBindings();
286
287 if (globalBindings != null) {
288 for (String key : globalBindings.keySet()) {
289 jsContext.set(key, globalBindings.get(key));
290 }
291 }
292 }
293 }