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;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.lang.reflect.InvocationTargetException;
22 import java.util.HashMap;
23 import java.util.Map;
24
25 import junit.framework.TestCase;
26
27 import org.apache.commons.io.IOUtils;
28
29 /**
30 * test to emulate container and application isolated from container
31 */
32 public class LoadTestCase extends TestCase {
33
34 /**
35 * A custom class loader which "duplicates" logging classes available
36 * in the parent class loader into itself.
37 * <p>
38 * When asked to load a class that is in one of the LOG_PCKG packages,
39 * it loads the class itself (child-first). This class doesn't need
40 * to be set up with a classpath, as it simply uses the same classpath
41 * as the class loader that loaded it.
42 */
43 static class AppClassLoader extends ClassLoader {
44
45 Map<String, Class<?>> classes = new HashMap<>();
46
47 AppClassLoader(final ClassLoader parent) {
48 super(parent);
49 }
50
51 private Class<?> def(final String name) throws ClassNotFoundException {
52 Class<?> result = classes.get(name);
53 if (result != null) {
54 return result;
55 }
56 try {
57 final ClassLoader cl = this.getClass().getClassLoader();
58 final String classFileName = name.replace('.', '/') + ".class";
59 try (InputStream is = cl.getResourceAsStream(classFileName)) {
60 final byte[] data = IOUtils.toByteArray(is);
61 result = super.defineClass(name, data, 0, data.length);
62 classes.put(name, result);
63 return result;
64 }
65 } catch (final IOException ioe) {
66 throw new ClassNotFoundException(name + " caused by " + ioe.getMessage());
67 }
68 }
69
70 // not very trivial to emulate we must implement "findClass",
71 // but it will delegate to JUnit class loader first
72 @Override
73 public Class<?> loadClass(final String name) throws ClassNotFoundException {
74 // isolates all logging classes, application in the same class loader too.
75 // filters exceptions to simplify handling in test
76 for (final String element : LOG_PCKG) {
77 if (name.startsWith(element) && !name.contains("Exception")) {
78 return def(name);
79 }
80 }
81 return super.loadClass(name);
82 }
83
84 }
85
86 //TODO: need some way to add service provider packages
87 static private String[] LOG_PCKG = {"org.apache.commons.logging",
88 "org.apache.commons.logging.impl"};
89
90 private ClassLoader origContextClassLoader;
91
92 private void execute(final Class<?> cls) throws Exception {
93 cls.getConstructor().newInstance();
94 }
95
96 /**
97 * Load class UserClass via a temporary class loader which is a child of
98 * the class loader used to load this test class.
99 */
100 private Class<?> reload() throws Exception {
101 Class<?> testObjCls = null;
102 final AppClassLoader appLoader = new AppClassLoader(this.getClass().getClassLoader());
103 try {
104
105 testObjCls = appLoader.loadClass(UserClass.class.getName());
106
107 } catch (final ClassNotFoundException cnfe) {
108 throw cnfe;
109 } catch (final Throwable t) {
110 t.printStackTrace();
111 fail("AppClassLoader failed ");
112 }
113
114 assertSame("app isolated", testObjCls.getClassLoader(), appLoader);
115
116 return testObjCls;
117
118 }
119
120 /**
121 * Call the static setAllowFlawedContext method on the specified class
122 * (expected to be a UserClass loaded via a custom class loader), passing
123 * it the specified state parameter.
124 */
125 private void setAllowFlawedContext(final Class<?> c, final String state) throws Exception {
126 final Class<?>[] params = {String.class};
127 final java.lang.reflect.Method m = c.getDeclaredMethod("setAllowFlawedContext", params);
128 m.invoke(null, state);
129 }
130
131 @Override
132 public void setUp() {
133 // save state before test starts so we can restore it when test ends
134 origContextClassLoader = Thread.currentThread().getContextClassLoader();
135 }
136
137 @Override
138 public void tearDown() {
139 // restore original state so a test can't stuff up later tests.
140 Thread.currentThread().setContextClassLoader(origContextClassLoader);
141 }
142
143 /**
144 * Test what happens when we play various class loader tricks like those
145 * that happen in web and j2ee containers.
146 * <p>
147 * Note that this test assumes that commons-logging.jar and log4j.jar
148 * are available via the system classpath.
149 */
150 public void testInContainer() throws Exception {
151
152 //problem can be in this step (broken app container or missconfiguration)
153 //1. Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
154 //2. Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
155 // we expect this :
156 // 1. Thread.currentThread().setContextClassLoader(appLoader);
157 // 2. Thread.currentThread().setContextClassLoader(null);
158
159 // Context class loader is same as class calling into log
160 Class<?> cls = reload();
161 Thread.currentThread().setContextClassLoader(cls.getClassLoader());
162 execute(cls);
163
164 // Context class loader is the "bootclass loader". This is technically
165 // bad, but LogFactoryImpl.ALLOW_FLAWED_CONTEXT defaults to true so
166 // this test should pass.
167 cls = reload();
168 Thread.currentThread().setContextClassLoader(null);
169 execute(cls);
170
171 // Context class loader is the "bootclass loader". This is same as above
172 // except that ALLOW_FLAWED_CONTEXT is set to false; an error should
173 // now be reported.
174 cls = reload();
175 Thread.currentThread().setContextClassLoader(null);
176 try {
177 setAllowFlawedContext(cls, "false");
178 execute(cls);
179 fail("Logging config succeeded when context class loader was null!");
180 } catch (final InvocationTargetException ex) {
181 final Throwable targetException = ex.getTargetException();
182 // LogConfigurationException is expected; the boot class loader doesn't *have* JCL available
183 if (!(targetException instanceof LogConfigurationException)) {
184 throw ex;
185 }
186 }
187
188 // Context class loader is the system class loader.
189 //
190 // This is expected to cause problems, as LogFactoryImpl will attempt
191 // to use the system class loader to load the Log4JLogger class, which
192 // will then be unable to cast that object to the Log interface loaded
193 // via the child class loader. However as ALLOW_FLAWED_CONTEXT defaults
194 // to true this test should pass.
195 cls = reload();
196 Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
197 execute(cls);
198
199 // Context class loader is the system class loader. This is the same
200 // as above except that ALLOW_FLAWED_CONTEXT is set to false; an error
201 // should now be reported.
202 cls = reload();
203 Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
204 try {
205 setAllowFlawedContext(cls, "false");
206 execute(cls);
207 fail("Error: somehow downcast a Logger loaded via system class loader"
208 + " to the Log interface loaded via a custom class loader");
209 } catch (final InvocationTargetException ex) {
210 final Throwable targetException = ex.getTargetException();
211 // LogConfigurationException is expected
212 if (!(targetException instanceof LogConfigurationException)) {
213 throw ex;
214 }
215 }
216 }
217 }