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.assertNull;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  
23  import java.math.BigInteger;
24  import java.util.Map;
25  import java.util.TreeMap;
26  
27  import org.junit.jupiter.api.Test;
28  
29  /**
30   * Test cases for calling antish variables as method names (JEXL-240);
31   * Also tests that a class instance is a functor that invokes the constructor when called.
32   */
33  @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
34  class AntishCallTest extends JexlTestCase {
35  
36      /**
37       * An arithmetic that considers class objects as callable.
38       */
39      public class CallSupportArithmetic extends JexlArithmetic {
40          public CallSupportArithmetic(final boolean strict) {
41              super(strict);
42          }
43  
44          public Object call(final Class<?> clazz, final Object... args) {
45              return callConstructor(null, clazz, args);
46          }
47  
48          public Object call(final ClassReference clazz, final Object... args) {
49              return callConstructor(null, clazz, args);
50          }
51      }
52  
53      /**
54       * A context that considers class references as callable.
55       */
56      public static class CallSupportContext extends MapContext {
57          private JexlEngine engine;
58          CallSupportContext(final Map<String, Object> map) {
59              super(map);
60          }
61  
62          public Object call(final Class<?> clazz, final Object... args) {
63              return callConstructor(engine, clazz, args);
64          }
65  
66          public Object call(final ClassReference clazz, final Object... args) {
67              return callConstructor(engine, clazz, args);
68          }
69  
70          CallSupportContext engine(final JexlEngine j) {
71              engine = j;
72              return this;
73          }
74  
75          @Override public Object get(final String str) {
76              if (!super.has(str)) {
77                  try {
78                      return CallSupportContext.class.getClassLoader().loadClass(str);
79                  } catch (final Exception xany) {
80                      return null;
81                  }
82              }
83              return super.get(str);
84          }
85  
86          @Override public boolean has(final String str) {
87              if (!super.has(str)){
88                  try {
89                      return CallSupportContext.class.getClassLoader().loadClass(str) != null;
90                  } catch (final Exception xany) {
91                      return false;
92                  }
93              }
94              return true;
95          }
96      }
97  
98      /**
99       * Wraps a class.
100      */
101     public static class ClassReference {
102         final Class<?> clazz;
103         ClassReference(final Class<?> c) {
104             this.clazz = c;
105         }
106     }
107     public static Object callConstructor(final JexlEngine engine, final Class<?> clazz, final Object... args) {
108         if (clazz == null || clazz.isPrimitive() || clazz.isInterface()
109             || clazz.isMemberClass() || clazz.isAnnotation() || clazz.isArray()) {
110             throw new ArithmeticException("not a constructible object");
111         }
112         JexlEngine jexl = engine;
113         if (jexl == null) {
114             jexl = JexlEngine.getThreadEngine();
115             if (jexl == null) {
116                 throw new ArithmeticException("no engine to solve constructor");
117             }
118         }
119         return jexl.newInstance(clazz, args);
120     }
121 
122     /**
123      * Considers any call using a class reference as functor as a call to its constructor.
124      * <p>Note that before 3.2, a class was not considered a functor.
125      * @param ref The ClassReference of the class we seek to instantiate
126      * @param args The constructor arguments
127      * @return An instance if that was possible
128      */
129     public static Object callConstructor(final JexlEngine engine, final ClassReference ref, final Object... args) {
130         return callConstructor(engine, ref.clazz, args);
131     }
132 
133     public AntishCallTest() {
134         super("AntishCallTest");
135     }
136 
137     void runTestCall(final JexlEngine jexl, final JexlContext jc) throws Exception {
138         final JexlScript check1 = jexl.createScript("var x = java.math.BigInteger; x('1234')");
139         final JexlScript check2 = jexl.createScript("java.math.BigInteger('4321')");
140 
141         final Object o1 = check1.execute(jc);
142         assertEquals(new java.math.BigInteger("1234"), o1, "Result is not 1234");
143 
144         final Object o2 = check2.execute(jc);
145         assertEquals(new java.math.BigInteger("4321"), o2, "Result is not 4321");
146     }
147 
148     @Test
149     void testAntishArithmetic() throws Exception {
150         final CallSupportArithmetic ja = new CallSupportArithmetic(true);
151         final JexlEngine jexl = new JexlBuilder().cache(512).arithmetic(ja).create();
152         final Map<String, Object> lmap = new TreeMap<>();
153         final JexlContext jc = new MapContext(lmap);
154         lmap.put("java.math.BigInteger", java.math.BigInteger.class);
155         runTestCall(jexl, jc);
156         lmap.put("java.math.BigInteger", new ClassReference(BigInteger.class));
157         runTestCall(jexl, jc);
158         lmap.remove("java.math.BigInteger");
159         assertThrows(JexlException.class, () -> runTestCall(jexl, jc));
160     }
161 
162     @Test
163     void testAntishContextVar() throws Exception {
164         final Map<String,Object> lmap = new TreeMap<>();
165         final JexlContext jc = new CallSupportContext(lmap).engine(JEXL);
166         runTestCall(JEXL, jc);
167         lmap.put("java.math.BigInteger", new ClassReference(BigInteger.class));
168         runTestCall(JEXL, jc);
169         lmap.remove("java.math.BigInteger");
170         runTestCall(JEXL, jc);
171     }
172 
173     // JEXL-300
174     @Test
175     void testSafeAnt() throws Exception {
176         final JexlEvalContext ctxt = new JexlEvalContext();
177         final JexlOptions options = ctxt.getEngineOptions();
178         ctxt.set("x.y.z", 42);
179         JexlScript script;
180         Object result;
181 
182         final JexlScript script0 = JEXL.createScript("x.y.z");
183         result = script0.execute(ctxt);
184         assertEquals(42, result);
185         assertEquals(42, ctxt.get("x.y.z"));
186 
187         options.setAntish(false);
188         assertThrows(JexlException.class, () -> script0.execute(ctxt), "antish var shall not be resolved");
189         options.setAntish(true);
190 
191         script = JEXL.createScript("x?.y?.z");
192         result = script.execute(ctxt);
193         assertNull(result); // safe navigation, null
194 
195         final JexlScript script1 = JEXL.createScript("x?.y?.z = 3");
196         assertThrows(JexlException.class, () -> script1.execute(ctxt), "not antish assign");
197 
198         final JexlScript script2 = JEXL.createScript("x.y?.z");
199         assertThrows(JexlException.class, () -> script2.execute(ctxt), "x not defined");
200 
201         final JexlScript script3 = JEXL.createScript("x.y?.z = 3");
202         assertThrows(JexlException.class, () -> script3.execute(ctxt), "x not defined");
203 
204         final JexlScript script4 = JEXL.createScript("x.`'y'`.z = 3");
205         assertThrows(JexlException.class, () -> script4.execute(ctxt), "x not defined");
206     }
207 
208 }