View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertNotNull;
21  import static org.junit.jupiter.api.Assertions.assertNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.math.BigDecimal;
26  import java.math.BigInteger;
27  import java.time.Duration;
28  import java.util.Collections;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.TreeMap;
32  import java.util.concurrent.ConcurrentHashMap;
33  import java.util.concurrent.ConcurrentMap;
34  import java.util.concurrent.atomic.AtomicInteger;
35  
36  import org.apache.commons.lang3.ThreadUtils;
37  import org.junit.jupiter.api.Test;
38  
39  /**
40   * Tests for pragmas
41   */
42  public class PragmaTest extends JexlTestCase {
43      public static class CachingModuleContext extends ModuleContext implements JexlContext.ModuleProcessor {
44          private final ConcurrentMap<String, Object> modules = new ConcurrentHashMap<>();
45          private final AtomicInteger count = new AtomicInteger();
46  
47          CachingModuleContext() {
48          }
49  
50          public int getCountCompute() {
51              return count.get();
52          }
53  
54          @Override
55          public Object processModule(final JexlEngine engine, final JexlInfo info, final String name, final String body) {
56              if (body.isEmpty()) {
57                  modules.remove(name);
58                  return null;
59              }
60              return modules.computeIfAbsent(name, n -> {
61                  Object module = engine.createExpression(info, body).evaluate(this);
62                  if (module instanceof JexlScript) {
63                      module = ((JexlScript) module).execute(this);
64                  }
65                  count.incrementAndGet();
66                  return module;
67              });
68          }
69      }
70  
71      public static class ModuleContext extends MapContext {
72          protected final Map<String, JexlScript> sources = new TreeMap<>();
73  
74          ModuleContext() {  }
75          public Object script(final String name) {
76              return sources.get(name);
77          }
78  
79          void script(final String name, final JexlScript script) { sources.put(name, script); }
80      }
81  
82      public static class SafeContext extends JexlEvalContext {
83          // @Override
84          public void processPragmas(final Map<String, Object> pragmas) {
85              if (pragmas != null && !pragmas.isEmpty()) {
86                  final JexlOptions options = getEngineOptions();
87                  for (final Map.Entry<String, Object> pragma : pragmas.entrySet()) {
88                      final String key = pragma.getKey();
89                      final Object value = pragma.getValue();
90                      if ("jexl.safe".equals(key) && value instanceof Boolean) {
91                          options.setSafe((Boolean) value);
92                      } else if ("jexl.strict".equals(key) && value instanceof Boolean) {
93                          options.setStrict((Boolean) value);
94                      } else if ("jexl.silent".equals(key) && value instanceof Boolean) {
95                          options.setSilent((Boolean) value);
96                      }
97                  }
98              }
99          }
100 
101         /**
102          * Sleeps, called through scripts.
103          * @param ms time to sleep in ms
104          */
105         public void sleep(final long ms) {
106             ThreadUtils.sleepQuietly(Duration.ofMillis(ms));
107         }
108     }
109 
110     public static class Sleeper {
111         public void sleep(final long ms) {
112             ThreadUtils.sleepQuietly(Duration.ofMillis(ms));
113         }
114     }
115 
116     public static class StaticSleeper {
117         public static void sleep(final long ms) {
118             ThreadUtils.sleepQuietly(Duration.ofMillis(ms));
119         }
120 
121         // precludes instantiation
122         private StaticSleeper() {}
123     }
124 
125     /**
126      * Create a new test case.
127      */
128     public PragmaTest() {
129         super("PragmaTest");
130     }
131 
132     void runPragmaModule(final ModuleContext ctxt, final CachingModuleContext cmCtxt) {
133         ctxt.script("module0", JEXL.createScript("function f42(x) { 42 + x; } function f43(x) { 43 + x; }; { 'f42' : f42, 'f43' : f43 }"));
134         final ConcurrentMap<String, Object> modules = new ConcurrentHashMap<>();
135         JexlScript script;
136         Object result;
137         script = JEXL.createScript("#pragma jexl.module.m0 \"script('module0')\"\n m0:f42(10);");
138         result = script.execute(ctxt);
139         assertEquals(52, result);
140         if (cmCtxt != null) {
141             assertEquals(1, cmCtxt.getCountCompute());
142         }
143         result = script.execute(ctxt);
144         assertEquals(52, result);
145         if (cmCtxt != null) {
146             assertEquals(1, cmCtxt.getCountCompute());
147         }
148         script = JEXL.createScript("#pragma jexl.module.m0 \"script('module0')\"\n m0:f43(10);");
149         result = script.execute(ctxt);
150         assertEquals(53, result);
151         if (cmCtxt != null) {
152             assertEquals(1, cmCtxt.getCountCompute());
153         }
154         final JexlScript script1 = JEXL.createScript("#pragma jexl.module.m0 ''\n#pragma jexl.module.m0 \"fubar('module0')\"\n m0:f43(10);");
155         final JexlException.Method xmethod = assertThrows(JexlException.Method.class, () -> script1.execute(ctxt));
156         assertEquals("fubar", xmethod.getMethod());
157     }
158 
159     @Test public void test354() {
160         final Map<String, Number> values = new TreeMap<>();
161         values.put("1", 1);
162         values.put("+1", 1);
163         values.put("-1", -1);
164         values.put("1l", 1L);
165         values.put("+1l", 1L);
166         values.put("-1l", -1L);
167         values.put("10h", BigInteger.valueOf(10));
168         values.put("-11h", BigInteger.valueOf(-11));
169         values.put("+12h", BigInteger.valueOf(12));
170         values.put("0xa", 0xa);
171         values.put("+0xa", 0xa);
172         values.put("-0xa", -0xa);
173         values.put("0xacl", 0xacL);
174         values.put("+0xadl", 0xadL);
175         values.put("-0xafl", -0xafL);
176         values.put("1d", 1d);
177         values.put("-1d", -1d);
178         values.put("+1d", 1d);
179         values.put("1f", 1f);
180         values.put("-1f", -1f);
181         values.put("+1f", 1f);
182         values.put("1B", new BigDecimal(1));
183         values.put("-1B", new BigDecimal(-1));
184         values.put("+1B", new BigDecimal(1));
185         values.put("-42424242424242424242424242424242", new BigInteger("-42424242424242424242424242424242"));
186         values.put("+42424242424242424242424242424242", new BigInteger("+42424242424242424242424242424242"));
187         values.put("42424242424242424242424242424242", new BigInteger("42424242424242424242424242424242"));
188         final JexlEngine jexl = new JexlBuilder().safe(true).create();
189         for(final Map.Entry<String, Number> e : values.entrySet()) {
190             final String text = "#pragma number " + e.getKey();
191             final JexlScript script = jexl.createScript(text);
192             assertNotNull(script);
193             final Map<String, Object> pragmas = script.getPragmas();
194             assertNotNull(pragmas);
195             assertEquals(e.getValue(), pragmas.get("number"), e::getKey);
196         }
197     }
198     @Test
199     public void testImportPragmaDisabled() {
200         final String src =
201                 "#pragma jexl.import java.util\n"+
202                         "#pragma jexl.import java.io\n"+
203                         "#pragma jexl.import java.net\n"+
204                         "42";
205         final JexlFeatures features = new JexlFeatures();
206         features.importPragma(false);
207         final JexlEngine jexl = new JexlBuilder().features(features).create();
208         final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript(src));
209         assertTrue(xparse.getMessage().contains("import pragma"));
210     }
211 
212     @Test
213     public void testImportPragmaValueSet() {
214         final String src =
215                 "#pragma jexl.import java.util\n"+
216                 "#pragma jexl.import java.io\n"+
217                 "#pragma jexl.import java.net\n"+
218                 "42";
219         final JexlScript script = JEXL.createScript(src);
220         final Map<String, Object> pragmas = script.getPragmas();
221         final Object importz = pragmas.get("jexl.import");
222         assertTrue(importz instanceof Set<?>);
223         final Set<String> importzz = (Set<String>) importz;
224         assertTrue(importzz.contains("java.util"));
225         assertTrue(importzz.contains("java.io"));
226         assertTrue(importzz.contains("java.net"));
227         assertEquals(3, importzz.size());
228         final String parsed = script.getParsedText();
229         assertEquals(src, parsed);
230     }
231 
232     @Test
233     public void testIssue416() {
234         final JexlEngine jexl = new JexlBuilder().create();
235         final JexlScript script = jexl.createScript("#pragma myNull null\n");
236         final Map<String, Object> pragmas = script.getPragmas();
237         assertTrue(pragmas.containsKey("myNull"), "pragma key present?");
238         assertNull(pragmas.get("myNull"), "expected null value");
239     }
240 
241     @Test
242     @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
243     public void testJxltPragmas() {
244         final JxltEngine engine = new JexlBuilder().create().createJxltEngine();
245         final JxltEngine.Template tscript = engine.createTemplate("$$ #pragma one 1\n$$ #pragma the.very.hard 'truth'\n2;");
246         assertNotNull(tscript);
247         final Map<String, Object> pragmas = tscript.getPragmas();
248         assertEquals(2, pragmas.size());
249         assertEquals(1, pragmas.get("one"));
250         assertEquals("truth", pragmas.get("the.very.hard"));
251     }
252 
253     @Test
254     @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
255     public void testNamespacePragma() {
256         final JexlContext jc = new SafeContext();
257         final String src =
258                 "#pragma jexl.namespace.sleeper " + Sleeper.class.getName() + "\n"
259                         + "sleeper:sleep(100);\n"
260                         + "42;\n";
261         final JexlScript script = JEXL.createScript(src);
262         final Object result = script.execute(jc);
263         assertEquals(42, result);
264         final String parsed = script.getParsedText();
265         assertEquals(src, parsed);
266     }
267     @Test
268     @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
269     public void testNamespacePragmaCtl() {
270         final Map<String, Object> ns = Collections.singletonMap("sleeper", Sleeper.class.getName());
271         final JexlEngine jexl = new JexlBuilder().namespaces(ns).create();
272         final JexlContext jc = new SafeContext();
273         final JexlScript script = jexl.createScript(
274                 "sleeper:sleep(100);"
275                 + "42");
276         final Object result = script.execute(jc);
277         assertEquals(42, result);
278     }
279     @Test
280     public void testNamespacePragmaDisabled() {
281         final JexlFeatures features = new JexlFeatures();
282         features.namespacePragma(false);
283         final JexlEngine jexl = new JexlBuilder().features(features).create();
284         // @formatter:off
285         final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript(
286                 "#pragma jexl.namespace.sleeper " + StaticSleeper.class.getName() + "\n"
287                         + "sleeper:sleep(100);"
288                         + "42"));
289         // @formatter:on
290         assertTrue(xparse.getMessage().contains("namespace pragma"));
291     }
292 
293     @Test public void testPragmaModuleCache() {
294         final CachingModuleContext ctxt = new CachingModuleContext();
295         runPragmaModule(ctxt, ctxt);
296     }
297 
298     @Test public void testPragmaModuleNoCache() {
299         final ModuleContext ctxt = new ModuleContext();
300         runPragmaModule(ctxt, null);
301     }
302 
303     @Test
304     public void testPragmaOptions1() {
305         final String str = "i; #pragma jexl.options '-strict'\n";
306         // @formatter:off
307         final JexlEngine jexl = new JexlBuilder()
308                 .features(new JexlFeatures().pragmaAnywhere(false))
309                 .strict(true).create();
310         // @formatter:on
311         assertThrows(JexlException.class, () -> jexl.createScript(str), "i should not be resolved");
312     }
313 
314     /**
315      * Test creating a script from a string.
316      */
317     @Test
318     @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
319     public void testPragmas() {
320         final JexlScript script = JEXL.createScript("#pragma one 1\n#pragma the.very.hard 'truth'\n2;");
321         assertNotNull(script);
322         final Map<String, Object> pragmas = script.getPragmas();
323         assertEquals(2, pragmas.size());
324         assertEquals(1, pragmas.get("one"));
325         assertEquals("truth", pragmas.get("the.very.hard"));
326     }
327 
328     @Test
329     @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
330     public void testSafePragma() {
331         final SafeContext jc = new SafeContext();
332         jc.set("foo", null);
333         final JexlScript script = JEXL.createScript("#pragma jexl.safe true\nfoo.bar;");
334         assertNotNull(script);
335         jc.processPragmas(script.getPragmas());
336         final Object result = script.execute(jc);
337         assertNull(result);
338         final SafeContext jc1 = new SafeContext();
339         jc1.set("foo", null);
340         assertThrows(JexlException.class, () -> script.execute(jc1));
341     }
342 
343     @Test
344     @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
345     public void testStaticNamespacePragma() {
346         final JexlContext jc = new SafeContext();
347         final JexlScript script = JEXL.createScript(
348                 "#pragma jexl.namespace.sleeper " + StaticSleeper.class.getName() + "\n"
349                 + "sleeper:sleep(100);"
350                 + "42");
351         final Object result = script.execute(jc);
352         assertEquals(42, result);
353     }
354 
355     @Test
356     @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
357     public void testStatictNamespacePragmaCtl() {
358         final Map<String, Object> ns = Collections.singletonMap("sleeper", StaticSleeper.class.getName());
359         final JexlEngine jexl = new JexlBuilder().namespaces(ns).create();
360         final JexlContext jc = new SafeContext();
361         final JexlScript script = jexl.createScript(
362                 "sleeper:sleep(100);"
363                 + "42");
364         final Object result = script.execute(jc);
365         assertEquals(42, result);
366     }
367 }