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;
018
019 import junit.framework.TestCase;
020
021 /**
022 * testcase to emulate container and application isolated from container
023 * @author baliuka
024 * @version $Id: LoadTestCase.java 424108 2006-07-20 23:19:55Z skitching $
025 */
026 public class LoadTestCase extends TestCase{
027 //TODO: need some way to add service provider packages
028 static private String LOG_PCKG[] = {"org.apache.commons.logging",
029 "org.apache.commons.logging.impl"};
030
031 /**
032 * A custom classloader which "duplicates" logging classes available
033 * in the parent classloader into itself.
034 * <p>
035 * When asked to load a class that is in one of the LOG_PCKG packages,
036 * it loads the class itself (child-first). This class doesn't need
037 * to be set up with a classpath, as it simply uses the same classpath
038 * as the classloader that loaded it.
039 */
040 static class AppClassLoader extends ClassLoader{
041
042 java.util.Map classes = new java.util.HashMap();
043
044 AppClassLoader(ClassLoader parent){
045 super(parent);
046 }
047
048 private Class def(String name)throws ClassNotFoundException{
049
050 Class result = (Class)classes.get(name);
051 if(result != null){
052 return result;
053 }
054
055 try{
056
057 ClassLoader cl = this.getClass().getClassLoader();
058 String classFileName = name.replace('.','/') + ".class";
059 java.io.InputStream is = cl.getResourceAsStream(classFileName);
060 java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
061
062 while(is.available() > 0){
063 out.write(is.read());
064 }
065
066 byte data [] = out.toByteArray();
067
068 result = super.defineClass(name, data, 0, data.length );
069 classes.put(name,result);
070
071 return result;
072
073 }catch(java.io.IOException ioe){
074
075 throw new ClassNotFoundException( name + " caused by "
076 + ioe.getMessage() );
077 }
078
079
080 }
081
082 // not very trivial to emulate we must implement "findClass",
083 // but it will delegete to junit class loder first
084 public Class loadClass(String name)throws ClassNotFoundException{
085
086 //isolates all logging classes, application in the same classloader too.
087 //filters exeptions to simlify handling in test
088 for(int i = 0; i < LOG_PCKG.length; i++ ){
089 if( name.startsWith( LOG_PCKG[i] ) &&
090 name.indexOf("Exception") == -1 ){
091 return def(name);
092 }
093 }
094 return super.loadClass(name);
095 }
096
097 }
098
099
100 /**
101 * Call the static setAllowFlawedContext method on the specified class
102 * (expected to be a UserClass loaded via a custom classloader), passing
103 * it the specified state parameter.
104 */
105 private void setAllowFlawedContext(Class c, String state) throws Exception {
106 Class[] params = {String.class};
107 java.lang.reflect.Method m = c.getDeclaredMethod("setAllowFlawedContext", params);
108 m.invoke(null, new Object[] {state});
109 }
110
111 /**
112 * Test what happens when we play various classloader tricks like those
113 * that happen in web and j2ee containers.
114 * <p>
115 * Note that this test assumes that commons-logging.jar and log4j.jar
116 * are available via the system classpath.
117 */
118 public void testInContainer()throws Exception{
119
120 //problem can be in this step (broken app container or missconfiguration)
121 //1. Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
122 //2. Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
123 // we expect this :
124 // 1. Thread.currentThread().setContextClassLoader(appLoader);
125 // 2. Thread.currentThread().setContextClassLoader(null);
126
127 // Context classloader is same as class calling into log
128 Class cls = reload();
129 Thread.currentThread().setContextClassLoader(cls.getClassLoader());
130 execute(cls);
131
132 // Context classloader is the "bootclassloader". This is technically
133 // bad, but LogFactoryImpl.ALLOW_FLAWED_CONTEXT defaults to true so
134 // this test should pass.
135 cls = reload();
136 Thread.currentThread().setContextClassLoader(null);
137 execute(cls);
138
139 // Context classloader is the "bootclassloader". This is same as above
140 // except that ALLOW_FLAWED_CONTEXT is set to false; an error should
141 // now be reported.
142 cls = reload();
143 Thread.currentThread().setContextClassLoader(null);
144 try {
145 setAllowFlawedContext(cls, "false");
146 execute(cls);
147 fail("Logging config succeeded when context classloader was null!");
148 } catch(LogConfigurationException ex) {
149 // expected; the boot classloader doesn't *have* JCL available
150 }
151
152 // Context classloader is the system classloader.
153 //
154 // This is expected to cause problems, as LogFactoryImpl will attempt
155 // to use the system classloader to load the Log4JLogger class, which
156 // will then be unable to cast that object to the Log interface loaded
157 // via the child classloader. However as ALLOW_FLAWED_CONTEXT defaults
158 // to true this test should pass.
159 cls = reload();
160 Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
161 execute(cls);
162
163 // Context classloader is the system classloader. This is the same
164 // as above except that ALLOW_FLAWED_CONTEXT is set to false; an error
165 // should now be reported.
166 cls = reload();
167 Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
168 try {
169 setAllowFlawedContext(cls, "false");
170 execute(cls);
171 fail("Error: somehow downcast a Logger loaded via system classloader"
172 + " to the Log interface loaded via a custom classloader");
173 } catch(LogConfigurationException ex) {
174 // expected
175 }
176 }
177
178 /**
179 * Load class UserClass via a temporary classloader which is a child of
180 * the classloader used to load this test class.
181 */
182 private Class reload()throws Exception{
183
184 Class testObjCls = null;
185
186 AppClassLoader appLoader = new AppClassLoader(
187 this.getClass().getClassLoader());
188 try{
189
190 testObjCls = appLoader.loadClass(UserClass.class.getName());
191
192 }catch(ClassNotFoundException cnfe){
193 throw cnfe;
194 }catch(Throwable t){
195 t.printStackTrace();
196 fail("AppClassLoader failed ");
197 }
198
199 assertTrue( "app isolated" ,testObjCls.getClassLoader() == appLoader );
200
201
202 return testObjCls;
203
204
205 }
206
207
208 private void execute(Class cls)throws Exception{
209
210 cls.newInstance();
211
212 }
213
214
215 public static void main(String[] args){
216 String[] testCaseName = { LoadTestCase.class.getName() };
217 junit.textui.TestRunner.main(testCaseName);
218 }
219
220 public void setUp() {
221 // save state before test starts so we can restore it when test ends
222 origContextClassLoader = Thread.currentThread().getContextClassLoader();
223 }
224
225 public void tearDown() {
226 // restore original state so a test can't stuff up later tests.
227 Thread.currentThread().setContextClassLoader(origContextClassLoader);
228 }
229
230 private ClassLoader origContextClassLoader;
231 }