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.assertThrows;
21  
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.Map;
25  import java.util.concurrent.atomic.AtomicInteger;
26  
27  import org.junit.jupiter.api.Test;
28  
29  /**
30   * Tests JexlContext (advanced) features.
31   */
32  @SuppressWarnings({"AssertEqualsBetweenInconvertibleTypes"})
33  class ContextNamespaceTest extends JexlTestCase {
34  
35      public static class Context346 extends MapContext {
36          public int func(final int y) { return 42 * y;}
37      }
38  
39      public static class ContextNs348 extends MapContext implements JexlContext.NamespaceResolver {
40          ContextNs348() { }
41  
42          @Override
43          public Object resolveNamespace(final String name) {
44              return "ns".equals(name)? new Ns348() : null;
45          }
46      }
47  
48      public static class Ns348 {
49          public static int func(final int y) { return 42 * y;}
50      }
51  
52      public static class NsNs {
53          private final int constVar;
54          public NsNs(final JexlContext ctxt) {
55              nsnsCtor.incrementAndGet();
56              final Object n = ctxt.get("NUMBER");
57              constVar = n instanceof Number ? ((Number) n).intValue() : -1;
58          }
59  
60          public int callIt(final int n) {
61              return n + constVar;
62          }
63      }
64  
65      public static class StaticNs {
66          public static int callIt(final int n) {
67              return n + 19;
68          }
69          private StaticNs() { }
70      }
71  
72      /*
73       * Accesses the thread context and cast it.
74       */
75      public static class Taxes {
76          private final double vat;
77  
78          public Taxes(final double d) {
79              vat = d;
80          }
81  
82          public Taxes(final TaxesContext ctxt) {
83              vat = ctxt.getVAT();
84          }
85  
86          public double vat(final double n) {
87              return n * vat / 100.;
88          }
89      }
90  
91      /**
92       * A thread local context carrying a namespace and some inner constants.
93       */
94      public static class TaxesContext extends MapContext implements JexlContext.ThreadLocal, JexlContext.NamespaceResolver {
95          private Taxes taxes;
96          private final double vat;
97  
98          TaxesContext(final double vat) {
99              this.vat = vat;
100         }
101 
102         public double getVAT() {
103             return vat;
104         }
105 
106         @Override
107         public Object resolveNamespace(final String name) {
108             if ("taxes".equals(name)) {
109                 if (taxes == null) {
110                     taxes = new Taxes(vat);
111                 }
112                 return taxes;
113             }
114             return null;
115         }
116     }
117 
118     public static class Vat {
119         private double vat;
120 
121         Vat(final double vat) {
122             this.vat = vat;
123         }
124 
125         public double getvat() {
126             throw new UnsupportedOperationException("no way");
127         }
128 
129         public double getVAT() {
130             return vat;
131         }
132 
133         public void setvat(final double vat) {
134             throw new UnsupportedOperationException("no way");
135         }
136 
137         public void setVAT(final double vat) {
138             this.vat = vat;
139         }
140     }
141 
142     static AtomicInteger nsnsCtor = new AtomicInteger();
143 
144     public ContextNamespaceTest() {
145         super("ContextNamespaceTest");
146     }
147 
148     private void run348a(final JexlEngine jexl, final JexlContext ctxt) {
149         run348a(jexl, ctxt, "ns : ");
150     }
151 
152     private void run348a(final JexlEngine jexl, final JexlContext ctxt, final String ns) {
153         final String src = "empty(x) ? "+ns+"func(y) : z";
154         // local vars
155         final JexlScript script = jexl.createScript(src, "x", "y", "z");
156         Object result = script.execute(ctxt, null, 1, 169);
157         assertEquals(42, result);
158         result = script.execute(ctxt, "42", 1, 169);
159         assertEquals(169, result);
160     }
161 
162     private void run348b(final JexlEngine jexl, final JexlContext ctxt) {
163         run348b(jexl, ctxt, "ns : ");
164     }
165 
166     private void run348b(final JexlEngine jexl, final JexlContext ctxt, final String ns) {
167         final String src = "empty(x) ? "+ns+"func(y) : z";
168         // global vars
169         final JexlScript script = jexl.createScript(src);
170         ctxt.set("x", null);
171         ctxt.set("y", 1);
172         ctxt.set("z", 169);
173         Object result = script.execute(ctxt);
174         assertEquals(42, result);
175         ctxt.set("x", "42");
176         result = script.execute(ctxt);
177         assertEquals(169, result);
178         //ctxt.set("x", "42");
179         result = script.execute(ctxt);
180         assertEquals(169, result);
181     }
182 
183     private void run348c(final JexlEngine jexl, final JexlContext ctxt) {
184         run348c(jexl, ctxt, "ns : ");
185     }
186     private void run348c(final JexlEngine jexl, final JexlContext ctxt, final String ns) {
187         final String src = "empty(x) ? z : "+ns+"func(y)";
188         // local vars
189         final JexlScript script = jexl.createScript(src, "x", "z", "y");
190         Object result = script.execute(ctxt, null, 169, 1);
191         assertEquals(169, result, src);
192         result = script.execute(ctxt, "42", 169, 1);
193         assertEquals(42, result, src);
194     }
195 
196     private void run348d(final JexlEngine jexl, final JexlContext ctxt) {
197         run348d(jexl, ctxt, "ns : ");
198     }
199     private void run348d(final JexlEngine jexl, final JexlContext ctxt, final String ns) {
200         final String src = "empty(x) ? z : "+ns+"func(y)";
201         // global vars
202         final JexlScript script = jexl.createScript(src);
203 
204         ctxt.set("x", null);
205         ctxt.set("z", 169);
206         ctxt.set("y", 1);
207         Object result = script.execute(ctxt);
208         assertEquals(169, result, src);
209         ctxt.set("x", "42");
210         result = script.execute(ctxt);
211         assertEquals(42, result, src);
212     }
213 
214     private void runNsNsContext(final Map<String,Object> nsMap) {
215         final JexlContext ctxt = new MapContext();
216         ctxt.set("NUMBER", 19);
217         final JexlEngine jexl = new JexlBuilder().strict(true).silent(false).cache(32)
218                 .namespaces(nsMap).create();
219         final JexlScript script = jexl.createScript("x ->{ nsns:callIt(x); nsns:callIt(x); }");
220         Number result = (Number) script.execute(ctxt, 23);
221         assertEquals(42, result);
222         assertEquals(1, nsnsCtor.get());
223         result = (Number) script.execute(ctxt, 623);
224         assertEquals(642, result);
225         assertEquals(2, nsnsCtor.get());
226     }
227     private void runStaticNsContext(final Map<String,Object> nsMap) {
228         final JexlContext ctxt = new MapContext();
229         final JexlEngine jexl = new JexlBuilder().strict(true).silent(false).cache(32)
230                 .namespaces(nsMap).create();
231         final JexlScript script = jexl.createScript("x ->{ sns:callIt(x); sns:callIt(x); }");
232         Number result = (Number) script.execute(ctxt, 23);
233         assertEquals(42, result);
234         result = (Number) script.execute(ctxt, 623);
235         assertEquals(642, result);
236     }
237 
238     @Test
239     void testNamespace346a() {
240         final JexlContext ctxt = new Context346();
241         final JexlEngine jexl = new JexlBuilder().safe(false).create();
242         final String src = "x != null ? x : func(y)";
243         final JexlScript script = jexl.createScript(src,"x","y");
244         Object result = script.execute(ctxt, null, 1);
245         assertEquals(42, result);
246         result = script.execute(ctxt, 169, -169);
247         assertEquals(169, result);
248     }
249     @Test
250     void testNamespace346b() {
251         final JexlContext ctxt = new MapContext();
252         final Map<String, Object> ns = new HashMap<>();
253         ns.put("x", Math.class);
254         ns.put(null, Math.class);
255         final JexlEngine jexl = new JexlBuilder().safe(false).namespaces(ns).create();
256         final String src = "x != null ? x : abs(y)";
257         final JexlScript script = jexl.createScript(src,"x","y");
258         Object result = script.execute(ctxt, null, 42);
259         assertEquals(42, result);
260         result = script.execute(ctxt, 169, -169);
261         assertEquals(169, result);
262     }
263 
264     @Test
265     void testNamespace348a() {
266         final JexlContext ctxt = new MapContext();
267         final Map<String, Object> ns = new HashMap<>();
268         ns.put("ns", Ns348.class);
269         final JexlEngine jexl = new JexlBuilder().safe(false).namespaces(ns).create();
270         run348a(jexl, ctxt);
271         run348b(jexl, ctxt);
272         run348c(jexl, ctxt);
273         run348d(jexl, ctxt);
274     }
275 
276     @Test
277     void testNamespace348b() {
278         final JexlContext ctxt = new ContextNs348();
279         final JexlEngine jexl = new JexlBuilder().safe(false).create();
280         // no space for ns name as syntactic hint
281         run348a(jexl, ctxt, "ns:");
282         run348b(jexl, ctxt, "ns:");
283         run348c(jexl, ctxt, "ns:");
284         run348d(jexl, ctxt, "ns:");
285     }
286 
287     @Test
288     void testNamespace348c() {
289         final JexlContext ctxt = new ContextNs348();
290         final Map<String, Object> ns = new HashMap<>();
291         ns.put("ns", Ns348.class);
292         final JexlFeatures f = new JexlFeatures();
293         f.namespaceTest(n -> true);
294         final JexlEngine jexl = new JexlBuilder().namespaces(ns).features(f).safe(false).create();
295         run348a(jexl, ctxt);
296         run348b(jexl, ctxt);
297         run348c(jexl, ctxt);
298         run348d(jexl, ctxt);
299     }
300 
301     @Test
302     void testNamespace348d() {
303         final JexlContext ctxt = new ContextNs348();
304         final JexlFeatures f = new JexlFeatures();
305         f.namespaceTest(n -> true);
306         final JexlEngine jexl = new JexlBuilder().features(f).safe(false).create();
307         run348a(jexl, ctxt);
308         run348b(jexl, ctxt);
309         run348c(jexl, ctxt);
310         run348d(jexl, ctxt);
311     }
312 
313     @Test
314     void testNamespacePragma() {
315         final JexlEngine jexl = new JexlBuilder().create();
316         final JexlContext context = new TaxesContext(18.6);
317         // local namespace tax declared
318         final String strs =
319                   "#pragma jexl.namespace.tax org.apache.commons.jexl3.ContextNamespaceTest$Taxes\n"
320                 + "tax:vat(2000)";
321         final JexlScript staxes = jexl.createScript(strs);
322         final Object result = staxes.execute(context);
323         assertEquals(372., result);
324     }
325 
326     @Test
327     void testNamespacePragmaString() {
328         final JexlEngine jexl = new JexlBuilder().create();
329         final JexlContext context = new MapContext();
330         // local namespace str declared
331         final String strs =
332                   "#pragma jexl.namespace.str java.lang.String\n"
333                 + "str:format('%04d', 42)";
334         final JexlScript staxes = jexl.createScript(strs);
335         final Object result = staxes.execute(context);
336         assertEquals("0042", result);
337     }
338 
339     @Test
340     void testNsNsContext0() {
341         nsnsCtor.set(0);
342         final String clsName = NsNs.class.getName();
343         runNsNsContext(Collections.singletonMap("nsns", clsName));
344     }
345 
346     @Test
347     void testNsNsContext1() {
348         nsnsCtor.set(0);
349         runNsNsContext(Collections.singletonMap("nsns", NsNs.class));
350     }
351 
352     @Test
353     void testObjectContext() {
354         final JexlEngine jexl = new JexlBuilder().strict(true).silent(false).create();
355         final Vat vat = new Vat(18.6);
356         final ObjectContext<Vat> ctxt = new ObjectContext<>(jexl, vat);
357         assertEquals(18.6d, (Double) ctxt.get("VAT"), 0.0001d);
358         ctxt.set("VAT", 20.0d);
359         assertEquals(20.0d, (Double) ctxt.get("VAT"), 0.0001d);
360         assertThrows(JexlException.Property.class, () -> ctxt.get("vat"));
361         assertThrows(JexlException.Property.class, () -> ctxt.set("vat", 33.0d));
362     }
363 
364     @Test
365     void testStaticNs0() {
366         runStaticNsContext(Collections.singletonMap("sns", StaticNs.class));
367     }
368 
369     @Test
370     void testStaticNs1() {
371         runStaticNsContext(Collections.singletonMap("sns", StaticNs.class.getName()));
372     }
373 
374     @Test
375     void testThreadedContext() {
376         final JexlEngine jexl = new JexlBuilder().create();
377         final JexlContext context = new TaxesContext(18.6);
378         final String strs = "taxes:vat(1000)";
379         final JexlScript staxes = jexl.createScript(strs);
380         final Object result = staxes.execute(context);
381         assertEquals(186., result);
382     }
383 }