1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.scxml2.env.groovy;
18
19 import groovy.lang.Script;
20
21 import java.io.Serializable;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.UUID;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28
29 import org.apache.commons.scxml2.Context;
30 import org.apache.commons.scxml2.Evaluator;
31 import org.apache.commons.scxml2.EvaluatorProvider;
32 import org.apache.commons.scxml2.SCXMLExpressionException;
33 import org.apache.commons.scxml2.SCXMLSystemContext;
34 import org.apache.commons.scxml2.XPathBuiltin;
35 import org.apache.commons.scxml2.env.EffectiveContextMap;
36 import org.apache.commons.scxml2.model.SCXML;
37
38
39
40
41
42
43
44 public class GroovyEvaluator implements Evaluator, Serializable {
45
46
47 private static final long serialVersionUID = 1L;
48
49
50
51
52 private static final String ASSIGN_VARIABLE_NAME = "a"+UUID.randomUUID().toString().replace('-','x');
53
54 public static final String SUPPORTED_DATA_MODEL = "groovy";
55
56 public static class GroovyEvaluatorProvider implements EvaluatorProvider {
57
58 @Override
59 public String getSupportedDatamodel() {
60 return SUPPORTED_DATA_MODEL;
61 }
62
63 @Override
64 public Evaluator getEvaluator() {
65 return new GroovyEvaluator();
66 }
67
68 @Override
69 public Evaluator getEvaluator(final SCXML document) {
70 return new GroovyEvaluator();
71 }
72 }
73
74
75 private static final String ERR_CTX_TYPE = "Error evaluating Groovy "
76 + "expression, Context must be a org.apache.commons.scxml2.env.groovy.GroovyContext";
77
78 protected static final GroovyExtendableScriptCache.ScriptPreProcessor scriptPreProcessor = new GroovyExtendableScriptCache.ScriptPreProcessor () {
79
80
81
82
83 public final Pattern GROOVY_OPERATOR_ALIASES_PATTERN = Pattern.compile("(?<=\\s)(and|or|not|eq|lt|le|ne|gt|ge)(?=\\s)");
84
85
86
87
88 public final Map<String, String> GROOVY_OPERATOR_ALIASES = Collections.unmodifiableMap(new HashMap<String, String>() {{
89 put("and", "&& "); put("or", "||"); put("not", " ! ");
90 put("eq", "=="); put("lt", "< "); put("le", "<=");
91 put("ne", "!="); put("gt", "> "); put("ge", ">=");
92 }});
93
94 @Override
95 public String preProcess(final String script) {
96 if (script == null || script.length() == 0) {
97 return script;
98 }
99 StringBuffer sb = null;
100 Matcher m = GROOVY_OPERATOR_ALIASES_PATTERN.matcher(script);
101 while (m.find()) {
102 if (sb == null) {
103 sb = new StringBuffer();
104 }
105 m.appendReplacement(sb, GROOVY_OPERATOR_ALIASES.get(m.group()));
106 }
107 if (sb != null) {
108 m.appendTail(sb);
109 return sb.toString();
110 }
111 return script;
112 }
113 };
114
115 private final boolean useInitialScriptAsBaseScript;
116 private final GroovyExtendableScriptCache scriptCache;
117
118 public GroovyEvaluator() {
119 this(false);
120 }
121
122 public GroovyEvaluator(boolean useInitialScriptAsBaseScript) {
123 this.useInitialScriptAsBaseScript = useInitialScriptAsBaseScript;
124 this.scriptCache = newScriptCache();
125 }
126
127
128
129
130
131
132
133
134 protected GroovyExtendableScriptCache newScriptCache() {
135 GroovyExtendableScriptCache scriptCache = new GroovyExtendableScriptCache();
136 scriptCache.setScriptPreProcessor(getScriptPreProcessor());
137 scriptCache.setScriptBaseClass(GroovySCXMLScript.class.getName());
138 return scriptCache;
139 }
140
141 @SuppressWarnings("unchecked")
142 protected Script getScript(GroovyContext groovyContext, String scriptBaseClassName, String scriptSource) {
143 Script script = scriptCache.getScript(scriptBaseClassName, scriptSource);
144 script.setBinding(groovyContext.getBinding());
145 return script;
146 }
147
148 @SuppressWarnings("unused")
149 public void clearCache() {
150 scriptCache.clearCache();
151 }
152
153 public GroovyExtendableScriptCache.ScriptPreProcessor getScriptPreProcessor() {
154 return scriptPreProcessor;
155 }
156
157
158
159
160 @Override
161 public String getSupportedDatamodel() {
162 return SUPPORTED_DATA_MODEL;
163 }
164
165
166
167
168
169
170
171
172
173
174 @Override
175 public Object eval(final Context ctx, final String expr) throws SCXMLExpressionException {
176 if (expr == null) {
177 return null;
178 }
179
180 if (!(ctx instanceof GroovyContext)) {
181 throw new SCXMLExpressionException(ERR_CTX_TYPE);
182 }
183
184 final GroovyContext groovyCtx = (GroovyContext) ctx;
185 if (groovyCtx.getGroovyEvaluator() == null) {
186 groovyCtx.setGroovyEvaluator(this);
187 }
188 try {
189 return getScript(getEffectiveContext(groovyCtx), groovyCtx.getScriptBaseClass(), expr).run();
190 }
191 catch (Exception e) {
192 String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName();
193 throw new SCXMLExpressionException("eval('" + expr + "'): " + exMessage, e);
194 }
195 }
196
197
198
199
200 @Override
201 public Boolean evalCond(final Context ctx, final String expr) throws SCXMLExpressionException {
202 if (expr == null) {
203 return null;
204 }
205
206 if (!(ctx instanceof GroovyContext)) {
207 throw new SCXMLExpressionException(ERR_CTX_TYPE);
208 }
209
210 final GroovyContext groovyCtx = (GroovyContext) ctx;
211 if (groovyCtx.getGroovyEvaluator() == null) {
212 groovyCtx.setGroovyEvaluator(this);
213 }
214 try {
215 final Object result = getScript(getEffectiveContext(groovyCtx), groovyCtx.getScriptBaseClass(), expr).run();
216 return result == null ? Boolean.FALSE : (Boolean)result;
217 } catch (Exception e) {
218 String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName();
219 throw new SCXMLExpressionException("evalCond('" + expr + "'): " + exMessage, e);
220 }
221 }
222
223
224
225
226 @Override
227 public Object evalLocation(final Context ctx, final String expr) throws SCXMLExpressionException {
228 if (expr == null) {
229 return null;
230 }
231 else if (ctx.has(expr)) {
232 return expr;
233 }
234
235 if (!(ctx instanceof GroovyContext)) {
236 throw new SCXMLExpressionException(ERR_CTX_TYPE);
237 }
238
239 GroovyContext groovyCtx = (GroovyContext) ctx;
240 if (groovyCtx.getGroovyEvaluator() == null) {
241 groovyCtx.setGroovyEvaluator(this);
242 }
243 try {
244 final GroovyContext effective = getEffectiveContext(groovyCtx);
245 return getScript(effective, groovyCtx.getScriptBaseClass(), expr).run();
246 } catch (Exception e) {
247 String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName();
248 throw new SCXMLExpressionException("evalLocation('" + expr + "'): " + exMessage, e);
249 }
250 }
251
252
253
254
255 public void evalAssign(final Context ctx, final String location, final Object data, final AssignType type,
256 final String attr) throws SCXMLExpressionException {
257
258 final Object loc = evalLocation(ctx, location);
259 if (loc != null) {
260
261 if (XPathBuiltin.isXPathLocation(ctx, loc)) {
262 XPathBuiltin.assign(ctx, loc, data, type, attr);
263 }
264 else {
265 final StringBuilder sb = new StringBuilder(location).append("=").append(ASSIGN_VARIABLE_NAME);
266 try {
267 ctx.getVars().put(ASSIGN_VARIABLE_NAME, data);
268 eval(ctx, sb.toString());
269 }
270 finally {
271 ctx.getVars().remove(ASSIGN_VARIABLE_NAME);
272 }
273 }
274 }
275 else {
276 throw new SCXMLExpressionException("evalAssign - cannot resolve location: '" + location + "'");
277 }
278 }
279
280
281
282
283 @Override
284 public Object evalScript(final Context ctx, final String scriptSource) throws SCXMLExpressionException {
285 if (scriptSource == null) {
286 return null;
287 }
288
289 if (!(ctx instanceof GroovyContext)) {
290 throw new SCXMLExpressionException(ERR_CTX_TYPE);
291 }
292
293 final GroovyContext groovyCtx = (GroovyContext) ctx;
294 if (groovyCtx.getGroovyEvaluator() == null) {
295 groovyCtx.setGroovyEvaluator(this);
296 }
297 try {
298 final GroovyContext effective = getEffectiveContext(groovyCtx);
299 final boolean inGlobalContext = groovyCtx.getParent() instanceof SCXMLSystemContext;
300 final Script script = getScript(effective, groovyCtx.getScriptBaseClass(), scriptSource);
301 final Object result = script.run();
302 if (inGlobalContext && useInitialScriptAsBaseScript) {
303 groovyCtx.setScriptBaseClass(script.getClass().getName());
304 }
305 return result;
306 } catch (Exception e) {
307 final String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName();
308 throw new SCXMLExpressionException("evalScript('" + scriptSource + "'): " + exMessage, e);
309 }
310 }
311
312 protected ClassLoader getGroovyClassLoader() {
313 return scriptCache.getGroovyClassLoader();
314 }
315
316
317
318
319
320
321
322
323 @Override
324 public Context newContext(final Context parent) {
325 return new GroovyContext(parent, this);
326 }
327
328
329
330
331
332
333
334
335
336
337 protected GroovyContext getEffectiveContext(final GroovyContext nodeCtx) {
338 return new GroovyContext(nodeCtx, new EffectiveContextMap(nodeCtx), this);
339 }
340 }