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