diff --git a/demonstration/README.txt b/demonstration/README.txt
new file mode 100644
index 0000000..723bac6
--- /dev/null
+++ b/demonstration/README.txt
@@ -0,0 +1,30 @@
+#
+# Copyright 2005 The Apache Software Foundation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+Contained are JCL Proof Of Concept Demonstrations
+
+The following jars must be added to this directory:
+
+Log4j.jar
+commons-logging.jar
+commons-logging-api.jar
+
+Create build directories by typing 'ant clean'
+Build by typing ant.
+
+Read the overview in the javadocs created under target/docs.
+
+When the time comes, run the demonstrations by typing 'ant run'
\ No newline at end of file
diff --git a/demonstration/build.xml b/demonstration/build.xml
new file mode 100644
index 0000000..b51ac8d
--- /dev/null
+++ b/demonstration/build.xml
@@ -0,0 +1,125 @@
+
+
+System.out.
+ */
+ public void runJCL() {
+ try {
+ SomeObject someObject = new SomeObject();
+ System.out.println("--------------------");
+ System.out.println("Logging to JCL:");
+ someObject.logToJCL();
+ System.out.println("JCL Logging OK");
+ } catch (Throwable t) {
+ System.out.println("JCL Logging FAILED: " + t.getClass());
+ System.out.println(t.getMessage());
+ System.out.println("");
+ }
+ }
+
+ /**
+ * Runs a test that logs to Log4J via static calls
+ * and outputs the results to System.out.
+ */
+ public void runStatic() {
+ try {
+ SomeObject someObject = new SomeObject();
+ System.out.println("--------------------");
+ System.out.println("Logging to Static:");
+ someObject.logToStaticLog4J();
+ System.out.println("Static Logging: OK");
+ } catch (Throwable t) {
+ System.out.println("Static Logging FAILED: " + t.getClass());
+ System.out.println(t.getMessage());
+ System.out.println("");
+ }
+ }
+}
diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/SomeObject.java b/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/SomeObject.java
new file mode 100644
index 0000000..ca0ec93
--- /dev/null
+++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/SomeObject.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.logging.proofofconcept.caller;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.proofofconcept.staticlogger.StaticLog4JLogger;
+
+/**
+ * This simulates some application or library code
+ * that uses logging.
+ * This separation allows tests to be run
+ * where this class is defined by either the parent
+ * or the child classloader.
+ */
+public class SomeObject {
+
+ /**
+ * Logs a message to Jakarta Commons Logging.
+ */
+ public void logToJCL() {
+ LogFactory.getLog("a log").info("A message");
+ }
+
+ /**
+ * Logs a message to Log4j via a class
+ * which makes a static call.
+ */
+ public void logToStaticLog4J() {
+ StaticLog4JLogger.info("A message");
+ }
+}
diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/package.html b/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/package.html
new file mode 100644
index 0000000..54aa603
--- /dev/null
+++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/package.html
@@ -0,0 +1,24 @@
+
+
Invokes logging +and takes the role of calling application code in these demonstrations. +This separation allows a greater variety of defining classloaders for +the calling application code to be simulated. +
+ + diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ChildFirstClassLoader.java b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ChildFirstClassLoader.java new file mode 100644 index 0000000..2c32552 --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ChildFirstClassLoader.java @@ -0,0 +1,52 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.logging.proofofconcept.runner; + +import java.net.URL; +import java.net.URLClassLoader; + +/** + * (Rather slack) implementation of a child first classloader. + * Should be fit for the purpose intended (which is demonstration) + * but a more complete and robust implementation should be + * preferred for more general purposes. + */ +public class ChildFirstClassLoader extends URLClassLoader { + + public ChildFirstClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + protected synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + + // very basic implementation + Class result = findLoadedClass(name); + if (result == null) { + try { + result = findClass(name); + if (resolve) { + resolveClass(result); + } + } catch (ClassNotFoundException e) { + result = super.loadClass(name, resolve); + } + } + + return result; + } + +} diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ChildFirstRunner.java b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ChildFirstRunner.java new file mode 100644 index 0000000..d69ab9d --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ChildFirstRunner.java @@ -0,0 +1,165 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.logging.proofofconcept.runner; + +import java.net.MalformedURLException; + + +/** + * Runs child first demonstrations. + * @see #main(String[]) + */ +public class ChildFirstRunner extends ClassLoaderRunner { + + + public ChildFirstRunner() throws MalformedURLException { + } + + + public void testCase17() { + int parentUrls = JCL_JAR + STATIC_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("17", parentUrls, childUrls, false, true); + } + + public void testCase18() { + int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("18", parentUrls, childUrls, false, true); + } + + public void testCase19() { + int parentUrls = JCL_JAR + STATIC_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("19", parentUrls, childUrls, true, true); + } + + public void testCase20() { + int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("20", parentUrls, childUrls, true, true); + } + + public void testCase21() { + int parentUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("21", parentUrls, childUrls, false, true); + } + + public void testCase22() { + int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("22", parentUrls, childUrls, false, true); + } + + public void testCase23() { + int parentUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("23", parentUrls, childUrls, true, true); + } + + public void testCase24() { + int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("24", parentUrls, childUrls, true, true); + } + + + public void testCase25() { + int parentUrls = API_JAR + STATIC_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("25", parentUrls, childUrls, false, true); + } + + public void testCase26() { + int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("26", parentUrls, childUrls, false, true); + } + + public void testCase27() { + int parentUrls = API_JAR + STATIC_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("27", parentUrls, childUrls, true, true); + } + + public void testCase28() { + int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("28", parentUrls, childUrls, true, true); + } + + public void testCase29() { + int parentUrls = API_JAR + STATIC_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("29", parentUrls, childUrls, false, true); + } + + public void testCase30() { + int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("30", parentUrls, childUrls, false, true); + } + + public void testCase31() { + int parentUrls = API_JAR + STATIC_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("31", parentUrls, childUrls, true, true); + } + + public void testCase32() { + int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("32", parentUrls, childUrls, true, true); + } + + /** + * Runs all child first cases. + * @param args + */ + public static void main(String[] args) + { + ChildFirstRunner runner; + try { + runner = new ChildFirstRunner(); + System.out.println(""); + System.out.println(""); + System.out.println("Running Child First Cases..."); + System.out.println(""); + System.out.println(""); + runner.testCase17(); + runner.testCase18(); + runner.testCase19(); + runner.testCase20(); + runner.testCase21(); + runner.testCase22(); + runner.testCase23(); + runner.testCase24(); + runner.testCase25(); + runner.testCase26(); + runner.testCase27(); + runner.testCase28(); + runner.testCase29(); + runner.testCase30(); + runner.testCase31(); + runner.testCase32(); + } catch (MalformedURLException e) { + System.out.println("Cannot find required jars"); + e.printStackTrace(); + } + + } +} diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ClassLoaderRunner.java b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ClassLoaderRunner.java new file mode 100644 index 0000000..21a8f94 --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ClassLoaderRunner.java @@ -0,0 +1,219 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.logging.proofofconcept.runner; + +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; + + +/** + * Runs demonstrations with complex classloader hierarchies + * and outputs formatted descriptions of the tests run. + */ +public class ClassLoaderRunner { + + private static final Class[] EMPTY_CLASSES = {}; + private static final Object[] EMPTY_OBJECTS = {}; + private static final URL[] EMPTY_URLS = {}; + + public static final int LOG4J_JAR = 1 << 0; + public static final int STATIC_JAR = 1 << 1; + public static final int JCL_JAR = 1 << 2; + public static final int CALLER_JAR = 1 << 3; + public static final int API_JAR = 1 << 4; + + private final URL log4jUrl; + private final URL staticUrl; + private final URL jclUrl; + private final URL callerUrl; + private final URL apiUrl; + + /** + * Loads URLs. + * @throws MalformedURLException when any of these URLs cannot + * be resolved + */ + public ClassLoaderRunner() throws MalformedURLException { + log4jUrl = new URL("file:log4j.jar"); + staticUrl = new URL("file:static.jar"); + jclUrl = new URL("file:commons-logging.jar"); + callerUrl = new URL("file:caller.jar"); + apiUrl = new URL("file:commons-logging-api.jar"); + } + + /** + * Runs a demonstration. + * @param caseName human name for test (used for output) + * @param parentJars bitwise code for jars to be definable + * by the parent classloader + * @param childJars bitwise code for jars to be definable + * by the child classloader + * @param setContextClassloader true if the context classloader + * should be set to the child classloader, + * false preserves the default + * @param childFirst true if the child classloader + * should delegate only if it cannot define a class, + * false otherwise + */ + public void run(String caseName, int parentJars, int childJars, + boolean setContextClassloader, boolean childFirst) { + + System.out.println(""); + System.out.println("*****************************"); + System.out.println(""); + System.out.println("Running case " + caseName + "..."); + System.out.println(""); + URL[] parentUrls = urlsForJars(parentJars, "Parent Classloader: "); + URL[] childUrls = urlsForJars(childJars, "Child Classloader: "); + System.out.println("Child context classloader: " + setContextClassloader); + System.out.println("Child first: " + childFirst); + System.out.println(""); + run("org.apache.commons.logging.proofofconcept.caller.JCLDemonstrator", + parentUrls, childUrls, setContextClassloader, childFirst); + System.out.println("*****************************"); + } + + /** + * Converts a bitwise jar code into an array of URLs + * containing approapriate URLs. + * @param jars bitwise jar code + * @param humanLoaderName human name for classloader + * @returnURL array, not null possibly empty
+ */
+ private URL[] urlsForJars(int jars, String humanLoaderName) {
+ List urls = new ArrayList();;
+ if ((LOG4J_JAR & jars) > 0) {
+ urls.add(log4jUrl);
+ }
+ if ((STATIC_JAR & jars) > 0) {
+ urls.add(staticUrl);
+ }
+ if ((JCL_JAR & jars) > 0) {
+ urls.add(jclUrl);
+ }
+ if ((API_JAR & jars) > 0) {
+ urls.add(apiUrl);
+ }
+ if ((CALLER_JAR & jars) > 0) {
+ urls.add(callerUrl);
+ }
+ System.out.println(humanLoaderName + " " + urls);
+ URL[] results = (URL[]) urls.toArray(EMPTY_URLS);
+ return results;
+ }
+
+ /**
+ * Runs a demonstration.
+ * @param testName the human name for this test
+ * @param parentClassloaderUrls the URL's which should
+ * be definable by the parent classloader, not null
+ * @param childClassloaderUrls the URL's which should
+ * be definable by the child classloader, not null
+ * @param setContextClassloader true if the context
+ * classloader should be set to the child classloader,
+ * false if the default context classloader should
+ * be maintained
+ * @param childFirst true if the child classloader
+ * should delegate only when it cannot define the class,
+ * false otherwise
+ */
+ public void run (String testName,
+ URL[] parentClassloaderUrls,
+ URL[] childClassloaderUrls,
+ boolean setContextClassloader,
+ boolean childFirst) {
+
+ URLClassLoader parent = new URLClassLoader(parentClassloaderUrls);
+ URLClassLoader child = null;
+ if (childFirst) {
+ child = new ChildFirstClassLoader(childClassloaderUrls, parent);
+ } else {
+ child = new URLClassLoader(childClassloaderUrls, parent);
+ }
+
+ if (setContextClassloader) {
+ Thread.currentThread().setContextClassLoader(child);
+ }
+
+ logDefiningLoaders(child, parent);
+
+ try {
+
+ Class callerClass = child.loadClass(testName);
+ Method runMethod = callerClass.getDeclaredMethod("run", EMPTY_CLASSES);
+ Object caller = callerClass.newInstance();
+ runMethod.invoke(caller, EMPTY_OBJECTS);
+
+ } catch (Exception e) {
+ System.out.println("Cannot execute test: " + e.getMessage());
+ e.printStackTrace();
+ }
+
+ }
+
+ /**
+ * Logs the classloaders which define important classes
+ * @param child child ClassLoader, not null
+ * @param parent parent ClassLoader, not null
+ */
+ private void logDefiningLoaders(ClassLoader child, ClassLoader parent)
+ {
+ System.out.println("");
+ logDefiningLoaders(child, parent, "org.apache.commons.logging.LogFactory", "JCL ");
+ logDefiningLoaders(child, parent, "org.apache.log4j.Logger", "Log4j ");
+ logDefiningLoaders(child, parent, "org.apache.commons.logging.proofofconcept.staticlogger.StaticLog4JLogger", "Static Logger");
+ logDefiningLoaders(child, parent, "org.apache.commons.logging.proofofconcept.caller.SomeObject", "Caller ");
+ System.out.println("");
+ }
+
+ /**
+ * Logs the classloader which defines the class with the given name whose
+ * loading is initiated by the child classloader.
+ * @param child child ClassLoader, not null
+ * @param parent parent ClassLoader, not null
+ * @param className name of the class to be loaded
+ * @param humanName the human name for the class
+ */
+ private void logDefiningLoaders(ClassLoader child, ClassLoader parent, String className, String humanName) {
+ try {
+ Class clazz = child.loadClass(className);
+ ClassLoader definingLoader = clazz.getClassLoader();
+ if (definingLoader == null)
+ {
+ System.out.println(humanName + " defined by SYSTEM class loader");
+ }
+ else if (definingLoader.equals(child))
+ {
+ System.out.println(humanName + " defined by CHILD class loader");
+ }
+ else if (definingLoader.equals(parent))
+ {
+ System.out.println(humanName + " defined by PARENT class loader");
+ }
+ else
+ {
+ System.out.println(humanName + " defined by OTHER class loader");
+ }
+ } catch (Exception e) {
+ System.out.println(humanName + " NOT LOADABLE by application classloader");
+ }
+ }
+
+}
diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ParentFirstRunner.java b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ParentFirstRunner.java
new file mode 100644
index 0000000..5c102ae
--- /dev/null
+++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ParentFirstRunner.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.logging.proofofconcept.runner;
+
+import java.net.MalformedURLException;
+
+
+/**
+ * Runs parent first demonstrations.
+ * @see #main(String[])
+ */
+public class ParentFirstRunner extends ClassLoaderRunner {
+
+
+ public ParentFirstRunner() throws MalformedURLException {
+ }
+
+ public void testCase1() {
+ int parentUrls = JCL_JAR + STATIC_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR;
+ run("1", parentUrls, childUrls, false, false);
+ }
+
+ public void testCase2() {
+ int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR;
+ run("2", parentUrls, childUrls, false, false);
+ }
+
+ public void testCase3() {
+ int parentUrls = JCL_JAR + STATIC_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR;
+ run("3", parentUrls, childUrls, true, false);
+ }
+
+ public void testCase4() {
+ int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR;
+ run("4", parentUrls, childUrls, true, false);
+ }
+
+ public void testCase5() {
+ int parentUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR;
+ run("5", parentUrls, childUrls, false, false);
+ }
+
+ public void testCase6() {
+ int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR;
+ run("6", parentUrls, childUrls, false, false);
+ }
+
+ public void testCase7() {
+ int parentUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR;
+ run("7", parentUrls, childUrls, true, false);
+ }
+
+ public void testCase8() {
+ int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR;
+ run("8", parentUrls, childUrls, true, false);
+ }
+
+
+ public void testCase9() {
+ int parentUrls = API_JAR + STATIC_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR;
+ run("9", parentUrls, childUrls, false, false);
+ }
+
+ public void testCase10() {
+ int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR;
+ run("10", parentUrls, childUrls, false, false);
+ }
+
+ public void testCase11() {
+ int parentUrls = API_JAR + STATIC_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR;
+ run("11", parentUrls, childUrls, true, false);
+ }
+
+ public void testCase12() {
+ int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR;
+ run("12", parentUrls, childUrls, true, false);
+ }
+
+ public void testCase13() {
+ int parentUrls = API_JAR + STATIC_JAR + LOG4J_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR;
+ run("13", parentUrls, childUrls, false, false);
+ }
+
+ public void testCase14() {
+ int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR;
+ run("14", parentUrls, childUrls, false, false);
+ }
+
+ public void testCase15() {
+ int parentUrls = API_JAR + STATIC_JAR + LOG4J_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR;
+ run("15", parentUrls, childUrls, true, false);
+ }
+
+ public void testCase16() {
+ int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR;
+ int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR;
+ run("16", parentUrls, childUrls, true, false);
+ }
+
+ /**
+ * Runs all parent first cases.
+ * @param args
+ */
+ public static void main(String[] args)
+ {
+ ParentFirstRunner runner;
+ try {
+ runner = new ParentFirstRunner();
+
+ System.out.println("");
+ System.out.println("");
+ System.out.println("Running Parent First Cases...");
+ System.out.println("");
+ System.out.println("");
+ runner.testCase1();
+ runner.testCase2();
+ runner.testCase3();
+ runner.testCase4();
+ runner.testCase5();
+ runner.testCase6();
+ runner.testCase7();
+ runner.testCase8();
+ runner.testCase9();
+ runner.testCase10();
+ runner.testCase11();
+ runner.testCase12();
+ runner.testCase13();
+ runner.testCase14();
+ runner.testCase15();
+ runner.testCase16();
+ } catch (MalformedURLException e) {
+ System.out.println("Cannot find required jars");
+ e.printStackTrace();
+ }
+
+ }
+}
diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/package.html b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/package.html
new file mode 100644
index 0000000..94723ea
--- /dev/null
+++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/package.html
@@ -0,0 +1,23 @@
+
+
+Runs the demonstrations.
++Also contained is utility code that efficiently sets up the +various classloader hierarchies. +
+ diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/staticlogger/StaticLog4JLogger.java b/demonstration/src/java/org/apache/commons/logging/proofofconcept/staticlogger/StaticLog4JLogger.java new file mode 100644 index 0000000..8d8b2e8 --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/staticlogger/StaticLog4JLogger.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.logging.proofofconcept.staticlogger; + +import org.apache.log4j.Logger; + + + +/** + * This simulates a simple, static binding to Log4J. + */ +public class StaticLog4JLogger { + + public static void info(String message) { + // could have got the logger at the start + Logger.getLogger("Whatever").info(message); + } + +} diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/staticlogger/package.html b/demonstration/src/java/org/apache/commons/logging/proofofconcept/staticlogger/package.html new file mode 100644 index 0000000..8e081c8 --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/staticlogger/package.html @@ -0,0 +1,25 @@ + + +Calls Log4j. Uses a symbolic reference. +
++This allows practical demonstrations of the behaviour of code +that calls Log4J statically when placed in similar +classloader circumstances of JCL. +
+ diff --git a/demonstration/src/java/overview.html b/demonstration/src/java/overview.html new file mode 100644 index 0000000..e6b34c2 --- /dev/null +++ b/demonstration/src/java/overview.html @@ -0,0 +1,832 @@ + + + + +This document contains analysis of various JCL use cases. It works +both as an educational document and as a specification (of sorts). +It's intended audience are (potential) JCL developers and (potential) +expert users. The code that accompanies +this document demonstrates the cases analysed in the text. +
+Familiarity with (advanced) class loading +concepts and terminology is assumed. Please digest the +JCL +Technology Guide before starting. +
+This approach was inspired by a JCL critique written by Ceki +Guici. +
+These demonstrations focus on discovery in general and discovery +of Log4J in particular. Not only is Log4J the most important target +but these are some of the most difficult and contentious cases. +Lessons learnt from these cases should easily and safely extrapolate +to other cases. +
+It is important that this document is as accurate as possible both +in facts and analysis. Readers are encouraged to contribute +corrections and improvements. Please either: +
+The basic set up for these demonstrations is intentionally simple. +There are no tricks that might obscure a clear view of the concepts. +The source code used to run these demonstrations is simple and should +be easy to understand. +
+Each demonstration is initiated by a runner. The runner loads the +other code components into appropriate ClassLoaders and then calls the caller. +
+The caller is used to simulate a class that needs to log +something. It is isolated from the code that runs the demonstration +in order to allow a greater variety of ClassLoader situations to be simulated. +The calling code is split +into one class that presents the formatted results and another that +actually performs the calls. Calls are made to JCL and to a static +logger. +
+The static logger simply contains a call directly to Log4J. This +is useful for comparison. +
+Now would be a good idea to study the javadocs. +
+Results are printed (with a little formatting) to System.out. The +run target in the ant build scripts runs all cases. It may be +difficult to run these demonstrations from an IDE (a clean system +ClassLoader is required). +
+This analysis will start by making a division between conventional +context classloaders and unconventional ones. Conventionally, the +context classloader for a thread executing code in a particular class +should either be the classloader used to load the class, an ancestor +of that classloader or a descendent of it. The conventional context +classloader cases will focus on context classloaders which obey these +rules. +
+The aim of the set up will be isolate the essentials. Only three +classloaders will be considered: +
+This situation is commonly encountered in containers (where the +child classloader is the application classloader). In practical +situations, the hierarchy is likely to be contain more classloaders +but it should be possible either to extrapolate from these simple +cases or reduce the more complex ones to these. +
+The analysis will proceed by considering two cases separately: +
+In the parent first cases, when the child classloader initiates +loading, it starts by delegating to the parent classloader. Only if +this classloader does not define the class will the child classloader +attempt to define the class. In the child first cases, the child +classloader will begin by attempting to define the class itself. Only +when it cannot define a class will it delegate to it's parents. +Further discussion of each of these cases will be contained in the +appropriate section. +
+The other variable is the setting of the context classloader. In +these cases, it will be set to either the system classloader (the +default) or the child classloader (the application classloader). +Perhaps the cases for setting to the parent classloader +should be added (but this would be unusual: conventionally the context +classloader should be either unset or set to the application +classloader). Contributions of these extra cases would be welcomed. +
+The cases will be presented in a matrix. This contains details of +the classloader set ups including which +which classes are definable by which loaders and how the context +classloader is set. It also contains the results of analysis about +the way that both the static logging and JCL (ideally) should behave. +
+For example +
+| Context ClassLoader | +System | +
|---|---|
| Parent | +JCL+Static | +
| Child | +
+ JCL+Static + Log4J + Caller + |
+
| Expected Result | +JCL->java.util + static FAIL + |
+
This describes a case where the context classloader is unset (and +so defaults to the system), the static.jar and commons-logging.jar +are definable by the parent classloader, the static.jar, +commons-logging.jar and log4j are definable by the child. The +caller.jar is also definable by the child classloader. The expected +result in this case is that JCL will discover java.util.logging and +that logging statically to Log4j will fail. +
+For purposes of reference, an indication is given in those cases +that correspond to examples used by Ceki in his critique. +
+Analytical notes explaining how these results were obtained are +included below each table. As the cases proceed, the notes will grow +more terse. +
+| Case | +1 | +2 | +3 | +4 | +
|---|---|---|---|---|
| Ceki Example | +- | +1 | +2A | +3 | +
| Context ClassLoader | +System | +System | +Child | +Child | +
| Parent | +JCL+Static | +JCL+Static Caller | +JCL+Static | +JCL+Static Caller | +
| Child | +
+ JCL+Static + Log4J + Caller |
+
+ JCL+Static + Log4J + |
+
+ JCL+Static + Log4J + Caller + |
+
+ JCL+Static + Log4J + |
+
| Expected Result | +
+ JCL->java.util + static FAIL + |
+
+ JCL->java.util + static FAIL + |
+
+ JCL->java.util + static FAIL + |
+
+ JCL->java.util + static FAIL + |
+
One important point to bear in mind when analysing parent-first +classloaders is that the presence or absence in the child classloader +of classes definable by the parent classloader produces no difference +in behaviour: the parent classloader will always load the class in +question. +
+In the cases above, Log4J is defined (only) by the child and all +JCL classes by the parent classloader. The symbolic references from +Log4JLogger to Log4J classes therefore cannot be resolved as Log4j is +not definable by the parent classloader. For the same reason, the +static call should also always fail. +
+The context classloader can be of no use (whether set or not) +since Log4JLogger will always be defined by the parent classloader +whether loading is initiated by the child or by the parent loader. +
+Therefore, in these cases, the appropriate behaviour is for JCL to +discover that java.util.logging is the only available logging system. +
+| Case | +5 | +6 | +7 | +8 | +
|---|---|---|---|---|
| Ceki Example | +- | +- | +- | +- | +
| Context ClassLoader | +System | +System | +Child | +Child | +
| Parent | +Log4J + JCL+Static + | Log4J + JCL+Static + Caller + | Log4J + JCL+Static + |
+ Log4J + JCL+Static + Caller + |
+
| Child | +JCL+Static + Log4J + Caller + |
+ JCL+Static + Log4J + |
+ JCL+Static + Log4J + Caller + |
+ JCL+Static + Log4J + |
+
| Expected Result + | +JCL->log4j + static OK + |
+ JCL->log4j + static OK + |
+
+ JCL->log4j + static OK + |
+
+ JCL->log4j + static OK + |
+
This demonstrates the usual way to fix the problems encountered +when Log4J is not definable by the loader that defines JCL: just add +the Log4j.jar into the classloader that defines JCL. +
+This is a very simple set of cases with JCL and static being able +to resolve the appropriate symbolic references to Log4j directly. +Whether the context classloader is set or not in these cases should +make no difference. +
+| Case | +9 | +10 | +11 | +12 | +
|---|---|---|---|---|
| Ceki Example | +- | +- | +- | +- | +
| Context ClassLoader | +System | +System | +Child | +Child | +
| Parent | +API+Static | +API+Static + Caller + |
+ API+Static + | +API+Static + Caller + |
+
| Child | +JCL+Static + Log4J + Caller + |
+ JCL+Static + Log4J + |
+ JCL+Static + Log4J + Caller + |
+ JCL+Static + Log4J + |
+
| Expected Result | +JCL->java.util + static FAIL + |
+ JCL->java.util + static FAIL + |
+ JCL->log4j + static FAIL + |
+ JCL->log4j + static FAIL + |
+
These demonstrate variations on the first series of cases where +Log4J is defined by the child. The placement of the static jar does +not vary (from the previous set) and again is expected to +consistently fail. +
+This time the API jar is placed in the parent classloader and the +full JCL jar in the child. This means that the symbolic reference +from the Log4JLogger class (contained in the full jar but not the +API) to Log4J classes can be resolved. +
+In these cases, whether JCL can succeed depends on the context +classloader. The delegation model employed by Java ClassLoaders +allows child classloaders to know about parents but not vice versa. +Therefore, the context classloader is the only means available to +attempt to load Log4JLogger. When the context classloader is set to +the child, this classloader defines Log4JLogger and Log4J. Therefore, +JCL should be able to discover Log4J (only) when the context +classloader is set to the child. +
+| Case | +13 | +14 | +15 | +16 | +
|---|---|---|---|---|
| Ceki Example | +- | +- | +- | +- | +
| Context ClassLoader | +System | +System | +Child | +Child | +
| Parent | +Log4J + API+Static + |
+ Log4J + API+Static + Caller + |
+ Log4J + API+Static + |
+ Log4J + API+Static + Caller + |
+
| Child | +JCL+Static + Log4J + Caller |
+ JCL+Static + Log4J |
+ JCL+Static + Log4J + Caller + |
+ JCL+Static + Log4J |
+
| Expected Result | +JCL->log4j + static OK |
+ JCL->log4j + static OK + |
+ JCL->log4j + static OK + |
+ JCL->log4j + static OK + |
+
Trivial variations on the second series. Now API and JCL are +definable only by parent and child respectively (as in the last +series). +
+The same classloader configuration can be repeated with (this +time) child-first classloading. +
+| Case | +17 | +18 | +19 | +20 | +
|---|---|---|---|---|
| Ceki Example | +- | +- | +5 | +6 | +
| Context ClassLoader | +System | +System | +Child | +Child | +
| Parent | +JCL+Static | +JCL+Static + Caller |
+ JCL+Static | +JCL+Static + Caller + |
+
| Child | +JCL+Static + Log4J + Caller + |
+ JCL+Static + Log4J + |
+ JCL+Static + Log4J + Caller + |
+ JCL+Static + Log4J + |
+
| Expected Result | +JCL->Log4j + static OK + |
+ JCL->java.util + static FAIL + |
+ JCL->Log4j + static OK + |
+ JCL->java.util + static FAIL + |
+
In child-first cases, the classloader which defines the caller +plays a more important role. When the caller is defined by the child +classloader, both static and JCL should succeed whether the context +classloader is set or not. +
+When the caller is defined by the parent classloader, this means +that the parent classloader will define the JCL and static classes +(rather than the child). Log4J is not defined by the parent loader +and so the static call must fail in both cases. +
+With a friendly context classloader, JCL can load an instance of +Log4JLogger whose symbolic references to Log4J can be resolved. +However, the child classloader which defines this class will resolve +the symbolic reference to Log to the class defined by the child +classloader rather than the Log class defined by the parent +classloader. They are not mutually accessible and so JCL must +discover java.util.logging. +
+| Case + | +21 | +22 | +23 | +24 | +
|---|---|---|---|---|
| Ceki Example | +- | +- | +- | +- | +
| Context ClassLoader | +System | +System | +Child | +Child | +
| Parent | +Log4J + JCL+Static + |
+ Log4J + JCL+Static + Caller + |
+ Log4J + JCL+Static + |
+ Log4J + JCL+Static + Caller + |
+
| Child | +JCL+Static + Log4J + Caller + |
+ JCL+Static + Log4J + |
+ JCL+Static + Log4J + Caller + |
+ JCL+Static + Log4J + |
+
| Expected Result | +JCL->log4j + static OK + |
+ JCL->log4j + static OK + |
+ JCL->log4j + static OK + |
+ JCL->log4j + static OK + |
+
Trivial case where jar's are definable by both loaders. +
+| Case | +25 | +26 | +27 | +28 | +
|---|---|---|---|---|
| Ceki Example | +- | +- | +- | +- | +
| Context ClassLoader | +System | +System | +Child | +Child | +
| Parent | +API+Static | +API+Static + Caller + |
+ API+Static | +API+Static + Caller + |
+
| Child | +JCL+Static + Log4J + Caller + |
+ JCL+Static + Log4J + |
+ JCL+Static + Log4J + Caller + |
+ JCL+Static + Log4J + |
+
| Expected Result | +JCL->log4j + static OK + |
+ JCL->java.util + static FAIL + |
+ JCL->log4j + static OK + |
+ JCL->java.util + static FAIL + |
+
(As above) to succeed, the static call requires that Log4J is +definable by the loader which defines the callers. JCL should +discover Log4J in the cases where the static call succeeds. +
+Even with a friendly context classloader, the Log class referenced +by the Log4JLogger defined by the child classloader will be +inaccessible to the caller (loader by the parent classloader). +
+| Case | +29 | +30 | +31 | +32 | +
|---|---|---|---|---|
| Ceki Example | +- | +- | +- | +- | +
| Context ClassLoader | +System | +System | +Child | +Child | +
| Parent | +Log4J + API+Static + |
+ Log4J + API+Static + Caller + |
+ Log4J + API+Static + |
+ Log4J + API+Static + Caller + |
+
| Child | +JCL+Static + Log4J + Caller + |
+ JCL+Static + Log4J + |
+ JCL+Static + Log4J + Caller + |
+ JCL+Static + Log4J + |
+
| Expected Result | +JCL->log4j + static OK + |
+ JCL->java.util + static OK + |
+ JCL->log4j + static OK + |
+ JCL->java.util + static OK + |
+
These results at first glance may seem a little confusing. The +issue for JCL is that classes needed to log to Log4J are only +definable by the child classloader. +
+Even with a friendly context classloader, JCL runs into the same +difficulties with accessibility that +occurred above. +
+ +