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 child-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 ChildFirstTestCase extends TestCase { 044 045 /** 046 * Set up a custom classloader hierarchy for this test case. 047 * The hierarchy is: 048 * <ul> 049 * <li> contextloader: child-first. 050 * <li> childloader: child-first, used to load test case. 051 * <li> parentloader: child-first, parent is the bootclassloader. 052 * </ul> 053 */ 054 public static Test suite() throws Exception { 055 Class thisClass = ChildFirstTestCase.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 parent.setParentFirst(false); 062 063 // Make the junit classes visible as a special case, as junit 064 // won't be able to call this class at all without this. The 065 // junit classes must be visible from the classloader that loaded 066 // this class, so use that as the source for future access to classes 067 // from the junit package. 068 parent.useExplicitLoader("junit.", thisClassLoader); 069 070 // Make the commons-logging.jar classes visible via the parent 071 parent.addLogicalLib("commons-logging"); 072 073 // Create a child classloader to load the test case through 074 PathableClassLoader child = new PathableClassLoader(parent); 075 child.setParentFirst(false); 076 077 // Obviously, the child classloader needs to have the test classes 078 // in its path! 079 child.addLogicalLib("testclasses"); 080 child.addLogicalLib("commons-logging-adapters"); 081 082 // Create a third classloader to be the context classloader. 083 PathableClassLoader context = new PathableClassLoader(child); 084 context.setParentFirst(false); 085 086 // reload this class via the child classloader 087 Class testClass = child.loadClass(thisClass.getName()); 088 089 // and return our custom TestSuite class 090 return new PathableTestSuite(testClass, context); 091 } 092 093 /** 094 * Utility method to return the set of all classloaders in the 095 * parent chain starting from the one that loaded the class for 096 * this object instance. 097 */ 098 private Set getAncestorCLs() { 099 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 }