1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.jexl3.scripting;
19
20 import java.io.BufferedReader;
21 import java.io.IOException;
22 import java.io.PrintWriter;
23 import java.io.Reader;
24 import java.io.Writer;
25 import java.lang.ref.Reference;
26 import java.lang.ref.SoftReference;
27
28 import javax.script.AbstractScriptEngine;
29 import javax.script.Bindings;
30 import javax.script.Compilable;
31 import javax.script.CompiledScript;
32 import javax.script.ScriptContext;
33 import javax.script.ScriptEngine;
34 import javax.script.ScriptEngineFactory;
35 import javax.script.ScriptException;
36 import javax.script.SimpleBindings;
37
38 import org.apache.commons.jexl3.JexlBuilder;
39 import org.apache.commons.jexl3.JexlContext;
40 import org.apache.commons.jexl3.JexlEngine;
41 import org.apache.commons.jexl3.JexlException;
42 import org.apache.commons.jexl3.JexlScript;
43
44 import org.apache.commons.jexl3.introspection.JexlPermissions;
45 import org.apache.commons.logging.Log;
46 import org.apache.commons.logging.LogFactory;
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 public class JexlScriptEngine extends AbstractScriptEngine implements Compilable {
69
70
71
72
73 private static Reference<JexlEngine> ENGINE = null;
74
75
76
77
78 private static JexlPermissions PERMISSIONS = null;
79
80
81
82
83
84
85
86
87
88
89 public static void setPermissions(final JexlPermissions permissions) {
90 PERMISSIONS = permissions;
91 ENGINE = null;
92 }
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109 public static void setInstance(final JexlEngine engine) {
110 ENGINE = new SoftReference<>(engine);
111 }
112
113
114
115
116 private static JexlEngine getEngine() {
117 JexlEngine engine = ENGINE != null? ENGINE.get() : null;
118 if (engine == null) {
119 synchronized (JexlScriptEngineFactory.class) {
120 engine = ENGINE != null? ENGINE.get() : null;
121 if (engine == null) {
122 final JexlBuilder builder = new JexlBuilder()
123 .strict(true)
124 .safe(false)
125 .logger(JexlScriptEngine.LOG)
126 .cache(JexlScriptEngine.CACHE_SIZE);
127 if (PERMISSIONS != null ) {
128 builder.permissions(PERMISSIONS);
129 }
130 engine = builder.create();
131 ENGINE = new SoftReference<>(engine);
132 }
133 }
134 }
135 return engine;
136 }
137
138
139
140 static final Log LOG = LogFactory.getLog(JexlScriptEngine.class);
141
142
143 static final int CACHE_SIZE = 512;
144
145
146 public static final String CONTEXT_KEY = "context";
147
148
149 public static final String JEXL_OBJECT_KEY = "JEXL";
150
151
152 final JexlScriptObject jexlObject;
153
154
155 final ScriptEngineFactory parentFactory;
156
157
158 final JexlEngine jexlEngine;
159
160
161
162
163
164
165
166 public JexlScriptEngine() {
167 this(FactorySingletonHolder.DEFAULT_FACTORY);
168 }
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186 public class JexlScriptObject {
187
188
189
190
191
192
193
194
195
196 public JexlEngine getEngine() {
197 return jexlEngine;
198 }
199
200
201
202
203
204
205 public PrintWriter getOut() {
206 final Writer out = context.getWriter();
207 if (out instanceof PrintWriter) {
208 return (PrintWriter) out;
209 }
210 if (out != null) {
211 return new PrintWriter(out, true);
212 }
213 return null;
214 }
215
216
217
218
219
220
221 public PrintWriter getErr() {
222 final Writer error = context.getErrorWriter();
223 if (error instanceof PrintWriter) {
224 return (PrintWriter) error;
225 }
226 if (error != null) {
227 return new PrintWriter(error, true);
228 }
229 return null;
230 }
231
232
233
234
235
236
237 public Reader getIn() {
238 return context.getReader();
239 }
240
241
242
243
244
245
246 public Class<System> getSystem() {
247 return System.class;
248 }
249
250
251
252
253
254
255 public Log getLogger() {
256 return LOG;
257 }
258 }
259
260
261
262
263
264
265
266
267 public JexlScriptEngine(final ScriptEngineFactory factory) {
268 if (factory == null) {
269 throw new NullPointerException("ScriptEngineFactory must not be null");
270 }
271 parentFactory = factory;
272 jexlEngine = getEngine();
273 jexlObject = new JexlScriptObject();
274 }
275
276 @Override
277 public Bindings createBindings() {
278 return new SimpleBindings();
279 }
280
281 @Override
282 public Object eval(final Reader reader, final ScriptContext context) throws ScriptException {
283
284 if (reader == null || context == null) {
285 throw new NullPointerException("script and context must be non-null");
286 }
287 return eval(readerToString(reader), context);
288 }
289
290 @Override
291 public Object eval(final String script, final ScriptContext context) throws ScriptException {
292
293 if (script == null || context == null) {
294 throw new NullPointerException("script and context must be non-null");
295 }
296
297 context.setAttribute(CONTEXT_KEY, context, ScriptContext.ENGINE_SCOPE);
298 try {
299 final JexlScript jexlScript = jexlEngine.createScript(script);
300 final JexlContext ctxt = new JexlContextWrapper(context);
301 return jexlScript.execute(ctxt);
302 } catch (final Exception e) {
303 throw scriptException(e);
304 }
305 }
306
307 @Override
308 public ScriptEngineFactory getFactory() {
309 return parentFactory;
310 }
311
312 @Override
313 public CompiledScript compile(final String script) throws ScriptException {
314
315 if (script == null) {
316 throw new NullPointerException("script must be non-null");
317 }
318 try {
319 final JexlScript jexlScript = jexlEngine.createScript(script);
320 return new JexlCompiledScript(jexlScript);
321 } catch (final Exception e) {
322 throw scriptException(e);
323 }
324 }
325
326 static ScriptException scriptException(final Exception e) {
327 Exception xany = e;
328
329 if (xany instanceof JexlException) {
330 final Throwable cause = xany.getCause();
331 if (cause instanceof Exception) {
332 xany = (Exception) cause;
333 }
334 }
335 return new ScriptException(xany);
336 }
337
338 @Override
339 public CompiledScript compile(final Reader script) throws ScriptException {
340
341 if (script == null) {
342 throw new NullPointerException("script must be non-null");
343 }
344 return compile(readerToString(script));
345 }
346
347
348
349
350
351
352
353
354
355 private static String readerToString(final Reader scriptReader) throws ScriptException {
356 final StringBuilder buffer = new StringBuilder();
357 BufferedReader reader;
358 if (scriptReader instanceof BufferedReader) {
359 reader = (BufferedReader) scriptReader;
360 } else {
361 reader = new BufferedReader(scriptReader);
362 }
363 try {
364 String line;
365 while ((line = reader.readLine()) != null) {
366 buffer.append(line).append('\n');
367 }
368 return buffer.toString();
369 } catch (final IOException e) {
370 throw new ScriptException(e);
371 }
372 }
373
374
375
376
377 private static class FactorySingletonHolder {
378
379 private FactorySingletonHolder() {}
380
381
382 private static final JexlScriptEngineFactory DEFAULT_FACTORY = new JexlScriptEngineFactory();
383 }
384
385
386
387
388
389
390 private final class JexlContextWrapper implements JexlContext {
391
392 final ScriptContext scriptContext;
393
394
395
396
397
398
399 JexlContextWrapper (final ScriptContext theContext){
400 scriptContext = theContext;
401 }
402
403 @Override
404 public Object get(final String name) {
405 final Object o = scriptContext.getAttribute(name);
406 if (JEXL_OBJECT_KEY.equals(name)) {
407 if (o != null) {
408 LOG.warn("JEXL is a reserved variable name, user defined value is ignored");
409 }
410 return jexlObject;
411 }
412 return o;
413 }
414
415 @Override
416 public void set(final String name, final Object value) {
417 int scope = scriptContext.getAttributesScope(name);
418 if (scope == -1) {
419 scope = ScriptContext.ENGINE_SCOPE;
420 }
421 scriptContext.getBindings(scope).put(name , value);
422 }
423
424 @Override
425 public boolean has(final String name) {
426 final Bindings bnd = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
427 return bnd.containsKey(name);
428 }
429
430 }
431
432
433
434
435 private final class JexlCompiledScript extends CompiledScript {
436
437 private final JexlScript script;
438
439
440
441
442
443
444 JexlCompiledScript(final JexlScript theScript) {
445 script = theScript;
446 }
447
448 @Override
449 public String toString() {
450 return script.getSourceText();
451 }
452
453 @Override
454 public Object eval(final ScriptContext context) throws ScriptException {
455
456 context.setAttribute(CONTEXT_KEY, context, ScriptContext.ENGINE_SCOPE);
457 try {
458 final JexlContext ctxt = new JexlContextWrapper(context);
459 return script.execute(ctxt);
460 } catch (final Exception e) {
461 throw scriptException(e);
462 }
463 }
464
465 @Override
466 public ScriptEngine getEngine() {
467 return JexlScriptEngine.this;
468 }
469 }
470 }