001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.logging.pathable;
018
019 import java.net.URL;
020 import java.util.ArrayList;
021 import java.util.Arrays;
022 import java.util.Enumeration;
023 import java.util.HashSet;
024 import java.util.Set;
025
026 import junit.framework.Test;
027 import junit.framework.TestCase;
028
029 import org.apache.commons.logging.PathableClassLoader;
030 import org.apache.commons.logging.PathableTestSuite;
031
032 /**
033 * Tests for the PathableTestSuite and PathableClassLoader functionality,
034 * where lookup order for the PathableClassLoader is parent-first.
035 * <p>
036 * These tests assume:
037 * <ul>
038 * <li>junit is in system classpath
039 * <li>nothing else is in system classpath
040 * </ul>
041 */
042
043 public class ParentFirstTestCase extends TestCase {
044
045 /**
046 * Set up a custom classloader hierarchy for this test case.
047 * The hierarchy is:
048 * <ul>
049 * <li> contextloader: parent-first.
050 * <li> childloader: parent-first, used to load test case.
051 * <li> parentloader: parent-first, parent is the bootclassloader.
052 * </ul>
053 */
054 public static Test suite() throws Exception {
055 Class thisClass = ParentFirstTestCase.class;
056 ClassLoader thisClassLoader = thisClass.getClassLoader();
057
058 // Make the parent a direct child of the bootloader to hide all
059 // other classes in the system classpath
060 PathableClassLoader parent = new PathableClassLoader(null);
061
062 // Make the junit classes visible as a special case, as junit
063 // won't be able to call this class at all without this. The
064 // junit classes must be visible from the classloader that loaded
065 // this class, so use that as the source for future access to classes
066 // from the junit package.
067 parent.useExplicitLoader("junit.", thisClassLoader);
068
069 // make the commons-logging.jar classes visible via the parent
070 parent.addLogicalLib("commons-logging");
071
072 // create a child classloader to load the test case through
073 PathableClassLoader child = new PathableClassLoader(parent);
074
075 // obviously, the child classloader needs to have the test classes
076 // in its path!
077 child.addLogicalLib("testclasses");
078 child.addLogicalLib("commons-logging-adapters");
079
080 // create a third classloader to be the context classloader.
081 PathableClassLoader context = new PathableClassLoader(child);
082
083 // reload this class via the child classloader
084 Class testClass = child.loadClass(thisClass.getName());
085
086 // and return our custom TestSuite class
087 return new PathableTestSuite(testClass, context);
088 }
089
090 /**
091 * Utility method to return the set of all classloaders in the
092 * parent chain starting from the one that loaded the class for
093 * this object instance.
094 */
095 private Set getAncestorCLs() {
096 Set s = new HashSet();
097 ClassLoader cl = this.getClass().getClassLoader();
098 while (cl != null) {
099 s.add(cl);
100 cl = cl.getParent();
101 }
102 return s;
103 }
104
105 /**
106 * Test that the classloader hierarchy is as expected, and that
107 * calling loadClass() on various classloaders works as expected.
108 * Note that for this test case, parent-first classloading is
109 * in effect.
110 */
111 public void testPaths() throws Exception {
112 // the context classloader is not expected to be null
113 ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
114 assertNotNull("Context classloader is null", contextLoader);
115 assertEquals("Context classloader has unexpected type",
116 PathableClassLoader.class.getName(),
117 contextLoader.getClass().getName());
118
119 // the classloader that loaded this class is obviously not null
120 ClassLoader thisLoader = this.getClass().getClassLoader();
121 assertNotNull("thisLoader is null", thisLoader);
122 assertEquals("thisLoader has unexpected type",
123 PathableClassLoader.class.getName(),
124 thisLoader.getClass().getName());
125
126 // the suite method specified that the context classloader's parent
127 // is the loader that loaded this test case.
128 assertSame("Context classloader is not child of thisLoader",
129 thisLoader, contextLoader.getParent());
130
131 // thisLoader's parent should be available
132 ClassLoader parentLoader = thisLoader.getParent();
133 assertNotNull("Parent classloader is null", parentLoader);
134 assertEquals("Parent classloader has unexpected type",
135 PathableClassLoader.class.getName(),
136 parentLoader.getClass().getName());
137
138 // parent should have a parent of null
139 assertNull("Parent classloader has non-null parent", parentLoader.getParent());
140
141 // getSystemClassloader is not a PathableClassLoader; it's of a
142 // built-in type. This also verifies that system classloader is none of
143 // (context, child, parent).
144 ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
145 assertNotNull("System classloader is null", systemLoader);
146 assertFalse("System classloader has unexpected type",
147 PathableClassLoader.class.getName().equals(
148 systemLoader.getClass().getName()));
149
150 // junit classes should be visible; their classloader is not
151 // in the hierarchy of parent classloaders for this class,
152 // though it is accessable due to trickery in the PathableClassLoader.
153 Class junitTest = contextLoader.loadClass("junit.framework.Test");
154 Set ancestorCLs = getAncestorCLs();
155 assertFalse("Junit not loaded by ancestor classloader",
156 ancestorCLs.contains(junitTest.getClassLoader()));
157
158 // jcl api classes should be visible only via the parent
159 Class logClass = contextLoader.loadClass("org.apache.commons.logging.Log");
160 assertSame("Log class not loaded via parent",
161 logClass.getClassLoader(), parentLoader);
162
163 // jcl adapter classes should be visible via both parent and child. However
164 // as the classloaders are parent-first we should see the parent one.
165 Class log4jClass = contextLoader.loadClass("org.apache.commons.logging.impl.Log4JLogger");
166 assertSame("Log4JLogger not loaded via parent",
167 log4jClass.getClassLoader(), parentLoader);
168
169 // test classes should be visible via the child only
170 Class testClass = contextLoader.loadClass("org.apache.commons.logging.PathableTestSuite");
171 assertSame("PathableTestSuite not loaded via child",
172 testClass.getClassLoader(), thisLoader);
173
174 // test loading of class that is not available
175 try {
176 Class noSuchClass = contextLoader.loadClass("no.such.class");
177 fail("Class no.such.class is unexpectedly available");
178 assertNotNull(noSuchClass); // silence warning about unused var
179 } catch(ClassNotFoundException ex) {
180 // ok
181 }
182
183 // String class classloader is null
184 Class stringClass = contextLoader.loadClass("java.lang.String");
185 assertNull("String class classloader is not null!",
186 stringClass.getClassLoader());
187 }
188
189 /**
190 * Test that the various flavours of ClassLoader.getResource work as expected.
191 */
192 public void testResource() {
193 URL resource;
194
195 ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
196 ClassLoader childLoader = contextLoader.getParent();
197
198 // getResource where it doesn't exist
199 resource = childLoader.getResource("nosuchfile");
200 assertNull("Non-null URL returned for invalid resource name", resource);
201
202 // getResource where it is accessable only to parent classloader
203 resource = childLoader.getResource("org/apache/commons/logging/Log.class");
204 assertNotNull("Unable to locate Log.class resource", resource);
205
206 // getResource where it is accessable only to child classloader
207 resource = childLoader.getResource("org/apache/commons/logging/PathableTestSuite.class");
208 assertNotNull("Unable to locate PathableTestSuite.class resource", resource);
209
210 // getResource where it is accessable to both classloaders. The one visible
211 // to the parent should be returned. The URL returned will be of form
212 // jar:file:/x/y.jar!path/to/resource. The filename part should include the jarname
213 // of form commons-logging-nnnn.jar, not commons-logging-adapters-nnnn.jar
214 resource = childLoader.getResource("org/apache/commons/logging/impl/Log4JLogger.class");
215 assertNotNull("Unable to locate Log4JLogger.class resource", resource);
216 assertTrue("Incorrect source for Log4JLogger class",
217 resource.toString().indexOf("/commons-logging-1.") > 0);
218 }
219
220 /**
221 * Test that the various flavours of ClassLoader.getResources work as expected.
222 */
223 public void testResources() throws Exception {
224 Enumeration resources;
225 URL[] urls;
226
227 // verify the classloader hierarchy
228 ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
229 ClassLoader childLoader = contextLoader.getParent();
230 ClassLoader parentLoader = childLoader.getParent();
231 ClassLoader bootLoader = parentLoader.getParent();
232 assertNull("Unexpected classloader hierarchy", bootLoader);
233
234 // getResources where no instances exist
235 resources = childLoader.getResources("nosuchfile");
236 urls = toURLArray(resources);
237 assertEquals("Non-null URL returned for invalid resource name", 0, urls.length);
238
239 // getResources where the resource only exists in the parent
240 resources = childLoader.getResources("org/apache/commons/logging/Log.class");
241 urls = toURLArray(resources);
242 assertEquals("Unexpected number of Log.class resources found", 1, urls.length);
243
244 // getResources where the resource only exists in the child
245 resources = childLoader.getResources("org/apache/commons/logging/PathableTestSuite.class");
246 urls = toURLArray(resources);
247 assertEquals("Unexpected number of PathableTestSuite.class resources found", 1, urls.length);
248
249 // getResources where the resource exists in both.
250 // resources should be returned in order (parent-resource, child-resource)
251 resources = childLoader.getResources("org/apache/commons/logging/impl/Log4JLogger.class");
252 urls = toURLArray(resources);
253 assertEquals("Unexpected number of Log4JLogger.class resources found", 2, urls.length);
254
255 // There is no gaurantee about the ordering of results returned from getResources
256 // To make this test portable across JVMs, sort the string to give them a known order
257 String[] urlsToStrings = new String[2];
258 urlsToStrings[0] = urls[0].toString();
259 urlsToStrings[1] = urls[1].toString();
260 Arrays.sort(urlsToStrings);
261 assertTrue("Incorrect source for Log4JLogger class",
262 urlsToStrings[0].indexOf("/commons-logging-1.") > 0);
263 assertTrue("Incorrect source for Log4JLogger class",
264 urlsToStrings[1].indexOf("/commons-logging-adapters-1.") > 0);
265
266 }
267
268 /**
269 * Utility method to convert an enumeration-of-URLs into an array of URLs.
270 */
271 private static URL[] toURLArray(Enumeration e) {
272 ArrayList l = new ArrayList();
273 while (e.hasMoreElements()) {
274 URL u = (URL) e.nextElement();
275 l.add(u);
276 }
277 URL[] tmp = new URL[l.size()];
278 return (URL[]) l.toArray(tmp);
279 }
280
281 /**
282 * Test that getResourceAsStream works.
283 */
284 public void testResourceAsStream() throws Exception {
285 java.io.InputStream is;
286
287 // verify the classloader hierarchy
288 ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
289 ClassLoader childLoader = contextLoader.getParent();
290 ClassLoader parentLoader = childLoader.getParent();
291 ClassLoader bootLoader = parentLoader.getParent();
292 assertNull("Unexpected classloader hierarchy", bootLoader);
293
294 // getResourceAsStream where no instances exist
295 is = childLoader.getResourceAsStream("nosuchfile");
296 assertNull("Invalid resource returned non-null stream", is);
297
298 // getResourceAsStream where resource does exist
299 is = childLoader.getResourceAsStream("org/apache/commons/logging/Log.class");
300 assertNotNull("Null returned for valid resource", is);
301 is.close();
302
303 // It would be nice to test parent-first ordering here, but that would require
304 // having a resource with the same name in both the parent and child loaders,
305 // but with different contents. That's a little tricky to set up so we'll
306 // skip that for now.
307 }
308 }