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