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.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  
24  import java.util.Collections;
25  import java.util.concurrent.atomic.AtomicInteger;
26  
27  import org.apache.commons.jexl3.annotations.NoJexl;
28  import org.junit.jupiter.api.Test;
29  
30  /**
31   * Tests the {@link JexlFeatures#namespaceInstantiation(boolean)} knob that gates the reflective
32   * auto-instantiation of a namespace functor from a class or class-name binding.
33   */
34  class NamespaceInstantiationTest extends JexlTestCase {
35  
36      public NamespaceInstantiationTest() {
37          super("NamespaceInstantiationTest");
38      }
39  
40      /** Counts constructor invocations to prove (non-)instantiation. */
41      static final AtomicInteger CTOR = new AtomicInteger();
42  
43      /** A namespace whose only method is an instance method; usable only if auto-instantiated. */
44      public static class InstanceNs {
45          private final int base;
46          public InstanceNs(final JexlContext ctxt) {
47              CTOR.incrementAndGet();
48              final Object n = ctxt.get("BASE");
49              base = n instanceof Number ? ((Number) n).intValue() : 0;
50          }
51          public int callIt(final int n) {
52              return n + base;
53          }
54      }
55  
56      /** A namespace with a usable static method; no instantiation required. */
57      public static class StaticNs {
58          private StaticNs() { }
59          public static int callIt(final int n) {
60              return n + 19;
61          }
62      }
63  
64      /** A namespace hidden from JEXL via {@code @NoJexl}; must not be reachable as a namespace. */
65      @NoJexl
66      public static class HiddenNs {
67          public static int callIt(final int n) {
68              return n + 1;
69          }
70      }
71  
72      @Test
73      void testFeatureDefaultsOn() {
74          assertTrue(new JexlFeatures().supportsNamespaceInstantiation());
75          assertTrue(new JexlFeatures().namespaceInstantiation(false).namespaceInstantiation(true)
76                  .supportsNamespaceInstantiation());
77          assertFalse(new JexlFeatures().namespaceInstantiation(false).supportsNamespaceInstantiation());
78      }
79  
80      @Test
81      void testInstantiationEnabled() {
82          CTOR.set(0);
83          final JexlContext ctxt = new MapContext();
84          ctxt.set("BASE", 19);
85          // class-bound namespace auto-instantiates (default feature on) and the instance method runs
86          final JexlEngine jexl = new JexlBuilder().strict(true).silent(false)
87                  .features(new JexlFeatures().namespaceInstantiation(true))
88                  .namespaces(Collections.singletonMap("nsns", InstanceNs.class)).create();
89          final JexlScript s = jexl.createScript("x -> { nsns:callIt(x); }");
90          assertEquals(42, s.execute(ctxt, 23));
91          assertEquals(1, CTOR.get());
92      }
93  
94      @Test
95      void testInstantiationDisabledBlocksInstanceNs() {
96          CTOR.set(0);
97          final JexlContext ctxt = new MapContext();
98          ctxt.set("BASE", 19);
99          final JexlEngine jexl = new JexlBuilder().strict(true).silent(false)
100                 .features(new JexlFeatures().namespaceInstantiation(false))
101                 .namespaces(Collections.singletonMap("nsns", InstanceNs.class)).create();
102         final JexlScript s = jexl.createScript("x -> { nsns:callIt(x); }");
103         // with instantiation disabled, the class is treated as a static-method namespace only;
104         // InstanceNs has no static callIt(int), so resolution fails and no constructor is invoked
105         assertThrows(JexlException.class, () -> s.execute(ctxt, 23));
106         assertEquals(0, CTOR.get());
107     }
108 
109     @Test
110     void testInstantiationDisabledKeepsStaticNs() {
111         final JexlEngine jexl = new JexlBuilder().strict(true).silent(false)
112                 .features(new JexlFeatures().namespaceInstantiation(false))
113                 .namespaces(Collections.singletonMap("sns", StaticNs.class)).create();
114         final JexlScript s = jexl.createScript("x -> { sns:callIt(x); }");
115         assertEquals(42, s.execute(new MapContext(), 23));
116     }
117 
118     @Test
119     void testInstantiationDisabledKeepsStaticNsByName() {
120         final JexlEngine jexl = new JexlBuilder().strict(true).silent(false)
121                 .features(new JexlFeatures().namespaceInstantiation(false))
122                 .namespaces(Collections.singletonMap("sns", StaticNs.class.getName())).create();
123         final JexlScript s = jexl.createScript("x -> { sns:callIt(x); }");
124         assertEquals(42, s.execute(new MapContext(), 23));
125     }
126 
127     @Test
128     void testStringNamespaceDeniedClass() {
129         // a namespace bound to a class-name that resolves to a permission-denied class must throw
130         // when used for static methods, rather than silently loading the class (f047)
131         final JexlEngine jexl = new JexlBuilder().strict(true).silent(false)
132                 .namespaces(Collections.singletonMap("h", HiddenNs.class.getName())).create();
133         final JexlScript script = jexl.createScript("h:callIt(41)");
134         final JexlException xany = assertThrows(JexlException.class, () -> script.execute(new MapContext()));
135         assertTrue(xany.getMessage().contains("class namespace") || xany.getMessage().contains("HiddenNs"),
136                 xany.getMessage());
137     }
138 }