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 }