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