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  
18  package org.apache.commons.jxpath.util;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  
23  import java.io.IOException;
24  import java.lang.reflect.InvocationTargetException;
25  import java.lang.reflect.Method;
26  import java.net.URL;
27  
28  import org.apache.commons.io.IOUtils;
29  import org.apache.commons.jxpath.JXPathContext;
30  import org.apache.commons.jxpath.JXPathException;
31  import org.apache.commons.lang3.ArrayUtils;
32  import org.junit.jupiter.api.AfterEach;
33  import org.junit.jupiter.api.BeforeEach;
34  import org.junit.jupiter.api.Test;
35  
36  /**
37   * Tests org.apache.commons.jxpath.util.ClassLoaderUtil.
38   */
39  public class ClassLoaderUtilTest {
40  
41      /**
42       * A simple class loader which delegates all class loading to its parent with two exceptions. First, attempts to load the class
43       * {@code org.apache.commons.jxpath.util.ClassLoaderUtilTest} will always result in a ClassNotFoundException. Second, loading the class
44       * {@code org.apache.commons.jxpath.util.ClassLoadingExampleClass} will result in the class being loaded by this class loader, regardless of whether the
45       * parent can/has loaded it.
46       *
47       */
48      private static final class TestClassLoader extends ClassLoader {
49  
50          private Class<?> testCaseClass = null;
51  
52          public TestClassLoader(final ClassLoader classLoader) {
53              super(classLoader);
54          }
55  
56          @Override
57          public synchronized Class<?> loadClass(final String name, final boolean resolved) throws ClassNotFoundException {
58              if (EXAMPLE_CLASS_NAME.equals(name)) {
59                  throw new ClassNotFoundException();
60              }
61              if (TEST_CASE_CLASS_NAME.equals(name)) {
62                  if (testCaseClass == null) {
63                      final URL classUrl = getParent().getResource("org/apache/commons/jxpath/util/ClassLoaderUtilTest.class");
64                      byte[] clazzBytes;
65                      try {
66                          clazzBytes = IOUtils.toByteArray(classUrl);
67                      } catch (final IOException e) {
68                          throw new ClassNotFoundException(classUrl.toString(), e);
69                      }
70                      this.testCaseClass = this.defineClass(TEST_CASE_CLASS_NAME, clazzBytes, 0, clazzBytes.length);
71                  }
72                  return this.testCaseClass;
73              }
74              return getParent().loadClass(name);
75          }
76      }
77  
78      // These must be string literals, and not populated by calling getName() on
79      // the respective classes, since the tests below will load this class in a
80      // special class loader which may be unable to load those classes.
81      private static final String TEST_CASE_CLASS_NAME = "org.apache.commons.jxpath.util.ClassLoaderUtilTest";
82      private static final String EXAMPLE_CLASS_NAME = "org.apache.commons.jxpath.util.ClassLoadingExampleClass";
83  
84      /**
85       * Performs a basic query that requires a class be loaded dynamically by JXPath and asserts the dynamic class load fails.
86       */
87      public static void callExampleMessageMethodAndAssertClassNotFoundJXPathException() {
88          final JXPathContext context = JXPathContext.newContext(new Object());
89          assertThrows(JXPathException.class, () -> context.selectSingleNode(EXAMPLE_CLASS_NAME + ".getMessage()"),
90                  "We should not be able to load " + EXAMPLE_CLASS_NAME + ".");
91      }
92  
93      /**
94       * Performs a basic query that requires a class be loaded dynamically by JXPath and asserts the dynamic class load succeeds.
95       */
96      public static void callExampleMessageMethodAndAssertSuccess() {
97          final JXPathContext context = JXPathContext.newContext(new Object());
98          assertEquals("an example class", context.selectSingleNode(EXAMPLE_CLASS_NAME + ".getMessage()"));
99      }
100 
101     private ClassLoader orginalContextClassLoader;
102 
103     /**
104      * Loads this class through the given class loader and then invokes the indicated no argument static method of the class.
105      *
106      * @param cl         the class loader under which to invoke the method.
107      * @param methodName the name of the static no argument method on this class to invoke.
108      * @throws ReflectiveOperationException on test failures.
109      */
110     private void executeTestMethodUnderClassLoader(final ClassLoader cl, final String methodName) throws ReflectiveOperationException {
111         final Class<?> testClass = cl.loadClass(TEST_CASE_CLASS_NAME);
112         final Method testMethod = testClass.getMethod(methodName, ArrayUtils.EMPTY_CLASS_ARRAY);
113         try {
114             testMethod.invoke(null, (Object[]) null);
115         } catch (final InvocationTargetException e) {
116             if (e.getCause() instanceof RuntimeException) {
117                 // Allow the runtime exception to propagate up.
118                 throw (RuntimeException) e.getCause();
119             }
120         }
121     }
122 
123     /**
124      * Setup for the tests.
125      */
126     @BeforeEach
127     public void setUp() {
128         this.orginalContextClassLoader = Thread.currentThread().getContextClassLoader();
129     }
130 
131     /**
132      * Cleanup for the tests.
133      */
134     @AfterEach
135     public void tearDown() {
136         Thread.currentThread().setContextClassLoader(this.orginalContextClassLoader);
137     }
138 
139     /**
140      * Tests that JXPath cannot dynamically load a class, which is not visible to its class loader, when the context class loader is null.
141      *
142      * @throws ReflectiveOperationException on test failures.
143      */
144     @Test
145     public void testClassLoadFailWithoutContextClassLoader() throws ReflectiveOperationException {
146         Thread.currentThread().setContextClassLoader(null);
147         final ClassLoader cl = new TestClassLoader(getClass().getClassLoader());
148         executeTestMethodUnderClassLoader(cl, "callExampleMessageMethodAndAssertClassNotFoundJXPathException");
149     }
150 
151     /**
152      * Tests that JXPath can dynamically load a class, which is not visible to its class loader, when the context class loader is set and can load the class.
153      *
154      * @throws ReflectiveOperationException on test failures.
155      */
156     @Test
157     public void testClassLoadSuccessWithContextClassLoader() throws ReflectiveOperationException {
158         Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
159         final ClassLoader cl = new TestClassLoader(getClass().getClassLoader());
160         executeTestMethodUnderClassLoader(cl, "callExampleMessageMethodAndAssertSuccess");
161     }
162 
163     /**
164      * Tests that JXPath can dynamically load a class, which is visible to its class loader, when there is no context class loader set.
165      */
166     @Test
167     public void testClassLoadSuccessWithoutContextClassLoader() {
168         Thread.currentThread().setContextClassLoader(null);
169         callExampleMessageMethodAndAssertSuccess();
170     }
171 
172     /**
173      * Tests that JXPath will use its class loader to dynamically load a requested class when the context class loader is set but unable to load the class.
174      */
175     @Test
176     public void testCurrentClassLoaderFallback() {
177         final ClassLoader cl = new TestClassLoader(getClass().getClassLoader());
178         Thread.currentThread().setContextClassLoader(cl);
179         callExampleMessageMethodAndAssertSuccess();
180     }
181 }