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