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  
18  package org.apache.commons.jexl3.internal.introspection;
19  
20  import java.lang.reflect.Constructor;
21  import java.lang.reflect.Field;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Modifier;
24  import java.lang.reflect.Proxy;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Map;
29  import java.util.Objects;
30  import java.util.Set;
31  import java.util.function.BiPredicate;
32  
33  import org.apache.commons.jexl3.annotations.NoJexl;
34  import org.apache.commons.jexl3.introspection.JexlPermissions;
35  
36  /**
37   * Checks whether an element (ctor, field, or method) is visible by JEXL introspection.
38   * <p>The default implementation does this by checking if an element has been annotated with NoJexl.</p>
39   *
40   * <p>The NoJexl annotation allows a fine grain permission on executable objects (methods, fields, constructors).
41   * </p>
42   * <ul>
43   * <li>NoJexl of a package implies all classes (including derived classes), and all interfaces
44   * of that package are invisible to JEXL.</li>
45   * <li>NoJexl on a class implies this class, and all its derived classes are invisible to JEXL.</li>
46   * <li>NoJexl on a (public) field makes it not visible as a property to JEXL.</li>
47   * <li>NoJexl on a constructor prevents that constructor to be used to instantiate through 'new'.</li>
48   * <li>NoJexl on a method prevents that method and any of its overrides to be visible to JEXL.</li>
49   * <li>NoJexl on an interface prevents all methods of that interface and their overrides to be visible to JEXL.</li>
50   * </ul>
51   * <p>It is possible to define permissions on external library classes used for which the source code
52   * cannot be altered using an instance of permissions using {@link JexlPermissions#parse(String...)}.</p>
53   */
54  public class Permissions implements JexlPermissions {
55      /**
56       * Represents the ability to create a copy of an object.
57       * Any class implementing this interface must provide a concrete
58       * implementation for the {@code copy()} method, which returns
59       * a new instance of the object that is a logical copy of the original.
60       *
61       * @param <T> the type of object that can be copied
62       */
63      interface Copyable<T> {
64          T copy() ;
65      }
66  
67      /**
68       * Creates a copy of a map containing copyable values.
69       * @param map the map to copy
70       * @return the copy of the map
71       * @param <T> the type of Copyable values
72       */
73      static <T extends Copyable<T>> Map<String, T> copyMap(final Map<String, T> map) {
74          final Map<String, T> copy = new HashMap<>(map.size());
75          for (final Map.Entry<String, T> entry : map.entrySet()) {
76              copy.put(entry.getKey(), entry.getValue().copy());
77          }
78          return copy;
79      }
80  
81      /**
82       * Equivalent of @NoJexl on a ctor, a method, or a field in a class.
83       * <p>Field or method that are named are denied access.</p>
84       */
85      static class NoJexlClass implements Copyable<NoJexlClass> {
86          // the NoJexl method names (including ctor, name of class)
87          final Set<String> methodNames;
88          // the NoJexl field names
89          final Set<String> fieldNames;
90  
91          NoJexlClass() {
92              this(new HashSet<>(), new HashSet<>());
93          }
94  
95          NoJexlClass(final Set<String> methods, final Set<String> fields) {
96              methodNames = methods;
97              fieldNames = fields;
98          }
99  
100         @Override public NoJexlClass copy() {
101             return new NoJexlClass(new HashSet<>(methodNames), new HashSet<>(fieldNames));
102         }
103 
104         boolean deny(final Constructor<?> method) {
105             return methodNames.contains(method.getDeclaringClass().getSimpleName());
106         }
107 
108         boolean deny(final Field field) {
109             return isEmpty() || fieldNames.contains(field.getName());
110         }
111 
112         boolean deny(final Method method) {
113             return isEmpty() || methodNames.contains(method.getName());
114         }
115 
116         boolean isEmpty() { return methodNames.isEmpty() && fieldNames.isEmpty(); }
117 
118         /**
119          * Whether this is a positive (allow-oriented) class declaration.
120          * <p>A positive class is explicitly allowed in its package (it contributes to
121          * {@link NoJexlPackage#hasAllowedClass()} and an empty one means &quot;allow the whole class&quot;).
122          * A plain deny-list {@code NoJexlClass} is not positive.</p>
123          *
124          * @return true if the class is positively allowed, false if it is a deny-list
125          */
126         boolean isPositive() { return false; }
127     }
128 
129     /**
130      * A positive NoJexl construct that defines what is denied by absence in the set.
131      * <p>Field or method that are named are the only one allowed access.</p>
132      */
133     static class JexlClass extends NoJexlClass {
134         JexlClass(final Set<String> methods, final Set<String> fields) {
135             super(methods, fields);
136         }
137         JexlClass() {
138         }
139         @Override public JexlClass copy() {
140             return new JexlClass(new HashSet<>(methodNames), new HashSet<>(fieldNames));
141         }
142         @Override boolean deny(final Constructor<?> method) { return !super.deny(method); }
143         @Override boolean deny(final Field field) { return !super.deny(field); }
144         @Override boolean deny(final Method method) { return !super.deny(method); }
145         @Override boolean isPositive() { return true; }
146     }
147 
148     /**
149      * A class that is positively allowed in its package yet carries deny-list member semantics.
150      * <p>Used for {@code +ClassName{ -member(); }} inside a deny package: the class is added to the
151      * allowed set (so unlisted classes in the package stay denied) but the named members are denied
152      * while all others remain allowed. It is a {@link NoJexlClass} (deny-list) tagged as positive;
153      * the deny-list behavior is inherited unchanged.</p>
154      */
155     static class AllowedNoJexlClass extends NoJexlClass {
156         AllowedNoJexlClass() {
157         }
158         AllowedNoJexlClass(final Set<String> methods, final Set<String> fields) {
159             super(methods, fields);
160         }
161         @Override public AllowedNoJexlClass copy() {
162             return new AllowedNoJexlClass(new HashSet<>(methodNames), new HashSet<>(fieldNames));
163         }
164         @Override boolean isPositive() { return true; }
165     }
166 
167     /**
168      * Equivalent of @NoJexl on a class in a package.
169      */
170     protected static class NoJexlPackage implements Copyable<NoJexlPackage> {
171         // the NoJexl class names
172         final Map<String, NoJexlClass> nojexl;
173 
174         /**
175          * Default ctor.
176          */
177         NoJexlPackage() {
178             this(null);
179         }
180 
181         /**
182          * Ctor.
183          *
184          * @param map the map of NoJexl classes
185          */
186         NoJexlPackage(final Map<String, NoJexlClass> map) {
187             this.nojexl = map == null || map.isEmpty() ? new HashMap<>() : map;
188         }
189 
190         void addNoJexl(final String key, final NoJexlClass njc) {
191             if (njc == null) {
192                 nojexl.remove(key);
193             } else {
194                 nojexl.put(key, njc);
195             }
196         }
197 
198         NoJexlClass getNoJexl(final Class<?> clazz) {
199             return nojexl.get(classKey(clazz));
200         }
201 
202         boolean isEmpty() { return nojexl.isEmpty(); }
203 
204         /**
205          * Whether this package has at least one explicitly-allowed class (a positive entry).
206          * <p>A package with allowed-class entries acts as an allow-list: unlisted classes are denied.
207          * A package with only denied-class entries acts as a deny-list: unlisted classes are allowed.</p>
208          *
209          * @return true if at least one class is explicitly allowed
210          */
211         boolean hasAllowedClass() {
212             for (final NoJexlClass njc : nojexl.values()) {
213                 if (njc.isPositive()) {
214                     return true;
215                 }
216             }
217             return false;
218         }
219 
220         /**
221          * Whether this is a positive (allow-oriented) package declaration.
222          * <p>A positive package allows its own classes by default; a deny-list package does not.</p>
223          *
224          * @return true if the package is positively allowed, false otherwise
225          */
226         boolean isPositive() { return false; }
227 
228         @Override public NoJexlPackage copy() {
229             return new NoJexlPackage(copyMap(nojexl));
230         }
231     }
232 
233     /**
234      * A package where classes are allowed by default.
235      */
236     static class JexlPackage extends NoJexlPackage {
237         JexlPackage(final Map<String, NoJexlClass> map) {
238             super(map);
239         }
240 
241         @Override
242         NoJexlClass getNoJexl(final Class<?> clazz) {
243             final NoJexlClass njc = nojexl.get(classKey(clazz));
244             return njc != null ? njc : JEXL_CLASS;
245         }
246 
247         @Override boolean isPositive() { return true; }
248 
249         @Override public JexlPackage copy() {
250             return new JexlPackage(copyMap(nojexl));
251         }
252     }
253 
254     /** Marker for whole NoJexl class. */
255     static final NoJexlClass NOJEXL_CLASS = new NoJexlClass(Collections.emptySet(), Collections.emptySet()) {
256         @Override boolean deny(final Constructor<?> method) {
257             return true;
258         }
259 
260         @Override boolean deny(final Field field) {
261             return true;
262         }
263 
264         @Override boolean deny(final Method method) {
265             return true;
266         }
267     };
268 
269     /** Marker for allowed class. */
270     static final NoJexlClass JEXL_CLASS = new JexlClass(Collections.emptySet(), Collections.emptySet()) {
271         @Override boolean deny(final Constructor<?> method) {
272             return false;
273         }
274 
275         @Override boolean deny(final Field field) {
276             return false;
277         }
278 
279         @Override  boolean deny(final Method method) {
280             return false;
281         }
282     };
283 
284     /** Marker for @NoJexl package. */
285     static final NoJexlPackage NOJEXL_PACKAGE = new NoJexlPackage(Collections.emptyMap()) {
286         @Override NoJexlClass getNoJexl(final Class<?> clazz) {
287             return NOJEXL_CLASS;
288         }
289     };
290 
291     /** Marker for fully allowed package. */
292     static final NoJexlPackage JEXL_PACKAGE = new NoJexlPackage(Collections.emptyMap()) {
293         @Override NoJexlClass getNoJexl(final Class<?> clazz) {
294             return JEXL_CLASS;
295         }
296         @Override boolean isPositive() {
297             return true;
298         }
299         // a constant singleton survives copy as itself, preserving its positive nature through compose()
300         @Override public NoJexlPackage copy() {
301             return this;
302         }
303     };
304 
305     /**
306      * The no-restriction introspection permission singleton.
307      */
308     static final Permissions UNRESTRICTED = new Permissions();
309 
310     /**
311      * The @NoJexl execution-time map.
312      */
313     private final Map<String, NoJexlPackage> packages;
314     /**
315      * The allowed package patterns (wildcards or exact package names).
316      * <p>Empty together with an empty {@link #packages} map means open-world: every package is accessible
317      * and only explicitly denied elements are carved out — the behavior of {@link #UNRESTRICTED}.
318      * Empty with a non-empty {@link #packages} map, or non-empty, means closed-world: only declared
319      * packages are accessible.</p>
320      */
321     private final Set<String> allowed;
322 
323     /** Allow inheritance. */
324     protected Permissions() {
325         this.allowed = Collections.emptySet();
326         this.packages = Collections.emptyMap();
327     }
328 
329     /**
330      * Default ctor.
331      *
332      * @param perimeter the allowed wildcard set of packages
333      * @param nojexl the NoJexl external map
334      */
335     protected Permissions(final Set<String> perimeter, final Map<String, NoJexlPackage> nojexl) {
336         this.allowed = perimeter;
337         this.packages = nojexl;
338     }
339 
340     /**
341      * Creates a new set of permissions by composing these permissions with a new set of rules.
342      *
343      * @param src the rules
344      * @return the new permissions
345      */
346     @Override
347     public Permissions compose(final String... src) {
348         return new PermissionsParser().parse(new HashSet<>(allowed), copyMap(packages), src);
349     }
350 
351     /**
352      * Creates a class key joining enclosing ascendants with '$'.
353      * <p>As in {@code outer$inner} for <code>class outer { class inner...</code>.</p>
354      *
355      * @param clazz the clazz
356      * @return the clazz key
357      */
358     static String classKey(final Class<?> clazz) {
359         return classKey(clazz, null);
360     }
361 
362     /**
363      * Creates a class key joining enclosing ascendants with '$'.
364      * <p>As in {@code outer$inner} for <code>class outer { class inner...</code>.</p>
365      *
366      * @param clazz the clazz
367      * @param strb the buffer to compose the key
368      * @return the clazz key
369      */
370     static String classKey(final Class<?> clazz, final StringBuilder strb) {
371         StringBuilder keyb = strb;
372         final Class<?> outer = clazz.getEnclosingClass();
373         if (outer != null) {
374             if (keyb == null) {
375                 keyb = new StringBuilder();
376             }
377             classKey(outer, keyb);
378             keyb.append('$');
379         }
380         if (keyb != null) {
381             keyb.append(clazz.getSimpleName());
382             return keyb.toString();
383         }
384         return clazz.getSimpleName();
385     }
386 
387     /**
388      * Whether the wildcard set of packages allows a given package to be introspected.
389      *
390      * @param allowed the allowed set (not null, may be empty)
391      * @param name the package name (not null)
392      * @return true if allowed, false otherwise
393      */
394     static boolean wildcardAllow(final Set<String> allowed, final String name) {
395         // allowed packages are explicit in this case
396         boolean found = allowed == null || allowed.isEmpty() || allowed.contains(name);
397         if (!found) {
398             String wildcard = name;
399             for (int i = name.length(); !found && i > 0; i = wildcard.lastIndexOf('.')) {
400                 wildcard = wildcard.substring(0, i);
401                 found = allowed.contains(wildcard + ".*");
402             }
403         }
404         return found;
405     }
406 
407     /**
408      * Gets the package constraints.
409      *
410      * @param packageName the package name
411      * @return the package constraints instance, not-null.
412      */
413     private NoJexlPackage getNoJexlPackage(final String packageName) {
414         return packages.getOrDefault(packageName, JEXL_PACKAGE);
415     }
416 
417     /**
418      * @return the packages
419      */
420     Map<String, NoJexlPackage> getPackages() {
421         return packages == null ? Collections.emptyMap() : Collections.unmodifiableMap(packages);
422     }
423 
424     /**
425      * @return the wildcards
426      */
427     Set<String> getWildcards() {
428         return allowed == null ? Collections.emptySet() : Collections.unmodifiableSet(allowed);
429     }
430 
431     /**
432      * Whether a package belongs to the allowed perimeter.
433      * <p>Open-world ({@link #UNRESTRICTED}: no rules at all) allows every package. Closed-world requires the
434      * package to match an entry in {@link #allowed}; an empty perimeter in closed-world matches nothing.</p>
435      *
436      * @param packageName the package name (not null)
437      * @return true if allowed, false otherwise
438      */
439     private boolean allowedPackage(final String packageName) {
440         if ((allowed.isEmpty() && packages.isEmpty()) || (!allowed.isEmpty() && wildcardAllow(allowed, packageName))) {
441             return true;
442         }
443         // a package explicitly declared positive allows itself (exact match only, no sub-package inference)
444         final NoJexlPackage njp = packages.get(packageName);
445         return njp != null && njp.isPositive();
446     }
447 
448     /**
449      * Determines whether a specified permission check is allowed for a given class.
450      * The check involves verifying if a class or its corresponding package explicitly permits
451      * a name (e.g., method) based on a given condition.
452      *
453      * @param <T> the type of the name to check (e.g., method, constructor)
454      * @param clazz the class to evaluate (not null)
455      * @param name the name to verify (not null)
456      * @param check the condition to test whether the specified name is allowed (not null)
457      * @return true if the specified name is allowed based on the condition, false otherwise
458      */
459     private <T> boolean specifiedAllow(final Class<?> clazz, final T name, final BiPredicate<NoJexlClass, T> check) {
460         final String packageName = ClassTool.getPackageName(clazz);
461         if (allowedPackage(packageName)) {
462             return true;
463         }
464         final NoJexlPackage njp = packages.get(packageName);
465         if (njp != null && check != null) {
466             // there is a package permission, check if there is a class permission
467             final NoJexlClass njc = njp.getNoJexl(clazz);
468             if (njc != null) {
469                 return check.test(njc, name);
470             }
471             // class not listed: allowed if the package is a deny-list (no explicit class allows);
472             // denied if the package is an allow-list (e.g. java.io -{ +PrintWriter{} ... })
473             return !njp.hasAllowedClass();
474         }
475         // package not declared at all
476         return false;
477     }
478 
479     /**
480      * Checks whether a class or one of its super-classes or implemented interfaces
481      * explicitly allows JEXL introspection.
482      *
483      * @param clazz the class to check
484      * @return true if JEXL is allowed to introspect, false otherwise
485      */
486     @Override
487     public boolean allow(final Class<?> clazz) {
488         // clazz must be not null
489         if (!validate(clazz)) {
490             return false;
491         }
492         // proxy goes through
493         if (Proxy.isProxyClass(clazz)) {
494             return true;
495         }
496         // class must not be denied, nor extend a denied class
497         if (deny(clazz)) {
498             return false;
499         }
500         // a subclass of a denied class is also denied; Object (the universal root) is skipped, and the
501         // null guard keeps the walk safe when clazz is an interface or Object itself (getSuperclass() null).
502         for (Class<?> walk = clazz.getSuperclass(); walk != null && walk != Object.class; walk = walk.getSuperclass()) {
503             if (deny(walk)) {
504                 return false;
505             }
506         }
507         // a class is allowed only by its own package or class-level declaration: there is deliberately no
508         // reach-through via an allowed super-type, which would otherwise expose the whole foreign class.
509         return allowedClass(clazz);
510     }
511 
512     /**
513      * Whether a class is allowed by its own package or class-level declaration.
514      *
515      * @param clazz the class to check (not null)
516      * @return true if explicitly allowed
517      */
518     private boolean allowedClass(final Class<?> clazz) {
519         return specifiedAllow(clazz, clazz, (njc, c) -> njc.isPositive() || !njc.isEmpty());
520     }
521 
522     /**
523      * Check whether a method is allowed to be introspected in one superclass or interface.
524      *
525      * @param clazz the superclass or interface to check
526      * @param method the method
527      * @param explicit carries whether the package holding the method is explicitly allowed
528      * @return true if JEXL is allowed to introspect, false otherwise
529      */
530     private boolean allow(final Class<?> clazz, final Method method, final boolean[] explicit) {
531         try {
532             // check if the method in that class is declared thus overrides
533             final Method override = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
534             if (override != method) {
535                 // should not be possible...
536                 if (denyMethod(override)) {
537                     return false;
538                 }
539                 // explicit |= ...
540                 if (!explicit[0]) {
541                     explicit[0] = specifiedAllow(clazz, override, (njc, m) -> !njc.deny(m));
542                 }
543             }
544             return true;
545         } catch (final NoSuchMethodException ex) {
546             // will happen if not overriding method in clazz
547             return true;
548         } catch (final SecurityException ex) {
549             // unexpected, can't do much
550             return false;
551         }
552     }
553 
554     /**
555      * Checks whether a constructor explicitly allows JEXL introspection.
556      *
557      * @param ctor the constructor to check
558      * @return true if JEXL is allowed to introspect, false otherwise
559      */
560     @Override
561     public boolean allow(final Constructor<?> ctor) {
562         // method must be not null, public
563         // check declared restrictions
564         if (!validate(ctor) || deny(ctor)) {
565             return false;
566         }
567         // class must agree
568         final Class<?> clazz = ctor.getDeclaringClass();
569         if (deny(clazz)) {
570             return false;
571         }
572         // check wildcards
573         return specifiedAllow(clazz, clazz, (njc, c) -> !njc.deny(ctor));
574     }
575 
576 
577     /**
578      * Checks whether a field explicitly allows JEXL introspection.
579      *
580      * @param field the field to check
581      * @return true if JEXL is allowed to introspect, false otherwise
582      */
583     @Override
584     public boolean allow(final Field field) {
585         // field must be public
586         // check declared restrictions
587         if (!validate(field) || deny(field)) {
588             return false;
589         }
590         // class must agree
591         final Class<?> clazz = field.getDeclaringClass();
592         if (deny(clazz)) {
593             return false;
594         }
595         // check wildcards
596         return specifiedAllow(clazz, field, (njc, m) -> !njc.deny(m));
597     }
598 
599     @Override
600     public boolean allow(final Class<?> clazz, final Method method) {
601         if (!validate(clazz) || !validate(method)) {
602             return false;
603         }
604         if ((method.getModifiers() & Modifier.STATIC) == 0) {
605             final Class<?> declaring = method.getDeclaringClass();
606             if (clazz != declaring) {
607                 // just check this is an override of a method in clazz, if not, it is not allowed (obviously)
608                 if (deny(clazz) || !declaring.isAssignableFrom(clazz)) {
609                     return false;
610                 }
611                 // an explicit permission on the concrete class can deny; otherwise fall through to
612                 // allow(method) which honors carve-outs on the declaring class/interfaces (e.g. Object.getClass())
613                 final NoJexlClass njc = getNoJexl(clazz, null);
614                 if (njc != null && njc.deny(method)) {
615                     return false;
616                 }
617             }
618         }
619         return allow(method);
620     }
621 
622     @Override
623     public boolean allow(final Class<?> clazz, final Field field) {
624         if (!validate(clazz) || !validate(field)) {
625             return false;
626         }
627         if ((field.getModifiers() & Modifier.STATIC) == 0) {
628             final Class<?> declaring = field.getDeclaringClass();
629             if (clazz != declaring) {
630                 // just check this clazz extends/inherits from declaring, if not, it is not allowed (obviously)
631                 if (deny(clazz) || !declaring.isAssignableFrom(clazz)) {
632                     return false;
633                 }
634                 // an explicit permission on the concrete class can deny; otherwise fall through to
635                 // allow(field) which honors carve-outs on the declaring class
636                 final NoJexlClass njc = getNoJexl(clazz, null);
637                 if (njc != null && njc.deny(field)) {
638                     return false;
639                 }
640             }
641         }
642         return allow(field);
643     }
644 
645     /**
646      * Checks whether a method explicitly allows JEXL introspection.
647      * <p>Since methods can be overridden, this also checks that no superclass or interface
648      * explicitly disallows this method.</p>
649      *
650      * @param method the method to check
651      * @return true if JEXL is allowed to introspect, false otherwise
652      */
653     @Override
654     public boolean allow(final Method method) {
655         // method must be not null, public, not synthetic, not bridge
656         // method must be allowed
657         if (!validate(method) || denyMethod(method)) {
658             return false;
659         }
660         Class<?> clazz = method.getDeclaringClass();
661         // gather if the packages explicitly allow any implementation of the method
662         final boolean[] explicit = { specifiedAllow(clazz, method, (njc, m) -> !njc.deny(m)) };
663         // let's walk all interfaces
664         for (final Class<?> inter : clazz.getInterfaces()) {
665             if (!allow(inter, method, explicit)) {
666                 return false;
667             }
668         }
669         // let's walk all super classes
670         clazz = clazz.getSuperclass();
671         while (clazz != null) {
672             if (!allow(clazz, method, explicit)) {
673                 return false;
674             }
675             clazz = clazz.getSuperclass();
676         }
677         return explicit[0];
678     }
679 
680     /**
681      * Checks whether a package explicitly disallows JEXL introspection.
682      *
683      * @param pack the package
684      * @return true if JEXL is allowed to introspect, false otherwise
685      */
686     @Override
687     public boolean allow(final Package pack) {
688         // field must be public
689         // check declared restrictions
690         if (!validate(pack) || deny(pack)) {
691             return false;
692         }
693         // an explicit package entry is allowed unless it is the deny marker
694         final String name = pack.getName();
695         final NoJexlPackage njp = packages.get(name);
696         return njp == null ? allowedPackage(name) : !Objects.equals(NOJEXL_PACKAGE, njp);
697     }
698 
699 
700     /**
701      * Tests whether a whole class is denied Jexl visibility.
702      * <p>Also checks package visibility.</p>
703      *
704      * @param clazz the class
705      * @return true if denied, false otherwise
706      */
707     private boolean deny(final Class<?> clazz) {
708         // Don't deny arrays
709         if (clazz.isArray()) {
710             return false;
711         }
712         // is clazz annotated with nojexl ?
713         final NoJexl nojexl = clazz.getAnnotation(NoJexl.class);
714         if (nojexl != null) {
715             return true;
716         }
717         final NoJexlPackage njp = packages.get(ClassTool.getPackageName(clazz));
718         return njp != null && Objects.equals(NOJEXL_CLASS, njp.getNoJexl(clazz));
719     }
720 
721     /**
722      * Tests whether a constructor is denied Jexl visibility.
723      *
724      * @param ctor the constructor
725      * @return true if denied, false otherwise
726      */
727     private boolean deny(final Constructor<?> ctor) {
728         // is ctor annotated with nojexl ?
729         final NoJexl nojexl = ctor.getAnnotation(NoJexl.class);
730         if (nojexl != null) {
731             return true;
732         }
733         return getNoJexl(ctor.getDeclaringClass()).deny(ctor);
734     }
735 
736     /**
737      * Tests whether a field is denied Jexl visibility.
738      *
739      * @param field the field
740      * @return true if denied, false otherwise
741      */
742     private boolean deny(final Field field) {
743         // is field annotated with nojexl ?
744         final NoJexl nojexl = field.getAnnotation(NoJexl.class);
745         if (nojexl != null) {
746             return true;
747         }
748         return getNoJexl(field.getDeclaringClass()).deny(field);
749     }
750 
751     /**
752      * Tests whether a method is denied Jexl visibility.
753      *
754      * @param method the method
755      * @return true if denied, false otherwise
756      */
757     private boolean deny(final Method method) {
758         // is method annotated with nojexl ?
759         final NoJexl nojexl = method.getAnnotation(NoJexl.class);
760         if (nojexl != null) {
761             return true;
762         }
763         return getNoJexl(method.getDeclaringClass()).deny(method);
764     }
765 
766     /**
767      * Tests whether a whole package is denied Jexl visibility.
768      *
769      * @param pack the package
770      * @return true if denied, false otherwise
771      */
772     private boolean deny(final Package pack) {
773         // is package annotated with nojexl ?
774         final NoJexl nojexl = pack.getAnnotation(NoJexl.class);
775         if (nojexl != null) {
776             return true;
777         }
778         return Objects.equals(NOJEXL_PACKAGE, packages.get(pack.getName()));
779     }
780 
781     /**
782      * Tests whether a method is denied.
783      *
784      * @param method the method
785      * @return true if it has been disallowed through annotation or declaration
786      */
787     private boolean denyMethod(final Method method) {
788         // check declared restrictions, class must not be denied
789         return deny(method) || deny(method.getDeclaringClass());
790     }
791 
792     /**
793      * Gets the class constraints.
794      * <p>If nothing was explicitly forbidden, everything is allowed.</p>
795      *
796      * @param clazz the class
797      * @return the class constraints instance, not-null.
798      */
799     private NoJexlClass getNoJexl(final Class<?> clazz) {
800         return getNoJexl(clazz, JEXL_CLASS);
801     }
802     private NoJexlClass getNoJexl(final Class<?> clazz, final NoJexlClass ifNone) {
803         final String pkgName = ClassTool.getPackageName(clazz);
804         final NoJexlPackage njp = getNoJexlPackage(pkgName);
805         if (njp != null) {
806             final NoJexlClass njc = njp.getNoJexl(clazz);
807             if (njc != null) {
808                 return njc;
809             }
810         }
811         return ifNone;
812     }
813 }