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.logging.pathable;
18
19 import java.net.URL;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Enumeration;
23 import java.util.HashSet;
24 import java.util.Set;
25
26 import junit.framework.Test;
27 import junit.framework.TestCase;
28
29 import org.apache.commons.logging.PathableClassLoader;
30 import org.apache.commons.logging.PathableTestSuite;
31
32 /**
33 * Tests for the PathableTestSuite and PathableClassLoader functionality,
34 * where lookup order for the PathableClassLoader is child-first.
35 * <p>
36 * These tests assume:
37 * <ul>
38 * <li>junit is in system classpath
39 * <li>nothing else is in system classpath
40 * </ul>
41 */
42
43 public class ChildFirstTestCase extends TestCase {
44
45 /**
46 * Set up a custom classloader hierarchy for this test case.
47 * The hierarchy is:
48 * <ul>
49 * <li> contextloader: child-first.
50 * <li> childloader: child-first, used to load test case.
51 * <li> parentloader: child-first, parent is the bootclassloader.
52 * </ul>
53 */
54 public static Test suite() throws Exception {
55 Class thisClass = ChildFirstTestCase.class;
56 ClassLoader thisClassLoader = thisClass.getClassLoader();
57
58 // Make the parent a direct child of the bootloader to hide all
59 // other classes in the system classpath
60 PathableClassLoader parent = new PathableClassLoader(null);
61 parent.setParentFirst(false);
62
63 // Make the junit classes visible as a special case, as junit
64 // won't be able to call this class at all without this. The
65 // junit classes must be visible from the classloader that loaded
66 // this class, so use that as the source for future access to classes
67 // from the junit package.
68 parent.useExplicitLoader("junit.", thisClassLoader);
69
70 // Make the commons-logging.jar classes visible via the parent
71 parent.addLogicalLib("commons-logging");
72
73 // Create a child classloader to load the test case through
74 PathableClassLoader child = new PathableClassLoader(parent);
75 child.setParentFirst(false);
76
77 // Obviously, the child classloader needs to have the test classes
78 // in its path!
79 child.addLogicalLib("testclasses");
80 child.addLogicalLib("commons-logging-adapters");
81
82 // Create a third classloader to be the context classloader.
83 PathableClassLoader context = new PathableClassLoader(child);
84 context.setParentFirst(false);
85
86 // reload this class via the child classloader
87 Class testClass = child.loadClass(thisClass.getName());
88
89 // and return our custom TestSuite class
90 return new PathableTestSuite(testClass, context);
91 }
92
93 /**
94 * Utility method to return the set of all classloaders in the
95 * parent chain starting from the one that loaded the class for
96 * this object instance.
97 */
98 private Set getAncestorCLs() {
99 Set s = new HashSet();
100 ClassLoader cl = this.getClass().getClassLoader();
101 while (cl != null) {
102 s.add(cl);
103 cl = cl.getParent();
104 }
105 return s;
106 }
107
108 /**
109 * Test that the classloader hierarchy is as expected, and that
110 * calling loadClass() on various classloaders works as expected.
111 * Note that for this test case, parent-first classloading is
112 * in effect.
113 */
114 public void testPaths() throws Exception {
115 // the context classloader is not expected to be null
116 ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
117 assertNotNull("Context classloader is null", contextLoader);
118 assertEquals("Context classloader has unexpected type",
119 PathableClassLoader.class.getName(),
120 contextLoader.getClass().getName());
121
122 // the classloader that loaded this class is obviously not null
123 ClassLoader thisLoader = this.getClass().getClassLoader();
124 assertNotNull("thisLoader is null", thisLoader);
125 assertEquals("thisLoader has unexpected type",
126 PathableClassLoader.class.getName(),
127 thisLoader.getClass().getName());
128
129 // the suite method specified that the context classloader's parent
130 // is the loader that loaded this test case.
131 assertSame("Context classloader is not child of thisLoader",
132 thisLoader, contextLoader.getParent());
133
134 // thisLoader's parent should be available
135 ClassLoader parentLoader = thisLoader.getParent();
136 assertNotNull("Parent classloader is null", parentLoader);
137 assertEquals("Parent classloader has unexpected type",
138 PathableClassLoader.class.getName(),
139 parentLoader.getClass().getName());
140
141 // parent should have a parent of null
142 assertNull("Parent classloader has non-null parent", parentLoader.getParent());
143
144 // getSystemClassloader is not a PathableClassLoader; it's of a
145 // built-in type. This also verifies that system classloader is none of
146 // (context, child, parent).
147 ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
148 assertNotNull("System classloader is null", systemLoader);
149 assertFalse("System classloader has unexpected type",
150 PathableClassLoader.class.getName().equals(
151 systemLoader.getClass().getName()));
152
153 // junit classes should be visible; their classloader is not
154 // in the hierarchy of parent classloaders for this class,
155 // though it is accessable due to trickery in the PathableClassLoader.
156 Class junitTest = contextLoader.loadClass("junit.framework.Test");
157 Set ancestorCLs = getAncestorCLs();
158 assertFalse("Junit not loaded by ancestor classloader",
159 ancestorCLs.contains(junitTest.getClassLoader()));
160
161 // jcl api classes should be visible only via the parent
162 Class logClass = contextLoader.loadClass("org.apache.commons.logging.Log");
163 assertSame("Log class not loaded via parent",
164 logClass.getClassLoader(), parentLoader);
165
166 // jcl adapter classes should be visible via both parent and child. However
167 // as the classloaders are child-first we should see the child one.
168 Class log4jClass = contextLoader.loadClass("org.apache.commons.logging.impl.Log4JLogger");
169 assertSame("Log4JLogger not loaded via child",
170 log4jClass.getClassLoader(), thisLoader);
171
172 // test classes should be visible via the child only
173 Class testClass = contextLoader.loadClass("org.apache.commons.logging.PathableTestSuite");
174 assertSame("PathableTestSuite not loaded via child",
175 testClass.getClassLoader(), thisLoader);
176
177 // test loading of class that is not available
178 try {
179 Class noSuchClass = contextLoader.loadClass("no.such.class");
180 fail("Class no.such.class is unexpectedly available");
181 assertNotNull(noSuchClass); // silence warning about unused var
182 } catch(ClassNotFoundException ex) {
183 // ok
184 }
185
186 // String class classloader is null
187 Class stringClass = contextLoader.loadClass("java.lang.String");
188 assertNull("String class classloader is not null!",
189 stringClass.getClassLoader());
190 }
191
192 /**
193 * Test that the various flavours of ClassLoader.getResource work as expected.
194 */
195 public void testResource() {
196 URL resource;
197
198 ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
199 ClassLoader childLoader = contextLoader.getParent();
200
201 // getResource where it doesn't exist
202 resource = childLoader.getResource("nosuchfile");
203 assertNull("Non-null URL returned for invalid resource name", resource);
204
205 // getResource where it is accessable only to parent classloader
206 resource = childLoader.getResource("org/apache/commons/logging/Log.class");
207 assertNotNull("Unable to locate Log.class resource", resource);
208
209 // getResource where it is accessable only to child classloader
210 resource = childLoader.getResource("org/apache/commons/logging/PathableTestSuite.class");
211 assertNotNull("Unable to locate PathableTestSuite.class resource", resource);
212
213 // getResource where it is accessable to both classloaders. The one visible
214 // to the child should be returned. The URL returned will be of form
215 // jar:file:/x/y.jar!path/to/resource. The filename part should include the jarname
216 // of form commons-logging-adapters-nnnn.jar, not commons-logging-nnnn.jar
217 resource = childLoader.getResource("org/apache/commons/logging/impl/Log4JLogger.class");
218 assertNotNull("Unable to locate Log4JLogger.class resource", resource);
219 assertTrue("Incorrect source for Log4JLogger class",
220 resource.toString().indexOf("/commons-logging-adapters-1.") > 0);
221 }
222
223 /**
224 * Test that the various flavours of ClassLoader.getResources work as expected.
225 */
226 public void testResources() throws Exception {
227 Enumeration resources;
228 URL[] urls;
229
230 // verify the classloader hierarchy
231 ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
232 ClassLoader childLoader = contextLoader.getParent();
233 ClassLoader parentLoader = childLoader.getParent();
234 ClassLoader bootLoader = parentLoader.getParent();
235 assertNull("Unexpected classloader hierarchy", bootLoader);
236
237 // getResources where no instances exist
238 resources = childLoader.getResources("nosuchfile");
239 urls = toURLArray(resources);
240 assertEquals("Non-null URL returned for invalid resource name", 0, urls.length);
241
242 // getResources where the resource only exists in the parent
243 resources = childLoader.getResources("org/apache/commons/logging/Log.class");
244 urls = toURLArray(resources);
245 assertEquals("Unexpected number of Log.class resources found", 1, urls.length);
246
247 // getResources where the resource only exists in the child
248 resources = childLoader.getResources("org/apache/commons/logging/PathableTestSuite.class");
249 urls = toURLArray(resources);
250 assertEquals("Unexpected number of PathableTestSuite.class resources found", 1, urls.length);
251
252 // getResources where the resource exists in both.
253 // resources should be returned in order (child-resource, parent-resource).
254 //
255 // IMPORTANT: due to the fact that in java 1.4 and earlier method
256 // ClassLoader.getResources is final it isn't possible for PathableClassLoader
257 // to override this. So even when child-first is enabled the resource order
258 // is still (parent-resources, child-resources). This test verifies the expected
259 // behaviour - even though it's not the desired behaviour.
260
261 resources = childLoader.getResources("org/apache/commons/logging/impl/Log4JLogger.class");
262 urls = toURLArray(resources);
263 assertEquals("Unexpected number of Log4JLogger.class resources found", 2, urls.length);
264
265 // There is no gaurantee about the ordering of results returned from getResources
266 // To make this test portable across JVMs, sort the string to give them a known order
267 String[] urlsToStrings = new String[2];
268 urlsToStrings[0] = urls[0].toString();
269 urlsToStrings[1] = urls[1].toString();
270 Arrays.sort(urlsToStrings);
271 assertTrue("Incorrect source for Log4JLogger class",
272 urlsToStrings[0].indexOf("/commons-logging-1.") > 0);
273 assertTrue("Incorrect source for Log4JLogger class",
274 urlsToStrings[1].indexOf("/commons-logging-adapters-1.") > 0);
275 }
276
277 /**
278 * Utility method to convert an enumeration-of-URLs into an array of URLs.
279 */
280 private static URL[] toURLArray(Enumeration e) {
281 ArrayList l = new ArrayList();
282 while (e.hasMoreElements()) {
283 URL u = (URL) e.nextElement();
284 l.add(u);
285 }
286 URL[] tmp = new URL[l.size()];
287 return (URL[]) l.toArray(tmp);
288 }
289
290 /**
291 * Test that getResourceAsStream works.
292 */
293 public void testResourceAsStream() throws Exception {
294 java.io.InputStream is;
295
296 // verify the classloader hierarchy
297 ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
298 ClassLoader childLoader = contextLoader.getParent();
299 ClassLoader parentLoader = childLoader.getParent();
300 ClassLoader bootLoader = parentLoader.getParent();
301 assertNull("Unexpected classloader hierarchy", bootLoader);
302
303 // getResourceAsStream where no instances exist
304 is = childLoader.getResourceAsStream("nosuchfile");
305 assertNull("Invalid resource returned non-null stream", is);
306
307 // getResourceAsStream where resource does exist
308 is = childLoader.getResourceAsStream("org/apache/commons/logging/Log.class");
309 assertNotNull("Null returned for valid resource", is);
310 is.close();
311
312 // It would be nice to test parent-first ordering here, but that would require
313 // having a resource with the same name in both the parent and child loaders,
314 // but with different contents. That's a little tricky to set up so we'll
315 // skip that for now.
316 }
317 }