commit e8a71d016ca4ddfce35879572016de7fb706c1ad Author: zhouganqing Date: Thu Nov 17 20:37:50 2022 +0800 Import Upstream version 1.2 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000..556bd03 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,6 @@ +Apache Commons Logging +Copyright 2003-2014 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + diff --git a/PROPOSAL.html b/PROPOSAL.html new file mode 100644 index 0000000..45e40d3 --- /dev/null +++ b/PROPOSAL.html @@ -0,0 +1,121 @@ + + + + +Proposal for Logging Library Package + + + +
+

Proposal for Logging Package

+
+ +

(0) Rationale

+ +

There is a great need for debugging and logging information inside of +Commons components such as HTTPClient and dbcp. However, there are many +logging APIs out there and it is difficult to choose among them. +

+ +

The Logging package will be an ultra-thin bridge between different logging +libraries. Commons components may use the Logging JAR to remove +compile-time/runtime dependencies on any particular logging package, +and contributors may write Log implementations for the library of their choice. +

+ +

(1) Scope of the Package

+ +

The package shall create and maintain a package that provides extremely +basic logging functionality and bridges to other, more sophisticated logging +implementations. +

+ +

+The package should : +

+

+ +

+Non-goals: +

+

+ +

(1.5) Interaction With Other Packages

+ +

Logging relies on: +

+ + + +

(2) Required Jakarta-Commons Resources

+ + + + +

(4) Initial Committers

+ +

The initial committers on the Logging component shall be:

+ + + + + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..9394f1f --- /dev/null +++ b/README.txt @@ -0,0 +1,50 @@ + + +Logging +------- + +Getting started: + +1) Build the jar file + + If you have the source distribution you will need to build the jar file + using Maven 2/3. For instructions on downloading and installing Maven see + http://maven.apache.org/. + + To build execute the command 'mvn package verify'. + The jar file will be built in the target directory. + + Note: the unit tests are executed during the verify phase and require that + the respective jar files have been created successfully. + +2) Generate the documentation + + Run the 'mvn verify site' command. The documentation will be written + to the target/site directory. The documentation has some examples of + how to use this package as well as a troubleshooting guide. + +3) Create source and binary distributions + + Run the 'mvn verify site assembly:assembly' command. The source and binary + distributions are created in the 'target' directory. + +4) Use + + Simply include the jar file built in step #1 in your classpath. Import the + classes that you want to use and you are ready to go! + diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt new file mode 100644 index 0000000..6376134 --- /dev/null +++ b/RELEASE-NOTES.txt @@ -0,0 +1,31 @@ + Apache Commons Logging + Version 1.2 + RELEASE NOTES + +The Apache Commons Logging team is pleased to announce +the release of Apache Commons Logging 1.2 + +Apache Commons Logging is a thin adapter allowing configurable +bridging to other, well known logging systems. + +This is a maintenance release containing bug fixes. +Java 1.2 or later is required. + +Changes in this version include: + +Fixed Bugs: +o LOGGING-37: Improve performance of LogFactory#getFactory() by calling + Thread#currentThread()#getContextClassLoader() directly instead + of using reflection. As a consequence support for JDK 1.1 has + been dropped. Thanks to Matthias Ernst, Archie Cobbs. +o LOGGING-156: Fix SecurityAllowedTestCase when executed with OpenJDK 1.7 due + to an additional required RuntimePermission. Thanks to Mikolaj Izdebski. +o LOGGING-157: Fix javadoc to comply with javadoc tool from jdk 1.8. Thanks to Ville Skyttä. + + +Historical list of changes: http://commons.apache.org/proper/commons-logging/changes-report.html + +For complete information on Apache Commons Logging, including instructions on how to submit bug reports, +patches, or suggestions for improvement, see the Apache Commons Logging website: + +http://commons.apache.org/proper/commons-logging/ \ No newline at end of file diff --git a/build-testing.xml b/build-testing.xml new file mode 100644 index 0000000..e06112c --- /dev/null +++ b/build-testing.xml @@ -0,0 +1,350 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Log4j12: ${log4j12.jar} + + + LogKit: ${logkit.jar} + Avalon-Framework: ${avalon-framework.jar} + + + + + + + + + + + + + + + + + + + + + + + + *** WARNING *** + Log4J 1.2.x Jar not found: Cannot execute 1.2.x tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + One or more unit tests failed. + + + + diff --git a/build.properties.sample b/build.properties.sample new file mode 100644 index 0000000..a9f1afc --- /dev/null +++ b/build.properties.sample @@ -0,0 +1,59 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +###################################################################### +# +# TO USE: +# +# Copy this file to build.properties and either +# +# a) Use 'ant getlibs' to populate the default directory +# with dependencies +# or b) Change the property values to appropriate values +# +######################################################################## + +# Apache Log4j 1.2.x series +log4j12.jar=lib/log4j-1.2.12.jar + +# Apache Log4j 1.3.x series +# Note: Log4j 1.3 support not available in the 1.1 release +#log4j13.jar=lib/log4j-1.3.0.jar + +# logkit.jar - Avalon LogKit classes (see http://jakarta.apache.org/avalon) +logkit.jar=lib/logkit-1.0.1.jar + +# Avalon framework - used for wrapper for avalon framework logger +avalon-framework.jar=lib/avalon-framework-4.1.3.jar + +# ServletApi - used to build ServletContextCleaner class +servletapi.jar=lib/servletapi-2.3.jar + +# +# if you want to run the test cases, junit needs to be in the classpath. +# the build.xml uses a default value so you might not need to set this property. +# Note that version junit 3.8 is required and 3.8.1 recommended. +# +junit.jar=lib/junit-3.8.1.jar + +# Maven properties (for web site build) +# Those committers using agents may like to use +#maven.username=rdonkin +#logging.cvs=lserver:rdonkin@cvs.apache.org:/home/cvs + + +# The path to a 1.4 JSDK javac +# Optional - used when building with a 1.2 JVM for releases +# executable.javac1.4=/opt/java/jdks/j2sdk1.4.2_10/bin/javac diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..f7a5923 --- /dev/null +++ b/build.xml @@ -0,0 +1,776 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Log4j12: ${log4j12.jar} + + + LogKit: ${logkit.jar} + Avalon-Framework: ${avalon-framework.jar} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + *** WARNING *** + Log4j 1.2 not found: Cannot Build Log4JLogger + + + + + + + + + + + *** WARNING *** + LogKit not found: Cannot Build LogKitLogger + + + + + + *** WARNING *** + Avalon-Framework not found: Cannot Build AvalonLogger + + + + + + *** WARNING *** + JDK 1.4 not present: Cannot Build Jdk14Logger + + + + + + *** WARNING *** + Log4J 1.2.x Jar not found: Cannot execute 1.2.x tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + *** WARNING *** + Maven generated documentation not found: Documentation distribution will be empty + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + One or more unit tests failed. + + + + diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 0000000..cec133f --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/license-header.txt b/license-header.txt new file mode 100644 index 0000000..ae6f28c --- /dev/null +++ b/license-header.txt @@ -0,0 +1,16 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ diff --git a/pmd.xml b/pmd.xml new file mode 100644 index 0000000..ceefc84 --- /dev/null +++ b/pmd.xml @@ -0,0 +1,26 @@ + + + + Excludes from default PMD rules. + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..cdad31c --- /dev/null +++ b/pom.xml @@ -0,0 +1,547 @@ + + + + + org.apache.commons + commons-parent + 34 + + 4.0.0 + commons-logging + commons-logging + Apache Commons Logging + 1.2 + Apache Commons Logging is a thin adapter allowing configurable bridging to other, + well known logging systems. + http://commons.apache.org/proper/commons-logging/ + + + JIRA + http://issues.apache.org/jira/browse/LOGGING + + + 2001 + + + + baliuka + Juozas Baliuka + baliuka@apache.org + + Java Developer + + + + morgand + Morgan Delagrange + morgand@apache.org + Apache + + Java Developer + + + + donaldp + Peter Donald + donaldp@apache.org + + + rdonkin + Robert Burrell Donkin + rdonkin@apache.org + The Apache Software Foundation + + + skitching + Simon Kitching + skitching@apache.org + The Apache Software Foundation + + + dennisl + Dennis Lundberg + dennisl@apache.org + The Apache Software Foundation + + + costin + Costin Manolache + costin@apache.org + The Apache Software Foundation + + + craigmcc + Craig McClanahan + craigmcc@apache.org + The Apache Software Foundation + + + tn + Thomas Neidhart + tn@apache.org + The Apache Software Foundation + + + sanders + Scott Sanders + sanders@apache.org + The Apache Software Foundation + + + rsitze + Richard Sitze + rsitze@apache.org + The Apache Software Foundation + + + bstansberry + Brian Stansberry + + + rwaldhoff + Rodney Waldhoff + rwaldhoff@apache.org + The Apache Software Foundation + + + + + Matthew P. Del Buono + + Provided patch + + + + Vince Eagen + vince256 at comcast dot net + + Lumberjack logging abstraction + + + + Peter Lawrey + + Provided patch + + + + Berin Loritsch + bloritsch at apache dot org + + Lumberjack logging abstraction + JDK 1.4 logging abstraction + + + + Philippe Mouawad + + Provided patch + + + + Neeme Praks + neeme at apache dot org + + Avalon logging abstraction + + + + + + + scm:svn:http://svn.apache.org/repos/asf/commons/proper/logging/trunk + scm:svn:https://svn.apache.org/repos/asf/commons/proper/logging/trunk + http://svn.apache.org/repos/asf/commons/proper/logging/trunk + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + testjar + package + + test-jar + + + commons-logging + + + + + apijar + package + + jar + + + ${project.artifactId}-api-${project.version} + + org/apache/commons/logging/*.class + org/apache/commons/logging/impl/LogFactoryImpl*.class + org/apache/commons/logging/impl/WeakHashtable*.class + org/apache/commons/logging/impl/SimpleLog*.class + org/apache/commons/logging/impl/NoOpLog*.class + org/apache/commons/logging/impl/Jdk14Logger.class + META-INF/LICENSE.txt + META-INF/NOTICE.txt + + + **/package.html + + + + + + adaptersjar + package + + jar + + + ${project.artifactId}-adapters-${project.version} + + org/apache/commons/logging/impl/**.class + META-INF/LICENSE.txt + META-INF/NOTICE.txt + + + org/apache/commons/logging/impl/WeakHashtable*.class + org/apache/commons/logging/impl/LogFactoryImpl*.class + + + + + + + fulljar + package + + jar + + + ${project.artifactId}-${project.version} + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + site.resources + site + + + + + + + + + + + run + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.0 + + + attach-artifacts + package + + attach-artifact + + + + + ${project.build.directory}/${project.artifactId}-adapters-${project.version}.jar + jar + adapters + + + ${project.build.directory}/${project.artifactId}-api-${project.version}.jar + jar + api + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.codehaus.mojo + cobertura-maven-plugin + ${commons.cobertura.version} + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${commons.surefire.version} + + + integration-test + + integration-test + verify + + + ${failsafe.runorder} + + **/*TestCase.java + + + + ${log4j:log4j:jar} + ${logkit:logkit:jar} + ${javax.servlet:servlet-api:jar} + target/${project.build.finalName}.jar + target/${project.artifactId}-api-${project.version}.jar + target/${project.artifactId}-adapters-${project.version}.jar + target/commons-logging-tests.jar + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.3 + + + src/main/assembly/bin.xml + src/main/assembly/src.xml + + gnu + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.4 + + + + properties + + + + + + + org.apache.maven.plugins + maven-scm-publish-plugin + + + javadocs + commons-logging-** + + + + + + + + + + junit + junit + 3.8.1 + test + + + log4j + log4j + 1.2.17 + true + + + logkit + logkit + 1.0.1 + true + + + avalon-framework + avalon-framework + 4.1.5 + true + + + javax.servlet + servlet-api + 2.3 + provided + true + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.7 + + ${basedir}/checkstyle.xml + false + ${basedir}/license-header.txt + + + + org.codehaus.mojo + clirr-maven-plugin + 2.2.2 + + + org.codehaus.mojo + jdepend-maven-plugin + 2.0-beta-1 + + + org.codehaus.mojo + findbugs-maven-plugin + 2.5.2 + + true + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.0.1 + + + 1.3 + true + + ${basedir}/pmd.xml + + + + + + + + + apache.website + ${commons.deployment.protocol}://people.apache.org/www/commons.apache.org/logging/ + + + + + 1.2 + 1.2 + logging + 1.2 + LOGGING + 12310484 + + RC2 + 2.12 + true + + filesystem + + + javax.servlet;version="[2.1.0, 3.0.0)";resolution:=optional, + org.apache.avalon.framework.logger;version="[4.1.3, 4.1.5]";resolution:=optional, + org.apache.log;version="[1.0.1, 1.0.1]";resolution:=optional, + org.apache.log4j;version="[1.2.15, 2.0.0)";resolution:=optional + + + diff --git a/src/changes/changes.xml b/src/changes/changes.xml new file mode 100644 index 0000000..3153d82 --- /dev/null +++ b/src/changes/changes.xml @@ -0,0 +1,112 @@ + + + + + + + Release Notes + + + + + Improve performance of LogFactory#getFactory() by calling Thread#currentThread()#getContextClassLoader() + directly instead of using reflection. As a consequence support for JDK 1.1 has been dropped. + + + Fix SecurityAllowedTestCase when executed with OpenJDK 1.7 due to an additional required RuntimePermission. + + + Fix javadoc to comply with javadoc tool from jdk 1.8. + + + + + Use "org.apache.commons.logging" as bundle symbolic name. + + + + + The jar manifest now contains proper OSGi-related metadata information. + + + LogFactory and LogFactoryImpl will not swallow certain errors anymore (ThreadDeath + and VirtualMachineError). + + + Improved thread-safety for several log adapters, including AvalonLogger, SimpleLog, + Log4JLogger, LogKitLogger. + + + In case of a discovery failure now also the stacktrace of the cause will be + added to the diagnostic message. + + + Jdk14Logger now correctly uses the specified logger name. + + + Change scope of Jdk14Logger.log(Level, String, Throwable) to protected, allowing + subclasses to modify the logging output. + + + Properly synchronize access to protected static field LogFactory.nullClassLoaderFactory. + + + Prevent potential deadlock scenario in WeakHashtable. + + + Potential missing privileged block for class loader. + + + LogFactoryImpl.setAttribute - possible NPE. + + + Log4JLogger uses deprecated static members of Priority such as INFO. + + + Static analysis suggests a number of potential improvements. + + + SimpleLog.log - unsafe update of shortLogName. + + + LogFactory.diagnosticPrefix and diagnosticsStream could be final. + + + + diff --git a/src/changes/release-notes.vm b/src/changes/release-notes.vm new file mode 100644 index 0000000..c0e2f57 --- /dev/null +++ b/src/changes/release-notes.vm @@ -0,0 +1,109 @@ +## Licensed to the Apache Software Foundation (ASF) under one +## or more contributor license agreements. See the NOTICE file +## distributed with this work for additional information +## regarding copyright ownership. The ASF licenses this file +## to you 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. + Apache ${project.name} + Version ${version} + RELEASE NOTES + +The ${developmentTeam} is pleased to announce the release of Apache ${project.name} ${version} + +$introduction.replaceAll("(? + + + bin + + tar.gz + zip + + false + + + + LICENSE* + NOTICE* + RELEASE-NOTES* + + + + target + + + *.jar + + + + target/site/apidocs + apidocs + + + \ No newline at end of file diff --git a/src/main/assembly/src.xml b/src/main/assembly/src.xml new file mode 100644 index 0000000..2de93ab --- /dev/null +++ b/src/main/assembly/src.xml @@ -0,0 +1,52 @@ + + + + src + + tar.gz + zip + + ${project.artifactId}-${project.version}-src + + + + checkstyle.xml + README.txt + LICENSE.txt + NOTICE.txt + pmd.xml + pom.xml + PROPOSAL.html + RELEASE-NOTES.txt + STATUS.txt + build.xml + build-testing.xml + build.properties.sample + license-header.txt + + + + src + + **/download*.cgi + + + + \ No newline at end of file diff --git a/src/main/java/org/apache/commons/logging/Log.java b/src/main/java/org/apache/commons/logging/Log.java new file mode 100644 index 0000000..5c5523d --- /dev/null +++ b/src/main/java/org/apache/commons/logging/Log.java @@ -0,0 +1,216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +/** + * A simple logging interface abstracting logging APIs. In order to be + * instantiated successfully by {@link LogFactory}, classes that implement + * this interface must have a constructor that takes a single String + * parameter representing the "name" of this Log. + *

+ * The six logging levels used by Log are (in order): + *

    + *
  1. trace (the least serious)
  2. + *
  3. debug
  4. + *
  5. info
  6. + *
  7. warn
  8. + *
  9. error
  10. + *
  11. fatal (the most serious)
  12. + *
+ * The mapping of these log levels to the concepts used by the underlying + * logging system is implementation dependent. + * The implementation should ensure, though, that this ordering behaves + * as expected. + *

+ * Performance is often a logging concern. + * By examining the appropriate property, + * a component can avoid expensive operations (producing information + * to be logged). + *

+ * For example, + *

+ *    if (log.isDebugEnabled()) {
+ *        ... do something expensive ...
+ *        log.debug(theResult);
+ *    }
+ * 
+ *

+ * Configuration of the underlying logging system will generally be done + * external to the Logging APIs, through whatever mechanism is supported by + * that system. + * + * @version $Id: Log.java 1606045 2014-06-27 12:11:56Z tn $ + */ +public interface Log { + + /** + * Logs a message with debug log level. + * + * @param message log this message + */ + void debug(Object message); + + /** + * Logs an error with debug log level. + * + * @param message log this message + * @param t log this cause + */ + void debug(Object message, Throwable t); + + /** + * Logs a message with error log level. + * + * @param message log this message + */ + void error(Object message); + + /** + * Logs an error with error log level. + * + * @param message log this message + * @param t log this cause + */ + void error(Object message, Throwable t); + + /** + * Logs a message with fatal log level. + * + * @param message log this message + */ + void fatal(Object message); + + /** + * Logs an error with fatal log level. + * + * @param message log this message + * @param t log this cause + */ + void fatal(Object message, Throwable t); + + /** + * Logs a message with info log level. + * + * @param message log this message + */ + void info(Object message); + + /** + * Logs an error with info log level. + * + * @param message log this message + * @param t log this cause + */ + void info(Object message, Throwable t); + + /** + * Is debug logging currently enabled? + *

+ * Call this method to prevent having to perform expensive operations + * (for example, String concatenation) + * when the log level is more than debug. + * + * @return true if debug is enabled in the underlying logger. + */ + boolean isDebugEnabled(); + + /** + * Is error logging currently enabled? + *

+ * Call this method to prevent having to perform expensive operations + * (for example, String concatenation) + * when the log level is more than error. + * + * @return true if error is enabled in the underlying logger. + */ + boolean isErrorEnabled(); + + /** + * Is fatal logging currently enabled? + *

+ * Call this method to prevent having to perform expensive operations + * (for example, String concatenation) + * when the log level is more than fatal. + * + * @return true if fatal is enabled in the underlying logger. + */ + boolean isFatalEnabled(); + + /** + * Is info logging currently enabled? + *

+ * Call this method to prevent having to perform expensive operations + * (for example, String concatenation) + * when the log level is more than info. + * + * @return true if info is enabled in the underlying logger. + */ + boolean isInfoEnabled(); + + /** + * Is trace logging currently enabled? + *

+ * Call this method to prevent having to perform expensive operations + * (for example, String concatenation) + * when the log level is more than trace. + * + * @return true if trace is enabled in the underlying logger. + */ + boolean isTraceEnabled(); + + /** + * Is warn logging currently enabled? + *

+ * Call this method to prevent having to perform expensive operations + * (for example, String concatenation) + * when the log level is more than warn. + * + * @return true if warn is enabled in the underlying logger. + */ + boolean isWarnEnabled(); + + /** + * Logs a message with trace log level. + * + * @param message log this message + */ + void trace(Object message); + + /** + * Logs an error with trace log level. + * + * @param message log this message + * @param t log this cause + */ + void trace(Object message, Throwable t); + + /** + * Logs a message with warn log level. + * + * @param message log this message + */ + void warn(Object message); + + /** + * Logs an error with warn log level. + * + * @param message log this message + * @param t log this cause + */ + void warn(Object message, Throwable t); +} diff --git a/src/main/java/org/apache/commons/logging/LogConfigurationException.java b/src/main/java/org/apache/commons/logging/LogConfigurationException.java new file mode 100644 index 0000000..e612e9b --- /dev/null +++ b/src/main/java/org/apache/commons/logging/LogConfigurationException.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +/** + * An exception that is thrown only if a suitable LogFactory + * or Log instance cannot be created by the corresponding + * factory methods. + * + * @version $Id: LogConfigurationException.java 1432663 2013-01-13 17:24:18Z tn $ + */ +public class LogConfigurationException extends RuntimeException { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 8486587136871052495L; + + /** + * Construct a new exception with null as its detail message. + */ + public LogConfigurationException() { + super(); + } + + /** + * Construct a new exception with the specified detail message. + * + * @param message The detail message + */ + public LogConfigurationException(String message) { + super(message); + } + + /** + * Construct a new exception with the specified cause and a derived + * detail message. + * + * @param cause The underlying cause + */ + public LogConfigurationException(Throwable cause) { + this(cause == null ? null : cause.toString(), cause); + } + + /** + * Construct a new exception with the specified detail message and cause. + * + * @param message The detail message + * @param cause The underlying cause + */ + public LogConfigurationException(String message, Throwable cause) { + super(message + " (Caused by " + cause + ")"); + this.cause = cause; // Two-argument version requires JDK 1.4 or later + } + + /** + * The underlying cause of this exception. + */ + protected Throwable cause = null; + + /** + * Return the underlying cause of this exception (if any). + */ + public Throwable getCause() { + return this.cause; + } +} diff --git a/src/main/java/org/apache/commons/logging/LogFactory.java b/src/main/java/org/apache/commons/logging/LogFactory.java new file mode 100644 index 0000000..705cb96 --- /dev/null +++ b/src/main/java/org/apache/commons/logging/LogFactory.java @@ -0,0 +1,1703 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import java.io.BufferedReader; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.net.URL; +import java.net.URLConnection; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; + +/** + * Factory for creating {@link Log} instances, with discovery and + * configuration features similar to that employed by standard Java APIs + * such as JAXP. + *

+ * IMPLEMENTATION NOTE - This implementation is heavily + * based on the SAXParserFactory and DocumentBuilderFactory implementations + * (corresponding to the JAXP pluggability APIs) found in Apache Xerces. + * + * @version $Id: LogFactory.java 1606041 2014-06-27 11:56:59Z tn $ + */ +public abstract class LogFactory { + // Implementation note re AccessController usage + // + // It is important to keep code invoked via an AccessController to small + // auditable blocks. Such code must carefully evaluate all user input + // (parameters, system properties, config file contents, etc). As an + // example, a Log implementation should not write to its logfile + // with an AccessController anywhere in the call stack, otherwise an + // insecure application could configure the log implementation to write + // to a protected file using the privileges granted to JCL rather than + // to the calling application. + // + // Under no circumstance should a non-private method return data that is + // retrieved via an AccessController. That would allow an insecure app + // to invoke that method and obtain data that it is not permitted to have. + // + // Invoking user-supplied code with an AccessController set is not a major + // issue (eg invoking the constructor of the class specified by + // HASHTABLE_IMPLEMENTATION_PROPERTY). That class will be in a different + // trust domain, and therefore must have permissions to do whatever it + // is trying to do regardless of the permissions granted to JCL. There is + // a slight issue in that untrusted code may point that environment var + // to another trusted library, in which case the code runs if both that + // lib and JCL have the necessary permissions even when the untrusted + // caller does not. That's a pretty hard route to exploit though. + + // ----------------------------------------------------- Manifest Constants + + /** + * The name (priority) of the key in the config file used to + * specify the priority of that particular config file. The associated value + * is a floating-point number; higher values take priority over lower values. + */ + public static final String PRIORITY_KEY = "priority"; + + /** + * The name (use_tccl) of the key in the config file used + * to specify whether logging classes should be loaded via the thread + * context class loader (TCCL), or not. By default, the TCCL is used. + */ + public static final String TCCL_KEY = "use_tccl"; + + /** + * The name (org.apache.commons.logging.LogFactory) of the property + * used to identify the LogFactory implementation + * class name. This can be used as a system property, or as an entry in a + * configuration properties file. + */ + public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory"; + + /** + * The fully qualified class name of the fallback LogFactory + * implementation class to use, if no other can be found. + */ + public static final String FACTORY_DEFAULT = "org.apache.commons.logging.impl.LogFactoryImpl"; + + /** + * The name (commons-logging.properties) of the properties file to search for. + */ + public static final String FACTORY_PROPERTIES = "commons-logging.properties"; + + /** + * JDK1.3+ + * 'Service Provider' specification. + */ + protected static final String SERVICE_ID = + "META-INF/services/org.apache.commons.logging.LogFactory"; + + /** + * The name (org.apache.commons.logging.diagnostics.dest) + * of the property used to enable internal commons-logging + * diagnostic output, in order to get information on what logging + * implementations are being discovered, what classloaders they + * are loaded through, etc. + *

+ * If a system property of this name is set then the value is + * assumed to be the name of a file. The special strings + * STDOUT or STDERR (case-sensitive) indicate output to + * System.out and System.err respectively. + *

+ * Diagnostic logging should be used only to debug problematic + * configurations and should not be set in normal production use. + */ + public static final String DIAGNOSTICS_DEST_PROPERTY = + "org.apache.commons.logging.diagnostics.dest"; + + /** + * When null (the usual case), no diagnostic output will be + * generated by LogFactory or LogFactoryImpl. When non-null, + * interesting events will be written to the specified object. + */ + private static PrintStream diagnosticsStream = null; + + /** + * A string that gets prefixed to every message output by the + * logDiagnostic method, so that users can clearly see which + * LogFactory class is generating the output. + */ + private static final String diagnosticPrefix; + + /** + * Setting this system property + * (org.apache.commons.logging.LogFactory.HashtableImpl) + * value allows the Hashtable used to store + * classloaders to be substituted by an alternative implementation. + *

+ * Note: LogFactory will print: + *

+     * [ERROR] LogFactory: Load of custom hashtable failed
+     * 
+ * to system error and then continue using a standard Hashtable. + *

+ * Usage: Set this property when Java is invoked + * and LogFactory will attempt to load a new instance + * of the given implementation class. + * For example, running the following ant scriplet: + *

+     *  <java classname="${test.runner}" fork="yes" failonerror="${test.failonerror}">
+     *     ...
+     *     <sysproperty
+     *        key="org.apache.commons.logging.LogFactory.HashtableImpl"
+     *        value="org.apache.commons.logging.AltHashtable"/>
+     *  </java>
+     * 
+ * will mean that LogFactory will load an instance of + * org.apache.commons.logging.AltHashtable. + *

+ * A typical use case is to allow a custom + * Hashtable implementation using weak references to be substituted. + * This will allow classloaders to be garbage collected without + * the need to release them (on 1.3+ JVMs only, of course ;). + */ + public static final String HASHTABLE_IMPLEMENTATION_PROPERTY = + "org.apache.commons.logging.LogFactory.HashtableImpl"; + + /** Name used to load the weak hashtable implementation by names. */ + private static final String WEAK_HASHTABLE_CLASSNAME = + "org.apache.commons.logging.impl.WeakHashtable"; + + /** + * A reference to the classloader that loaded this class. This is the + * same as LogFactory.class.getClassLoader(). However computing this + * value isn't quite as simple as that, as we potentially need to use + * AccessControllers etc. It's more efficient to compute it once and + * cache it here. + */ + private static final ClassLoader thisClassLoader; + + // ----------------------------------------------------------- Constructors + + /** + * Protected constructor that is not available for public use. + */ + protected LogFactory() { + } + + // --------------------------------------------------------- Public Methods + + /** + * Return the configuration attribute with the specified name (if any), + * or null if there is no such attribute. + * + * @param name Name of the attribute to return + */ + public abstract Object getAttribute(String name); + + /** + * Return an array containing the names of all currently defined + * configuration attributes. If there are no such attributes, a zero + * length array is returned. + */ + public abstract String[] getAttributeNames(); + + /** + * Convenience method to derive a name from the specified class and + * call getInstance(String) with it. + * + * @param clazz Class for which a suitable Log name will be derived + * @throws LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public abstract Log getInstance(Class clazz) + throws LogConfigurationException; + + /** + * Construct (if necessary) and return a Log instance, + * using the factory's current set of configuration attributes. + *

+ * NOTE - Depending upon the implementation of + * the LogFactory you are using, the Log + * instance you are returned may or may not be local to the current + * application, and may or may not be returned again on a subsequent + * call with the same name argument. + * + * @param name Logical name of the Log instance to be + * returned (the meaning of this name is only known to the underlying + * logging implementation that is being wrapped) + * @throws LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public abstract Log getInstance(String name) + throws LogConfigurationException; + + /** + * Release any internal references to previously created {@link Log} + * instances returned by this factory. This is useful in environments + * like servlet containers, which implement application reloading by + * throwing away a ClassLoader. Dangling references to objects in that + * class loader would prevent garbage collection. + */ + public abstract void release(); + + /** + * Remove any configuration attribute associated with the specified name. + * If there is no such attribute, no action is taken. + * + * @param name Name of the attribute to remove + */ + public abstract void removeAttribute(String name); + + /** + * Set the configuration attribute with the specified name. Calling + * this with a null value is equivalent to calling + * removeAttribute(name). + * + * @param name Name of the attribute to set + * @param value Value of the attribute to set, or null + * to remove any setting for this attribute + */ + public abstract void setAttribute(String name, Object value); + + // ------------------------------------------------------- Static Variables + + /** + * The previously constructed LogFactory instances, keyed by + * the ClassLoader with which it was created. + */ + protected static Hashtable factories = null; + + /** + * Previously constructed LogFactory instance as in the + * factories map, but for the case where + * getClassLoader returns null. + * This can happen when: + *

    + *
  • using JDK1.1 and the calling code is loaded via the system + * classloader (very common)
  • + *
  • using JDK1.2+ and the calling code is loaded via the boot + * classloader (only likely for embedded systems work).
  • + *
+ * Note that factories is a Hashtable (not a HashMap), + * and hashtables don't allow null as a key. + * @deprecated since 1.1.2 + */ + protected static volatile LogFactory nullClassLoaderFactory = null; + + /** + * Create the hashtable which will be used to store a map of + * (context-classloader -> logfactory-object). Version 1.2+ of Java + * supports "weak references", allowing a custom Hashtable class + * to be used which uses only weak references to its keys. Using weak + * references can fix memory leaks on webapp unload in some cases (though + * not all). Version 1.1 of Java does not support weak references, so we + * must dynamically determine which we are using. And just for fun, this + * code also supports the ability for a system property to specify an + * arbitrary Hashtable implementation name. + *

+ * Note that the correct way to ensure no memory leaks occur is to ensure + * that LogFactory.release(contextClassLoader) is called whenever a + * webapp is undeployed. + */ + private static final Hashtable createFactoryStore() { + Hashtable result = null; + String storeImplementationClass; + try { + storeImplementationClass = getSystemProperty(HASHTABLE_IMPLEMENTATION_PROPERTY, null); + } catch (SecurityException ex) { + // Permissions don't allow this to be accessed. Default to the "modern" + // weak hashtable implementation if it is available. + storeImplementationClass = null; + } + + if (storeImplementationClass == null) { + storeImplementationClass = WEAK_HASHTABLE_CLASSNAME; + } + try { + Class implementationClass = Class.forName(storeImplementationClass); + result = (Hashtable) implementationClass.newInstance(); + } catch (Throwable t) { + handleThrowable(t); // may re-throw t + + // ignore + if (!WEAK_HASHTABLE_CLASSNAME.equals(storeImplementationClass)) { + // if the user's trying to set up a custom implementation, give a clue + if (isDiagnosticsEnabled()) { + // use internal logging to issue the warning + logDiagnostic("[ERROR] LogFactory: Load of custom hashtable failed"); + } else { + // we *really* want this output, even if diagnostics weren't + // explicitly enabled by the user. + System.err.println("[ERROR] LogFactory: Load of custom hashtable failed"); + } + } + } + if (result == null) { + result = new Hashtable(); + } + return result; + } + + // --------------------------------------------------------- Static Methods + + /** Utility method to safely trim a string. */ + private static String trim(String src) { + if (src == null) { + return null; + } + return src.trim(); + } + + /** + * Checks whether the supplied Throwable is one that needs to be + * re-thrown and ignores all others. + * + * The following errors are re-thrown: + *

    + *
  • ThreadDeath
  • + *
  • VirtualMachineError
  • + *
+ * + * @param t the Throwable to check + */ + protected static void handleThrowable(Throwable t) { + if (t instanceof ThreadDeath) { + throw (ThreadDeath) t; + } + if (t instanceof VirtualMachineError) { + throw (VirtualMachineError) t; + } + // All other instances of Throwable will be silently ignored + } + + /** + * Construct (if necessary) and return a LogFactory + * instance, using the following ordered lookup procedure to determine + * the name of the implementation class to be loaded. + *

+ *

    + *
  • The org.apache.commons.logging.LogFactory system + * property.
  • + *
  • The JDK 1.3 Service Discovery mechanism
  • + *
  • Use the properties file commons-logging.properties + * file, if found in the class path of this class. The configuration + * file is in standard java.util.Properties format and + * contains the fully qualified name of the implementation class + * with the key being the system property defined above.
  • + *
  • Fall back to a default implementation class + * (org.apache.commons.logging.impl.LogFactoryImpl).
  • + *
+ *

+ * NOTE - If the properties file method of identifying the + * LogFactory implementation class is utilized, all of the + * properties defined in this file will be set as configuration attributes + * on the corresponding LogFactory instance. + *

+ * NOTE - In a multi-threaded environment it is possible + * that two different instances will be returned for the same + * classloader environment. + * + * @throws LogConfigurationException if the implementation class is not + * available or cannot be instantiated. + */ + public static LogFactory getFactory() throws LogConfigurationException { + // Identify the class loader we will be using + ClassLoader contextClassLoader = getContextClassLoaderInternal(); + + if (contextClassLoader == null) { + // This is an odd enough situation to report about. This + // output will be a nuisance on JDK1.1, as the system + // classloader is null in that environment. + if (isDiagnosticsEnabled()) { + logDiagnostic("Context classloader is null."); + } + } + + // Return any previously registered factory for this class loader + LogFactory factory = getCachedFactory(contextClassLoader); + if (factory != null) { + return factory; + } + + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] LogFactory implementation requested for the first time for context classloader " + + objectId(contextClassLoader)); + logHierarchy("[LOOKUP] ", contextClassLoader); + } + + // Load properties file. + // + // If the properties file exists, then its contents are used as + // "attributes" on the LogFactory implementation class. One particular + // property may also control which LogFactory concrete subclass is + // used, but only if other discovery mechanisms fail.. + // + // As the properties file (if it exists) will be used one way or + // another in the end we may as well look for it first. + + Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES); + + // Determine whether we will be using the thread context class loader to + // load logging classes or not by checking the loaded properties file (if any). + ClassLoader baseClassLoader = contextClassLoader; + if (props != null) { + String useTCCLStr = props.getProperty(TCCL_KEY); + if (useTCCLStr != null) { + // The Boolean.valueOf(useTCCLStr).booleanValue() formulation + // is required for Java 1.2 compatibility. + if (Boolean.valueOf(useTCCLStr).booleanValue() == false) { + // Don't use current context classloader when locating any + // LogFactory or Log classes, just use the class that loaded + // this abstract class. When this class is deployed in a shared + // classpath of a container, it means webapps cannot deploy their + // own logging implementations. It also means that it is up to the + // implementation whether to load library-specific config files + // from the TCCL or not. + baseClassLoader = thisClassLoader; + } + } + } + + // Determine which concrete LogFactory subclass to use. + // First, try a global system property + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY + + "] to define the LogFactory subclass to use..."); + } + + try { + String factoryClass = getSystemProperty(FACTORY_PROPERTY, null); + if (factoryClass != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Creating an instance of LogFactory class '" + factoryClass + + "' as specified by system property " + FACTORY_PROPERTY); + } + factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined."); + } + } + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] A security exception occurred while trying to create an" + + " instance of the custom factory class" + ": [" + trim(e.getMessage()) + + "]. Trying alternative implementations..."); + } + // ignore + } catch (RuntimeException e) { + // This is not consistent with the behaviour when a bad LogFactory class is + // specified in a services file. + // + // One possible exception that can occur here is a ClassCastException when + // the specified class wasn't castable to this LogFactory type. + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] An exception occurred while trying to create an" + + " instance of the custom factory class" + ": [" + + trim(e.getMessage()) + + "] as specified by a system property."); + } + throw e; + } + + // Second, try to find a service by using the JDK1.3 class + // discovery mechanism, which involves putting a file with the name + // of an interface class in the META-INF/services directory, where the + // contents of the file is a single line specifying a concrete class + // that implements the desired interface. + + if (factory == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID + + "] to define the LogFactory subclass to use..."); + } + try { + final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID); + + if( is != null ) { + // This code is needed by EBCDIC and other strange systems. + // It's a fix for bugs reported in xerces + BufferedReader rd; + try { + rd = new BufferedReader(new InputStreamReader(is, "UTF-8")); + } catch (java.io.UnsupportedEncodingException e) { + rd = new BufferedReader(new InputStreamReader(is)); + } + + String factoryClassName = rd.readLine(); + rd.close(); + + if (factoryClassName != null && ! "".equals(factoryClassName)) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Creating an instance of LogFactory class " + + factoryClassName + + " as specified by file '" + SERVICE_ID + + "' which was present in the path of the context classloader."); + } + factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader ); + } + } else { + // is == null + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] No resource file with name '" + SERVICE_ID + "' found."); + } + } + } catch (Exception ex) { + // note: if the specified LogFactory class wasn't compatible with LogFactory + // for some reason, a ClassCastException will be caught here, and attempts will + // continue to find a compatible class. + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] A security exception occurred while trying to create an" + + " instance of the custom factory class" + + ": [" + trim(ex.getMessage()) + + "]. Trying alternative implementations..."); + } + // ignore + } + } + + // Third try looking into the properties file read earlier (if found) + + if (factory == null) { + if (props != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Looking in properties file for entry with key '" + FACTORY_PROPERTY + + "' to define the LogFactory subclass to use..."); + } + String factoryClass = props.getProperty(FACTORY_PROPERTY); + if (factoryClass != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Properties file specifies LogFactory subclass '" + factoryClass + "'"); + } + factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); + + // TODO: think about whether we need to handle exceptions from newFactory + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Properties file has no entry specifying LogFactory subclass."); + } + } + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] No properties file available to determine" + " LogFactory subclass from.."); + } + } + } + + // Fourth, try the fallback implementation class + + if (factory == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Loading the default LogFactory implementation '" + FACTORY_DEFAULT + + "' via the same classloader that loaded this LogFactory" + + " class (ie not looking in the context classloader)."); + } + + // Note: unlike the above code which can try to load custom LogFactory + // implementations via the TCCL, we don't try to load the default LogFactory + // implementation via the context classloader because: + // * that can cause problems (see comments in newFactory method) + // * no-one should be customising the code of the default class + // Yes, we do give up the ability for the child to ship a newer + // version of the LogFactoryImpl class and have it used dynamically + // by an old LogFactory class in the parent, but that isn't + // necessarily a good idea anyway. + factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader); + } + + if (factory != null) { + /** + * Always cache using context class loader. + */ + cacheFactory(contextClassLoader, factory); + + if (props != null) { + Enumeration names = props.propertyNames(); + while (names.hasMoreElements()) { + String name = (String) names.nextElement(); + String value = props.getProperty(name); + factory.setAttribute(name, value); + } + } + } + + return factory; + } + + /** + * Convenience method to return a named logger, without the application + * having to care about factories. + * + * @param clazz Class from which a log name will be derived + * @throws LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public static Log getLog(Class clazz) throws LogConfigurationException { + return getFactory().getInstance(clazz); + } + + /** + * Convenience method to return a named logger, without the application + * having to care about factories. + * + * @param name Logical name of the Log instance to be + * returned (the meaning of this name is only known to the underlying + * logging implementation that is being wrapped) + * @throws LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public static Log getLog(String name) throws LogConfigurationException { + return getFactory().getInstance(name); + } + + /** + * Release any internal references to previously created {@link LogFactory} + * instances that have been associated with the specified class loader + * (if any), after calling the instance method release() on + * each of them. + * + * @param classLoader ClassLoader for which to release the LogFactory + */ + public static void release(ClassLoader classLoader) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Releasing factory for classloader " + objectId(classLoader)); + } + // factories is not final and could be replaced in this block. + final Hashtable factories = LogFactory.factories; + synchronized (factories) { + if (classLoader == null) { + if (nullClassLoaderFactory != null) { + nullClassLoaderFactory.release(); + nullClassLoaderFactory = null; + } + } else { + final LogFactory factory = (LogFactory) factories.get(classLoader); + if (factory != null) { + factory.release(); + factories.remove(classLoader); + } + } + } + } + + /** + * Release any internal references to previously created {@link LogFactory} + * instances, after calling the instance method release() on + * each of them. This is useful in environments like servlet containers, + * which implement application reloading by throwing away a ClassLoader. + * Dangling references to objects in that class loader would prevent + * garbage collection. + */ + public static void releaseAll() { + if (isDiagnosticsEnabled()) { + logDiagnostic("Releasing factory for all classloaders."); + } + // factories is not final and could be replaced in this block. + final Hashtable factories = LogFactory.factories; + synchronized (factories) { + final Enumeration elements = factories.elements(); + while (elements.hasMoreElements()) { + LogFactory element = (LogFactory) elements.nextElement(); + element.release(); + } + factories.clear(); + + if (nullClassLoaderFactory != null) { + nullClassLoaderFactory.release(); + nullClassLoaderFactory = null; + } + } + } + + // ------------------------------------------------------ Protected Methods + + /** + * Safely get access to the classloader for the specified class. + *

+ * Theoretically, calling getClassLoader can throw a security exception, + * and so should be done under an AccessController in order to provide + * maximum flexibility. However in practice people don't appear to use + * security policies that forbid getClassLoader calls. So for the moment + * all code is written to call this method rather than Class.getClassLoader, + * so that we could put AccessController stuff in this method without any + * disruption later if we need to. + *

+ * Even when using an AccessController, however, this method can still + * throw SecurityException. Commons-logging basically relies on the + * ability to access classloaders, ie a policy that forbids all + * classloader access will also prevent commons-logging from working: + * currently this method will throw an exception preventing the entire app + * from starting up. Maybe it would be good to detect this situation and + * just disable all commons-logging? Not high priority though - as stated + * above, security policies that prevent classloader access aren't common. + *

+ * Note that returning an object fetched via an AccessController would + * technically be a security flaw anyway; untrusted code that has access + * to a trusted JCL library could use it to fetch the classloader for + * a class even when forbidden to do so directly. + * + * @since 1.1 + */ + protected static ClassLoader getClassLoader(Class clazz) { + try { + return clazz.getClassLoader(); + } catch (SecurityException ex) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to get classloader for class '" + clazz + + "' due to security restrictions - " + ex.getMessage()); + } + throw ex; + } + } + + /** + * Returns the current context classloader. + *

+ * In versions prior to 1.1, this method did not use an AccessController. + * In version 1.1, an AccessController wrapper was incorrectly added to + * this method, causing a minor security flaw. + *

+ * In version 1.1.1 this change was reverted; this method no longer uses + * an AccessController. User code wishing to obtain the context classloader + * must invoke this method via AccessController.doPrivileged if it needs + * support for that. + * + * @return the context classloader associated with the current thread, + * or null if security doesn't allow it. + * @throws LogConfigurationException if there was some weird error while + * attempting to get the context classloader. + */ + protected static ClassLoader getContextClassLoader() throws LogConfigurationException { + return directGetContextClassLoader(); + } + + /** + * Calls LogFactory.directGetContextClassLoader under the control of an + * AccessController class. This means that java code running under a + * security manager that forbids access to ClassLoaders will still work + * if this class is given appropriate privileges, even when the caller + * doesn't have such privileges. Without using an AccessController, the + * the entire call stack must have the privilege before the call is + * allowed. + * + * @return the context classloader associated with the current thread, + * or null if security doesn't allow it. + * @throws LogConfigurationException if there was some weird error while + * attempting to get the context classloader. + */ + private static ClassLoader getContextClassLoaderInternal() throws LogConfigurationException { + return (ClassLoader)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return directGetContextClassLoader(); + } + }); + } + + /** + * Return the thread context class loader if available; otherwise return null. + *

+ * Most/all code should call getContextClassLoaderInternal rather than + * calling this method directly. + *

+ * The thread context class loader is available for JDK 1.2 + * or later, if certain security conditions are met. + *

+ * Note that no internal logging is done within this method because + * this method is called every time LogFactory.getLogger() is called, + * and we don't want too much output generated here. + * + * @throws LogConfigurationException if a suitable class loader + * cannot be identified. + * @return the thread's context classloader or {@code null} if the java security + * policy forbids access to the context classloader from one of the classes + * in the current call stack. + * @since 1.1 + */ + protected static ClassLoader directGetContextClassLoader() throws LogConfigurationException { + ClassLoader classLoader = null; + + try { + classLoader = Thread.currentThread().getContextClassLoader(); + } catch (SecurityException ex) { + /** + * getContextClassLoader() throws SecurityException when + * the context class loader isn't an ancestor of the + * calling class's class loader, or if security + * permissions are restricted. + * + * We ignore this exception to be consistent with the previous + * behavior (e.g. 1.1.3 and earlier). + */ + // ignore + } + + // Return the selected class loader + return classLoader; + } + + /** + * Check cached factories (keyed by contextClassLoader) + * + * @param contextClassLoader is the context classloader associated + * with the current thread. This allows separate LogFactory objects + * per component within a container, provided each component has + * a distinct context classloader set. This parameter may be null + * in JDK1.1, and in embedded systems where jcl-using code is + * placed in the bootclasspath. + * + * @return the factory associated with the specified classloader if + * one has previously been created, or null if this is the first time + * we have seen this particular classloader. + */ + private static LogFactory getCachedFactory(ClassLoader contextClassLoader) { + if (contextClassLoader == null) { + // We have to handle this specially, as factories is a Hashtable + // and those don't accept null as a key value. + // + // nb: nullClassLoaderFactory might be null. That's ok. + return nullClassLoaderFactory; + } else { + return (LogFactory) factories.get(contextClassLoader); + } + } + + /** + * Remember this factory, so later calls to LogFactory.getCachedFactory + * can return the previously created object (together with all its + * cached Log objects). + * + * @param classLoader should be the current context classloader. Note that + * this can be null under some circumstances; this is ok. + * @param factory should be the factory to cache. This should never be null. + */ + private static void cacheFactory(ClassLoader classLoader, LogFactory factory) { + // Ideally we would assert(factory != null) here. However reporting + // errors from within a logging implementation is a little tricky! + + if (factory != null) { + if (classLoader == null) { + nullClassLoaderFactory = factory; + } else { + factories.put(classLoader, factory); + } + } + } + + /** + * Return a new instance of the specified LogFactory + * implementation class, loaded by the specified class loader. + * If that fails, try the class loader used to load this + * (abstract) LogFactory. + *

ClassLoader conflicts

+ *

+ * Note that there can be problems if the specified ClassLoader is not the + * same as the classloader that loaded this class, ie when loading a + * concrete LogFactory subclass via a context classloader. + *

+ * The problem is the same one that can occur when loading a concrete Log + * subclass via a context classloader. + *

+ * The problem occurs when code running in the context classloader calls + * class X which was loaded via a parent classloader, and class X then calls + * LogFactory.getFactory (either directly or via LogFactory.getLog). Because + * class X was loaded via the parent, it binds to LogFactory loaded via + * the parent. When the code in this method finds some LogFactoryYYYY + * class in the child (context) classloader, and there also happens to be a + * LogFactory class defined in the child classloader, then LogFactoryYYYY + * will be bound to LogFactory@childloader. It cannot be cast to + * LogFactory@parentloader, ie this method cannot return the object as + * the desired type. Note that it doesn't matter if the LogFactory class + * in the child classloader is identical to the LogFactory class in the + * parent classloader, they are not compatible. + *

+ * The solution taken here is to simply print out an error message when + * this occurs then throw an exception. The deployer of the application + * must ensure they remove all occurrences of the LogFactory class from + * the child classloader in order to resolve the issue. Note that they + * do not have to move the custom LogFactory subclass; that is ok as + * long as the only LogFactory class it can find to bind to is in the + * parent classloader. + * + * @param factoryClass Fully qualified name of the LogFactory + * implementation class + * @param classLoader ClassLoader from which to load this class + * @param contextClassLoader is the context that this new factory will + * manage logging for. + * @throws LogConfigurationException if a suitable instance + * cannot be created + * @since 1.1 + */ + protected static LogFactory newFactory(final String factoryClass, + final ClassLoader classLoader, + final ClassLoader contextClassLoader) + throws LogConfigurationException { + // Note that any unchecked exceptions thrown by the createFactory + // method will propagate out of this method; in particular a + // ClassCastException can be thrown. + Object result = AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return createFactory(factoryClass, classLoader); + } + }); + + if (result instanceof LogConfigurationException) { + LogConfigurationException ex = (LogConfigurationException) result; + if (isDiagnosticsEnabled()) { + logDiagnostic("An error occurred while loading the factory class:" + ex.getMessage()); + } + throw ex; + } + if (isDiagnosticsEnabled()) { + logDiagnostic("Created object " + objectId(result) + " to manage classloader " + + objectId(contextClassLoader)); + } + return (LogFactory)result; + } + + /** + * Method provided for backwards compatibility; see newFactory version that + * takes 3 parameters. + *

+ * This method would only ever be called in some rather odd situation. + * Note that this method is static, so overriding in a subclass doesn't + * have any effect unless this method is called from a method in that + * subclass. However this method only makes sense to use from the + * getFactory method, and as that is almost always invoked via + * LogFactory.getFactory, any custom definition in a subclass would be + * pointless. Only a class with a custom getFactory method, then invoked + * directly via CustomFactoryImpl.getFactory or similar would ever call + * this. Anyway, it's here just in case, though the "managed class loader" + * value output to the diagnostics will not report the correct value. + */ + protected static LogFactory newFactory(final String factoryClass, + final ClassLoader classLoader) { + return newFactory(factoryClass, classLoader, null); + } + + /** + * Implements the operations described in the javadoc for newFactory. + * + * @param factoryClass + * @param classLoader used to load the specified factory class. This is + * expected to be either the TCCL or the classloader which loaded this + * class. Note that the classloader which loaded this class might be + * "null" (ie the bootloader) for embedded systems. + * @return either a LogFactory object or a LogConfigurationException object. + * @since 1.1 + */ + protected static Object createFactory(String factoryClass, ClassLoader classLoader) { + // This will be used to diagnose bad configurations + // and allow a useful message to be sent to the user + Class logFactoryClass = null; + try { + if (classLoader != null) { + try { + // First the given class loader param (thread class loader) + + // Warning: must typecast here & allow exception + // to be generated/caught & recast properly. + logFactoryClass = classLoader.loadClass(factoryClass); + if (LogFactory.class.isAssignableFrom(logFactoryClass)) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Loaded class " + logFactoryClass.getName() + + " from classloader " + objectId(classLoader)); + } + } else { + // + // This indicates a problem with the ClassLoader tree. + // An incompatible ClassLoader was used to load the + // implementation. + // As the same classes + // must be available in multiple class loaders, + // it is very likely that multiple JCL jars are present. + // The most likely fix for this + // problem is to remove the extra JCL jars from the + // ClassLoader hierarchy. + // + if (isDiagnosticsEnabled()) { + logDiagnostic("Factory class " + logFactoryClass.getName() + + " loaded from classloader " + objectId(logFactoryClass.getClassLoader()) + + " does not extend '" + LogFactory.class.getName() + + "' as loaded by this classloader."); + logHierarchy("[BAD CL TREE] ", classLoader); + } + } + + return (LogFactory) logFactoryClass.newInstance(); + + } catch (ClassNotFoundException ex) { + if (classLoader == thisClassLoader) { + // Nothing more to try, onwards. + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to locate any class called '" + factoryClass + + "' via classloader " + objectId(classLoader)); + } + throw ex; + } + // ignore exception, continue + } catch (NoClassDefFoundError e) { + if (classLoader == thisClassLoader) { + // Nothing more to try, onwards. + if (isDiagnosticsEnabled()) { + logDiagnostic("Class '" + factoryClass + "' cannot be loaded" + + " via classloader " + objectId(classLoader) + + " - it depends on some other class that cannot be found."); + } + throw e; + } + // ignore exception, continue + } catch (ClassCastException e) { + if (classLoader == thisClassLoader) { + // There's no point in falling through to the code below that + // tries again with thisClassLoader, because we've just tried + // loading with that loader (not the TCCL). Just throw an + // appropriate exception here. + + final boolean implementsLogFactory = implementsLogFactory(logFactoryClass); + + // + // Construct a good message: users may not actual expect that a custom implementation + // has been specified. Several well known containers use this mechanism to adapt JCL + // to their native logging system. + // + final StringBuffer msg = new StringBuffer(); + msg.append("The application has specified that a custom LogFactory implementation "); + msg.append("should be used but Class '"); + msg.append(factoryClass); + msg.append("' cannot be converted to '"); + msg.append(LogFactory.class.getName()); + msg.append("'. "); + if (implementsLogFactory) { + msg.append("The conflict is caused by the presence of multiple LogFactory classes "); + msg.append("in incompatible classloaders. "); + msg.append("Background can be found in http://commons.apache.org/logging/tech.html. "); + msg.append("If you have not explicitly specified a custom LogFactory then it is likely "); + msg.append("that the container has set one without your knowledge. "); + msg.append("In this case, consider using the commons-logging-adapters.jar file or "); + msg.append("specifying the standard LogFactory from the command line. "); + } else { + msg.append("Please check the custom implementation. "); + } + msg.append("Help can be found @http://commons.apache.org/logging/troubleshooting.html."); + + if (isDiagnosticsEnabled()) { + logDiagnostic(msg.toString()); + } + + throw new ClassCastException(msg.toString()); + } + + // Ignore exception, continue. Presumably the classloader was the + // TCCL; the code below will try to load the class via thisClassLoader. + // This will handle the case where the original calling class is in + // a shared classpath but the TCCL has a copy of LogFactory and the + // specified LogFactory implementation; we will fall back to using the + // LogFactory implementation from the same classloader as this class. + // + // Issue: this doesn't handle the reverse case, where this LogFactory + // is in the webapp, and the specified LogFactory implementation is + // in a shared classpath. In that case: + // (a) the class really does implement LogFactory (bad log msg above) + // (b) the fallback code will result in exactly the same problem. + } + } + + /* At this point, either classLoader == null, OR + * classLoader was unable to load factoryClass. + * + * In either case, we call Class.forName, which is equivalent + * to LogFactory.class.getClassLoader().load(name), ie we ignore + * the classloader parameter the caller passed, and fall back + * to trying the classloader associated with this class. See the + * javadoc for the newFactory method for more info on the + * consequences of this. + * + * Notes: + * * LogFactory.class.getClassLoader() may return 'null' + * if LogFactory is loaded by the bootstrap classloader. + */ + // Warning: must typecast here & allow exception + // to be generated/caught & recast properly. + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to load factory class via classloader " + objectId(classLoader) + + " - trying the classloader associated with this LogFactory."); + } + logFactoryClass = Class.forName(factoryClass); + return (LogFactory) logFactoryClass.newInstance(); + } catch (Exception e) { + // Check to see if we've got a bad configuration + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to create LogFactory instance."); + } + if (logFactoryClass != null && !LogFactory.class.isAssignableFrom(logFactoryClass)) { + return new LogConfigurationException( + "The chosen LogFactory implementation does not extend LogFactory." + + " Please check your configuration.", e); + } + return new LogConfigurationException(e); + } + } + + /** + * Determines whether the given class actually implements LogFactory. + * Diagnostic information is also logged. + *

+ * Usage: to diagnose whether a classloader conflict is the cause + * of incompatibility. The test used is whether the class is assignable from + * the LogFactory class loaded by the class's classloader. + * @param logFactoryClass Class which may implement LogFactory + * @return true if the logFactoryClass does extend + * LogFactory when that class is loaded via the same + * classloader that loaded the logFactoryClass. + */ + private static boolean implementsLogFactory(Class logFactoryClass) { + boolean implementsLogFactory = false; + if (logFactoryClass != null) { + try { + ClassLoader logFactoryClassLoader = logFactoryClass.getClassLoader(); + if (logFactoryClassLoader == null) { + logDiagnostic("[CUSTOM LOG FACTORY] was loaded by the boot classloader"); + } else { + logHierarchy("[CUSTOM LOG FACTORY] ", logFactoryClassLoader); + Class factoryFromCustomLoader + = Class.forName("org.apache.commons.logging.LogFactory", false, logFactoryClassLoader); + implementsLogFactory = factoryFromCustomLoader.isAssignableFrom(logFactoryClass); + if (implementsLogFactory) { + logDiagnostic("[CUSTOM LOG FACTORY] " + logFactoryClass.getName() + + " implements LogFactory but was loaded by an incompatible classloader."); + } else { + logDiagnostic("[CUSTOM LOG FACTORY] " + logFactoryClass.getName() + + " does not implement LogFactory."); + } + } + } catch (SecurityException e) { + // + // The application is running within a hostile security environment. + // This will make it very hard to diagnose issues with JCL. + // Consider running less securely whilst debugging this issue. + // + logDiagnostic("[CUSTOM LOG FACTORY] SecurityException thrown whilst trying to determine whether " + + "the compatibility was caused by a classloader conflict: " + e.getMessage()); + } catch (LinkageError e) { + // + // This should be an unusual circumstance. + // LinkageError's usually indicate that a dependent class has incompatibly changed. + // Another possibility may be an exception thrown by an initializer. + // Time for a clean rebuild? + // + logDiagnostic("[CUSTOM LOG FACTORY] LinkageError thrown whilst trying to determine whether " + + "the compatibility was caused by a classloader conflict: " + e.getMessage()); + } catch (ClassNotFoundException e) { + // + // LogFactory cannot be loaded by the classloader which loaded the custom factory implementation. + // The custom implementation is not viable until this is corrected. + // Ensure that the JCL jar and the custom class are available from the same classloader. + // Running with diagnostics on should give information about the classloaders used + // to load the custom factory. + // + logDiagnostic("[CUSTOM LOG FACTORY] LogFactory class cannot be loaded by classloader which loaded " + + "the custom LogFactory implementation. Is the custom factory in the right classloader?"); + } + } + return implementsLogFactory; + } + + /** + * Applets may run in an environment where accessing resources of a loader is + * a secure operation, but where the commons-logging library has explicitly + * been granted permission for that operation. In this case, we need to + * run the operation using an AccessController. + */ + private static InputStream getResourceAsStream(final ClassLoader loader, final String name) { + return (InputStream)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + if (loader != null) { + return loader.getResourceAsStream(name); + } else { + return ClassLoader.getSystemResourceAsStream(name); + } + } + }); + } + + /** + * Given a filename, return an enumeration of URLs pointing to + * all the occurrences of that filename in the classpath. + *

+ * This is just like ClassLoader.getResources except that the + * operation is done under an AccessController so that this method will + * succeed when this jarfile is privileged but the caller is not. + * This method must therefore remain private to avoid security issues. + *

+ * If no instances are found, an Enumeration is returned whose + * hasMoreElements method returns false (ie an "empty" enumeration). + * If resources could not be listed for some reason, null is returned. + */ + private static Enumeration getResources(final ClassLoader loader, final String name) { + PrivilegedAction action = + new PrivilegedAction() { + public Object run() { + try { + if (loader != null) { + return loader.getResources(name); + } else { + return ClassLoader.getSystemResources(name); + } + } catch (IOException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Exception while trying to find configuration file " + + name + ":" + e.getMessage()); + } + return null; + } catch (NoSuchMethodError e) { + // we must be running on a 1.1 JVM which doesn't support + // ClassLoader.getSystemResources; just return null in + // this case. + return null; + } + } + }; + Object result = AccessController.doPrivileged(action); + return (Enumeration) result; + } + + /** + * Given a URL that refers to a .properties file, load that file. + * This is done under an AccessController so that this method will + * succeed when this jarfile is privileged but the caller is not. + * This method must therefore remain private to avoid security issues. + *

+ * {@code Null} is returned if the URL cannot be opened. + */ + private static Properties getProperties(final URL url) { + PrivilegedAction action = + new PrivilegedAction() { + public Object run() { + InputStream stream = null; + try { + // We must ensure that useCaches is set to false, as the + // default behaviour of java is to cache file handles, and + // this "locks" files, preventing hot-redeploy on windows. + URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + stream = connection.getInputStream(); + if (stream != null) { + Properties props = new Properties(); + props.load(stream); + stream.close(); + stream = null; + return props; + } + } catch (IOException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to read URL " + url); + } + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + // ignore exception; this should not happen + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to close stream for URL " + url); + } + } + } + } + + return null; + } + }; + return (Properties) AccessController.doPrivileged(action); + } + + /** + * Locate a user-provided configuration file. + *

+ * The classpath of the specified classLoader (usually the context classloader) + * is searched for properties files of the specified name. If none is found, + * null is returned. If more than one is found, then the file with the greatest + * value for its PRIORITY property is returned. If multiple files have the + * same PRIORITY value then the first in the classpath is returned. + *

+ * This differs from the 1.0.x releases; those always use the first one found. + * However as the priority is a new field, this change is backwards compatible. + *

+ * The purpose of the priority field is to allow a webserver administrator to + * override logging settings in all webapps by placing a commons-logging.properties + * file in a shared classpath location with a priority > 0; this overrides any + * commons-logging.properties files without priorities which are in the + * webapps. Webapps can also use explicit priorities to override a configuration + * file in the shared classpath if needed. + */ + private static final Properties getConfigurationFile(ClassLoader classLoader, String fileName) { + Properties props = null; + double priority = 0.0; + URL propsUrl = null; + try { + Enumeration urls = getResources(classLoader, fileName); + + if (urls == null) { + return null; + } + + while (urls.hasMoreElements()) { + URL url = (URL) urls.nextElement(); + + Properties newProps = getProperties(url); + if (newProps != null) { + if (props == null) { + propsUrl = url; + props = newProps; + String priorityStr = props.getProperty(PRIORITY_KEY); + priority = 0.0; + if (priorityStr != null) { + priority = Double.parseDouble(priorityStr); + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Properties file found at '" + url + "'" + + " with priority " + priority); + } + } else { + String newPriorityStr = newProps.getProperty(PRIORITY_KEY); + double newPriority = 0.0; + if (newPriorityStr != null) { + newPriority = Double.parseDouble(newPriorityStr); + } + + if (newPriority > priority) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Properties file at '" + url + "'" + + " with priority " + newPriority + + " overrides file at '" + propsUrl + "'" + + " with priority " + priority); + } + + propsUrl = url; + props = newProps; + priority = newPriority; + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Properties file at '" + url + "'" + + " with priority " + newPriority + + " does not override file at '" + propsUrl + "'" + + " with priority " + priority); + } + } + } + + } + } + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("SecurityException thrown while trying to find/read config files."); + } + } + + if (isDiagnosticsEnabled()) { + if (props == null) { + logDiagnostic("[LOOKUP] No properties file of name '" + fileName + "' found."); + } else { + logDiagnostic("[LOOKUP] Properties file of name '" + fileName + "' found at '" + propsUrl + '"'); + } + } + + return props; + } + + /** + * Read the specified system property, using an AccessController so that + * the property can be read if JCL has been granted the appropriate + * security rights even if the calling code has not. + *

+ * Take care not to expose the value returned by this method to the + * calling application in any way; otherwise the calling app can use that + * info to access data that should not be available to it. + */ + private static String getSystemProperty(final String key, final String def) + throws SecurityException { + return (String) AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return System.getProperty(key, def); + } + }); + } + + /** + * Determines whether the user wants internal diagnostic output. If so, + * returns an appropriate writer object. Users can enable diagnostic + * output by setting the system property named {@link #DIAGNOSTICS_DEST_PROPERTY} to + * a filename, or the special values STDOUT or STDERR. + */ + private static PrintStream initDiagnostics() { + String dest; + try { + dest = getSystemProperty(DIAGNOSTICS_DEST_PROPERTY, null); + if (dest == null) { + return null; + } + } catch (SecurityException ex) { + // We must be running in some very secure environment. + // We just have to assume output is not wanted.. + return null; + } + + if (dest.equals("STDOUT")) { + return System.out; + } else if (dest.equals("STDERR")) { + return System.err; + } else { + try { + // open the file in append mode + FileOutputStream fos = new FileOutputStream(dest, true); + return new PrintStream(fos); + } catch (IOException ex) { + // We should report this to the user - but how? + return null; + } + } + } + + /** + * Indicates true if the user has enabled internal logging. + *

+ * By the way, sorry for the incorrect grammar, but calling this method + * areDiagnosticsEnabled just isn't java beans style. + * + * @return true if calls to logDiagnostic will have any effect. + * @since 1.1 + */ + protected static boolean isDiagnosticsEnabled() { + return diagnosticsStream != null; + } + + /** + * Write the specified message to the internal logging destination. + *

+ * Note that this method is private; concrete subclasses of this class + * should not call it because the diagnosticPrefix string this + * method puts in front of all its messages is LogFactory@...., + * while subclasses should put SomeSubClass@... + *

+ * Subclasses should instead compute their own prefix, then call + * logRawDiagnostic. Note that calling isDiagnosticsEnabled is + * fine for subclasses. + *

+ * Note that it is safe to call this method before initDiagnostics + * is called; any output will just be ignored (as isDiagnosticsEnabled + * will return false). + * + * @param msg is the diagnostic message to be output. + */ + private static final void logDiagnostic(String msg) { + if (diagnosticsStream != null) { + diagnosticsStream.print(diagnosticPrefix); + diagnosticsStream.println(msg); + diagnosticsStream.flush(); + } + } + + /** + * Write the specified message to the internal logging destination. + * + * @param msg is the diagnostic message to be output. + * @since 1.1 + */ + protected static final void logRawDiagnostic(String msg) { + if (diagnosticsStream != null) { + diagnosticsStream.println(msg); + diagnosticsStream.flush(); + } + } + + /** + * Generate useful diagnostics regarding the classloader tree for + * the specified class. + *

+ * As an example, if the specified class was loaded via a webapp's + * classloader, then you may get the following output: + *

+     * Class com.acme.Foo was loaded via classloader 11111
+     * ClassLoader tree: 11111 -> 22222 (SYSTEM) -> 33333 -> BOOT
+     * 
+ *

+ * This method returns immediately if isDiagnosticsEnabled() + * returns false. + * + * @param clazz is the class whose classloader + tree are to be + * output. + */ + private static void logClassLoaderEnvironment(Class clazz) { + if (!isDiagnosticsEnabled()) { + return; + } + + try { + // Deliberately use System.getProperty here instead of getSystemProperty; if + // the overall security policy for the calling application forbids access to + // these variables then we do not want to output them to the diagnostic stream. + logDiagnostic("[ENV] Extension directories (java.ext.dir): " + System.getProperty("java.ext.dir")); + logDiagnostic("[ENV] Application classpath (java.class.path): " + System.getProperty("java.class.path")); + } catch (SecurityException ex) { + logDiagnostic("[ENV] Security setting prevent interrogation of system classpaths."); + } + + String className = clazz.getName(); + ClassLoader classLoader; + + try { + classLoader = getClassLoader(clazz); + } catch (SecurityException ex) { + // not much useful diagnostics we can print here! + logDiagnostic("[ENV] Security forbids determining the classloader for " + className); + return; + } + + logDiagnostic("[ENV] Class " + className + " was loaded via classloader " + objectId(classLoader)); + logHierarchy("[ENV] Ancestry of classloader which loaded " + className + " is ", classLoader); + } + + /** + * Logs diagnostic messages about the given classloader + * and it's hierarchy. The prefix is prepended to the message + * and is intended to make it easier to understand the logs. + * @param prefix + * @param classLoader + */ + private static void logHierarchy(String prefix, ClassLoader classLoader) { + if (!isDiagnosticsEnabled()) { + return; + } + ClassLoader systemClassLoader; + if (classLoader != null) { + final String classLoaderString = classLoader.toString(); + logDiagnostic(prefix + objectId(classLoader) + " == '" + classLoaderString + "'"); + } + + try { + systemClassLoader = ClassLoader.getSystemClassLoader(); + } catch (SecurityException ex) { + logDiagnostic(prefix + "Security forbids determining the system classloader."); + return; + } + if (classLoader != null) { + final StringBuffer buf = new StringBuffer(prefix + "ClassLoader tree:"); + for(;;) { + buf.append(objectId(classLoader)); + if (classLoader == systemClassLoader) { + buf.append(" (SYSTEM) "); + } + + try { + classLoader = classLoader.getParent(); + } catch (SecurityException ex) { + buf.append(" --> SECRET"); + break; + } + + buf.append(" --> "); + if (classLoader == null) { + buf.append("BOOT"); + break; + } + } + logDiagnostic(buf.toString()); + } + } + + /** + * Returns a string that uniquely identifies the specified object, including + * its class. + *

+ * The returned string is of form "classname@hashcode", ie is the same as + * the return value of the Object.toString() method, but works even when + * the specified object's class has overidden the toString method. + * + * @param o may be null. + * @return a string of form classname@hashcode, or "null" if param o is null. + * @since 1.1 + */ + public static String objectId(Object o) { + if (o == null) { + return "null"; + } else { + return o.getClass().getName() + "@" + System.identityHashCode(o); + } + } + + // ---------------------------------------------------------------------- + // Static initialiser block to perform initialisation at class load time. + // + // We can't do this in the class constructor, as there are many + // static methods on this class that can be called before any + // LogFactory instances are created, and they depend upon this + // stuff having been set up. + // + // Note that this block must come after any variable declarations used + // by any methods called from this block, as we want any static initialiser + // associated with the variable to run first. If static initialisers for + // variables run after this code, then (a) their value might be needed + // by methods called from here, and (b) they might *override* any value + // computed here! + // + // So the wisest thing to do is just to place this code at the very end + // of the class file. + // ---------------------------------------------------------------------- + + static { + // note: it's safe to call methods before initDiagnostics (though + // diagnostic output gets discarded). + thisClassLoader = getClassLoader(LogFactory.class); + // In order to avoid confusion where multiple instances of JCL are + // being used via different classloaders within the same app, we + // ensure each logged message has a prefix of form + // [LogFactory from classloader OID] + // + // Note that this prefix should be kept consistent with that + // in LogFactoryImpl. However here we don't need to output info + // about the actual *instance* of LogFactory, as all methods that + // output diagnostics from this class are static. + String classLoaderName; + try { + ClassLoader classLoader = thisClassLoader; + if (thisClassLoader == null) { + classLoaderName = "BOOTLOADER"; + } else { + classLoaderName = objectId(classLoader); + } + } catch (SecurityException e) { + classLoaderName = "UNKNOWN"; + } + diagnosticPrefix = "[LogFactory from " + classLoaderName + "] "; + diagnosticsStream = initDiagnostics(); + logClassLoaderEnvironment(LogFactory.class); + factories = createFactoryStore(); + if (isDiagnosticsEnabled()) { + logDiagnostic("BOOTSTRAP COMPLETED"); + } + } +} diff --git a/src/main/java/org/apache/commons/logging/LogSource.java b/src/main/java/org/apache/commons/logging/LogSource.java new file mode 100644 index 0000000..95a12c0 --- /dev/null +++ b/src/main/java/org/apache/commons/logging/LogSource.java @@ -0,0 +1,219 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import java.lang.reflect.Constructor; +import java.util.Hashtable; + +import org.apache.commons.logging.impl.NoOpLog; + +/** + * Factory for creating {@link Log} instances. Applications should call + * the makeNewLogInstance() method to instantiate new instances + * of the configured {@link Log} implementation class. + *

+ * By default, calling getInstance() will use the following + * algorithm: + *

    + *
  • If Log4J is available, return an instance of + * org.apache.commons.logging.impl.Log4JLogger.
  • + *
  • If JDK 1.4 or later is available, return an instance of + * org.apache.commons.logging.impl.Jdk14Logger.
  • + *
  • Otherwise, return an instance of + * org.apache.commons.logging.impl.NoOpLog.
  • + *
+ *

+ * You can change the default behavior in one of two ways: + *

    + *
  • On the startup command line, set the system property + * org.apache.commons.logging.log to the name of the + * org.apache.commons.logging.Log implementation class + * you want to use.
  • + *
  • At runtime, call LogSource.setLogImplementation().
  • + *
+ * + * @deprecated Use {@link LogFactory} instead - The default factory + * implementation performs exactly the same algorithm as this class did + * + * @version $Id: LogSource.java 1432675 2013-01-13 17:53:30Z tn $ + */ +public class LogSource { + + // ------------------------------------------------------- Class Attributes + + static protected Hashtable logs = new Hashtable(); + + /** Is log4j available (in the current classpath) */ + static protected boolean log4jIsAvailable = false; + + /** Is JDK 1.4 logging available */ + static protected boolean jdk14IsAvailable = false; + + /** Constructor for current log class */ + static protected Constructor logImplctor = null; + + // ----------------------------------------------------- Class Initializers + + static { + + // Is Log4J Available? + try { + log4jIsAvailable = null != Class.forName("org.apache.log4j.Logger"); + } catch (Throwable t) { + log4jIsAvailable = false; + } + + // Is JDK 1.4 Logging Available? + try { + jdk14IsAvailable = null != Class.forName("java.util.logging.Logger") && + null != Class.forName("org.apache.commons.logging.impl.Jdk14Logger"); + } catch (Throwable t) { + jdk14IsAvailable = false; + } + + // Set the default Log implementation + String name = null; + try { + name = System.getProperty("org.apache.commons.logging.log"); + if (name == null) { + name = System.getProperty("org.apache.commons.logging.Log"); + } + } catch (Throwable t) { + } + if (name != null) { + try { + setLogImplementation(name); + } catch (Throwable t) { + try { + setLogImplementation("org.apache.commons.logging.impl.NoOpLog"); + } catch (Throwable u) { + // ignored + } + } + } else { + try { + if (log4jIsAvailable) { + setLogImplementation("org.apache.commons.logging.impl.Log4JLogger"); + } else if (jdk14IsAvailable) { + setLogImplementation("org.apache.commons.logging.impl.Jdk14Logger"); + } else { + setLogImplementation("org.apache.commons.logging.impl.NoOpLog"); + } + } catch (Throwable t) { + try { + setLogImplementation("org.apache.commons.logging.impl.NoOpLog"); + } catch (Throwable u) { + // ignored + } + } + } + + } + + // ------------------------------------------------------------ Constructor + + /** Don't allow others to create instances. */ + private LogSource() { + } + + // ---------------------------------------------------------- Class Methods + + /** + * Set the log implementation/log implementation factory + * by the name of the class. The given class must implement {@link Log}, + * and provide a constructor that takes a single {@link String} argument + * (containing the name of the log). + */ + static public void setLogImplementation(String classname) + throws LinkageError, NoSuchMethodException, SecurityException, ClassNotFoundException { + try { + Class logclass = Class.forName(classname); + Class[] argtypes = new Class[1]; + argtypes[0] = "".getClass(); + logImplctor = logclass.getConstructor(argtypes); + } catch (Throwable t) { + logImplctor = null; + } + } + + /** + * Set the log implementation/log implementation factory by class. + * The given class must implement {@link Log}, and provide a constructor + * that takes a single {@link String} argument (containing the name of the log). + */ + static public void setLogImplementation(Class logclass) + throws LinkageError, ExceptionInInitializerError, NoSuchMethodException, SecurityException { + Class[] argtypes = new Class[1]; + argtypes[0] = "".getClass(); + logImplctor = logclass.getConstructor(argtypes); + } + + /** Get a Log instance by class name. */ + static public Log getInstance(String name) { + Log log = (Log) logs.get(name); + if (null == log) { + log = makeNewLogInstance(name); + logs.put(name, log); + } + return log; + } + + /** Get a Log instance by class. */ + static public Log getInstance(Class clazz) { + return getInstance(clazz.getName()); + } + + /** + * Create a new {@link Log} implementation, based on the given name. + *

+ * The specific {@link Log} implementation returned is determined by the + * value of the org.apache.commons.logging.log property. The value + * of org.apache.commons.logging.log may be set to the fully specified + * name of a class that implements the {@link Log} interface. This class must + * also have a public constructor that takes a single {@link String} argument + * (containing the name of the {@link Log} to be constructed. + *

+ * When org.apache.commons.logging.log is not set, or when no corresponding + * class can be found, this method will return a Log4JLogger if the log4j Logger + * class is available in the {@link LogSource}'s classpath, or a Jdk14Logger if we + * are on a JDK 1.4 or later system, or NoOpLog if neither of the above conditions is true. + * + * @param name the log name (or category) + */ + static public Log makeNewLogInstance(String name) { + Log log; + try { + Object[] args = { name }; + log = (Log) logImplctor.newInstance(args); + } catch (Throwable t) { + log = null; + } + if (null == log) { + log = new NoOpLog(name); + } + return log; + } + + /** + * Returns a {@link String} array containing the names of + * all logs known to me. + */ + static public String[] getLogNames() { + return (String[]) logs.keySet().toArray(new String[logs.size()]); + } +} diff --git a/src/main/java/org/apache/commons/logging/impl/AvalonLogger.java b/src/main/java/org/apache/commons/logging/impl/AvalonLogger.java new file mode 100644 index 0000000..344462b --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/AvalonLogger.java @@ -0,0 +1,298 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.impl; + +import org.apache.avalon.framework.logger.Logger; +import org.apache.commons.logging.Log; + +/** + * Implementation of commons-logging Log interface that delegates all + * logging calls to the Avalon logging abstraction: the Logger interface. + *

+ * There are two ways in which this class can be used: + *

    + *
  • the instance can be constructed with an Avalon logger + * (by calling {@link #AvalonLogger(Logger)}). In this case, it acts + * as a simple thin wrapping implementation over the logger. This is + * particularly useful when using a property setter. + *
  • + *
  • the {@link #setDefaultLogger} class property can be called which + * sets the ancestral Avalon logger for this class. Any AvalonLogger + * instances created through the LogFactory mechanisms will output + * to child loggers of this Logger. + *
  • + *
+ *

+ * Note: AvalonLogger does not implement Serializable + * because the constructors available for it make this impossible to achieve in all + * circumstances; there is no way to "reconnect" to an underlying Logger object on + * deserialization if one was just passed in to the constructor of the original + * object. This class was marked Serializable in the 1.0.4 release of + * commons-logging, but this never actually worked (a NullPointerException would + * be thrown as soon as the deserialized object was used), so removing this marker + * is not considered to be an incompatible change. + * + * @version $Id: AvalonLogger.java 1435115 2013-01-18 12:40:19Z tn $ + */ +public class AvalonLogger implements Log { + + /** Ancestral Avalon logger. */ + private static volatile Logger defaultLogger = null; + /** Avalon logger used to perform log. */ + private final transient Logger logger; + + /** + * Constructs an AvalonLogger that outputs to the given + * Logger instance. + * + * @param logger the Avalon logger implementation to delegate to + */ + public AvalonLogger(Logger logger) { + this.logger = logger; + } + + /** + * Constructs an AvalonLogger that will log to a child + * of the Logger set by calling {@link #setDefaultLogger}. + * + * @param name the name of the avalon logger implementation to delegate to + */ + public AvalonLogger(String name) { + if (defaultLogger == null) { + throw new NullPointerException("default logger has to be specified if this constructor is used!"); + } + this.logger = defaultLogger.getChildLogger(name); + } + + /** + * Gets the Avalon logger implementation used to perform logging. + * + * @return avalon logger implementation + */ + public Logger getLogger() { + return logger; + } + + /** + * Sets the ancestral Avalon logger from which the delegating loggers will descend. + * + * @param logger the default avalon logger, + * in case there is no logger instance supplied in constructor + */ + public static void setDefaultLogger(Logger logger) { + defaultLogger = logger; + } + + /** + * Logs a message with org.apache.avalon.framework.logger.Logger.debug. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#debug(Object, Throwable) + */ + public void debug(Object message, Throwable t) { + if (getLogger().isDebugEnabled()) { + getLogger().debug(String.valueOf(message), t); + } + } + + /** + * Logs a message with org.apache.avalon.framework.logger.Logger.debug. + * + * @param message to log. + * @see org.apache.commons.logging.Log#debug(Object) + */ + public void debug(Object message) { + if (getLogger().isDebugEnabled()) { + getLogger().debug(String.valueOf(message)); + } + } + + /** + * Logs a message with org.apache.avalon.framework.logger.Logger.error. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#error(Object, Throwable) + */ + public void error(Object message, Throwable t) { + if (getLogger().isErrorEnabled()) { + getLogger().error(String.valueOf(message), t); + } + } + + /** + * Logs a message with org.apache.avalon.framework.logger.Logger.error. + * + * @param message to log + * @see org.apache.commons.logging.Log#error(Object) + */ + public void error(Object message) { + if (getLogger().isErrorEnabled()) { + getLogger().error(String.valueOf(message)); + } + } + + /** + * Logs a message with org.apache.avalon.framework.logger.Logger.fatalError. + * + * @param message to log. + * @param t log this cause. + * @see org.apache.commons.logging.Log#fatal(Object, Throwable) + */ + public void fatal(Object message, Throwable t) { + if (getLogger().isFatalErrorEnabled()) { + getLogger().fatalError(String.valueOf(message), t); + } + } + + /** + * Logs a message with org.apache.avalon.framework.logger.Logger.fatalError. + * + * @param message to log + * @see org.apache.commons.logging.Log#fatal(Object) + */ + public void fatal(Object message) { + if (getLogger().isFatalErrorEnabled()) { + getLogger().fatalError(String.valueOf(message)); + } + } + + /** + * Logs a message with org.apache.avalon.framework.logger.Logger.info. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#info(Object, Throwable) + */ + public void info(Object message, Throwable t) { + if (getLogger().isInfoEnabled()) { + getLogger().info(String.valueOf(message), t); + } + } + + /** + * Logs a message with org.apache.avalon.framework.logger.Logger.info. + * + * @param message to log + * @see org.apache.commons.logging.Log#info(Object) + */ + public void info(Object message) { + if (getLogger().isInfoEnabled()) { + getLogger().info(String.valueOf(message)); + } + } + + /** + * Is logging to org.apache.avalon.framework.logger.Logger.debug enabled? + * @see org.apache.commons.logging.Log#isDebugEnabled() + */ + public boolean isDebugEnabled() { + return getLogger().isDebugEnabled(); + } + + /** + * Is logging to org.apache.avalon.framework.logger.Logger.error enabled? + * @see org.apache.commons.logging.Log#isErrorEnabled() + */ + public boolean isErrorEnabled() { + return getLogger().isErrorEnabled(); + } + + /** + * Is logging to org.apache.avalon.framework.logger.Logger.fatalError enabled? + * @see org.apache.commons.logging.Log#isFatalEnabled() + */ + public boolean isFatalEnabled() { + return getLogger().isFatalErrorEnabled(); + } + + /** + * Is logging to org.apache.avalon.framework.logger.Logger.info enabled? + * @see org.apache.commons.logging.Log#isInfoEnabled() + */ + public boolean isInfoEnabled() { + return getLogger().isInfoEnabled(); + } + + /** + * Is logging to org.apache.avalon.framework.logger.Logger.debug enabled? + * @see org.apache.commons.logging.Log#isTraceEnabled() + */ + public boolean isTraceEnabled() { + return getLogger().isDebugEnabled(); + } + + /** + * Is logging to org.apache.avalon.framework.logger.Logger.warn enabled? + * @see org.apache.commons.logging.Log#isWarnEnabled() + */ + public boolean isWarnEnabled() { + return getLogger().isWarnEnabled(); + } + + /** + * Logs a message with org.apache.avalon.framework.logger.Logger.debug. + * + * @param message to log. + * @param t log this cause. + * @see org.apache.commons.logging.Log#trace(Object, Throwable) + */ + public void trace(Object message, Throwable t) { + if (getLogger().isDebugEnabled()) { + getLogger().debug(String.valueOf(message), t); + } + } + + /** + * Logs a message with org.apache.avalon.framework.logger.Logger.debug. + * + * @param message to log + * @see org.apache.commons.logging.Log#trace(Object) + */ + public void trace(Object message) { + if (getLogger().isDebugEnabled()) { + getLogger().debug(String.valueOf(message)); + } + } + + /** + * Logs a message with org.apache.avalon.framework.logger.Logger.warn. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#warn(Object, Throwable) + */ + public void warn(Object message, Throwable t) { + if (getLogger().isWarnEnabled()) { + getLogger().warn(String.valueOf(message), t); + } + } + + /** + * Logs a message with org.apache.avalon.framework.logger.Logger.warn. + * + * @param message to log + * @see org.apache.commons.logging.Log#warn(Object) + */ + public void warn(Object message) { + if (getLogger().isWarnEnabled()) { + getLogger().warn(String.valueOf(message)); + } + } +} diff --git a/src/main/java/org/apache/commons/logging/impl/Jdk13LumberjackLogger.java b/src/main/java/org/apache/commons/logging/impl/Jdk13LumberjackLogger.java new file mode 100644 index 0000000..da3faa4 --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/Jdk13LumberjackLogger.java @@ -0,0 +1,302 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.impl; + +import java.io.Serializable; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.LogRecord; +import java.util.StringTokenizer; +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.apache.commons.logging.Log; + +/** + * Implementation of the org.apache.commons.logging.Log + * interface that wraps the standard JDK logging mechanisms that are + * available in SourceForge's Lumberjack for JDKs prior to 1.4. + * + * @version $Id: Jdk13LumberjackLogger.java 1432663 2013-01-13 17:24:18Z tn $ + * @since 1.1 + */ +public class Jdk13LumberjackLogger implements Log, Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -8649807923527610591L; + + // ----------------------------------------------------- Instance Variables + + /** + * The underlying Logger implementation we are using. + */ + protected transient Logger logger = null; + protected String name = null; + private String sourceClassName = "unknown"; + private String sourceMethodName = "unknown"; + private boolean classAndMethodFound = false; + + /** + * This member variable simply ensures that any attempt to initialise + * this class in a pre-1.4 JVM will result in an ExceptionInInitializerError. + * It must not be private, as an optimising compiler could detect that it + * is not used and optimise it away. + */ + protected static final Level dummyLevel = Level.FINE; + + // ----------------------------------------------------------- Constructors + + /** + * Construct a named instance of this Logger. + * + * @param name Name of the logger to be constructed + */ + public Jdk13LumberjackLogger(String name) { + this.name = name; + logger = getLogger(); + } + + // --------------------------------------------------------- Public Methods + + private void log( Level level, String msg, Throwable ex ) { + if( getLogger().isLoggable(level) ) { + LogRecord record = new LogRecord(level, msg); + if( !classAndMethodFound ) { + getClassAndMethod(); + } + record.setSourceClassName(sourceClassName); + record.setSourceMethodName(sourceMethodName); + if( ex != null ) { + record.setThrown(ex); + } + getLogger().log(record); + } + } + + /** + * Gets the class and method by looking at the stack trace for the + * first entry that is not this class. + */ + private void getClassAndMethod() { + try { + Throwable throwable = new Throwable(); + throwable.fillInStackTrace(); + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter( stringWriter ); + throwable.printStackTrace( printWriter ); + String traceString = stringWriter.getBuffer().toString(); + StringTokenizer tokenizer = + new StringTokenizer( traceString, "\n" ); + tokenizer.nextToken(); + String line = tokenizer.nextToken(); + while ( line.indexOf( this.getClass().getName() ) == -1 ) { + line = tokenizer.nextToken(); + } + while ( line.indexOf( this.getClass().getName() ) >= 0 ) { + line = tokenizer.nextToken(); + } + int start = line.indexOf( "at " ) + 3; + int end = line.indexOf( '(' ); + String temp = line.substring( start, end ); + int lastPeriod = temp.lastIndexOf( '.' ); + sourceClassName = temp.substring( 0, lastPeriod ); + sourceMethodName = temp.substring( lastPeriod + 1 ); + } catch ( Exception ex ) { + // ignore - leave class and methodname unknown + } + classAndMethodFound = true; + } + + /** + * Logs a message with java.util.logging.Level.FINE. + * + * @param message to log + * @see org.apache.commons.logging.Log#debug(Object) + */ + public void debug(Object message) { + log(Level.FINE, String.valueOf(message), null); + } + + /** + * Logs a message with java.util.logging.Level.FINE. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#debug(Object, Throwable) + */ + public void debug(Object message, Throwable exception) { + log(Level.FINE, String.valueOf(message), exception); + } + + /** + * Logs a message with java.util.logging.Level.SEVERE. + * + * @param message to log + * @see org.apache.commons.logging.Log#error(Object) + */ + public void error(Object message) { + log(Level.SEVERE, String.valueOf(message), null); + } + + /** + * Logs a message with java.util.logging.Level.SEVERE. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#error(Object, Throwable) + */ + public void error(Object message, Throwable exception) { + log(Level.SEVERE, String.valueOf(message), exception); + } + + /** + * Logs a message with java.util.logging.Level.SEVERE. + * + * @param message to log + * @see org.apache.commons.logging.Log#fatal(Object) + */ + public void fatal(Object message) { + log(Level.SEVERE, String.valueOf(message), null); + } + + /** + * Logs a message with java.util.logging.Level.SEVERE. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#fatal(Object, Throwable) + */ + public void fatal(Object message, Throwable exception) { + log(Level.SEVERE, String.valueOf(message), exception); + } + + /** + * Return the native Logger instance we are using. + */ + public Logger getLogger() { + if (logger == null) { + logger = Logger.getLogger(name); + } + return logger; + } + + /** + * Logs a message with java.util.logging.Level.INFO. + * + * @param message to log + * @see org.apache.commons.logging.Log#info(Object) + */ + public void info(Object message) { + log(Level.INFO, String.valueOf(message), null); + } + + /** + * Logs a message with java.util.logging.Level.INFO. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#info(Object, Throwable) + */ + public void info(Object message, Throwable exception) { + log(Level.INFO, String.valueOf(message), exception); + } + + /** + * Is debug logging currently enabled? + */ + public boolean isDebugEnabled() { + return getLogger().isLoggable(Level.FINE); + } + + /** + * Is error logging currently enabled? + */ + public boolean isErrorEnabled() { + return getLogger().isLoggable(Level.SEVERE); + } + + /** + * Is fatal logging currently enabled? + */ + public boolean isFatalEnabled() { + return getLogger().isLoggable(Level.SEVERE); + } + + /** + * Is info logging currently enabled? + */ + public boolean isInfoEnabled() { + return getLogger().isLoggable(Level.INFO); + } + + /** + * Is trace logging currently enabled? + */ + public boolean isTraceEnabled() { + return getLogger().isLoggable(Level.FINEST); + } + + /** + * Is warn logging currently enabled? + */ + public boolean isWarnEnabled() { + return getLogger().isLoggable(Level.WARNING); + } + + /** + * Logs a message with java.util.logging.Level.FINEST. + * + * @param message to log + * @see org.apache.commons.logging.Log#trace(Object) + */ + public void trace(Object message) { + log(Level.FINEST, String.valueOf(message), null); + } + + /** + * Logs a message with java.util.logging.Level.FINEST. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#trace(Object, Throwable) + */ + public void trace(Object message, Throwable exception) { + log(Level.FINEST, String.valueOf(message), exception); + } + + /** + * Logs a message with java.util.logging.Level.WARNING. + * + * @param message to log + * @see org.apache.commons.logging.Log#warn(Object) + */ + public void warn(Object message) { + log(Level.WARNING, String.valueOf(message), null); + } + + /** + * Logs a message with java.util.logging.Level.WARNING. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#warn(Object, Throwable) + */ + public void warn(Object message, Throwable exception) { + log(Level.WARNING, String.valueOf(message), exception); + } +} diff --git a/src/main/java/org/apache/commons/logging/impl/Jdk14Logger.java b/src/main/java/org/apache/commons/logging/impl/Jdk14Logger.java new file mode 100644 index 0000000..7a19acb --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/Jdk14Logger.java @@ -0,0 +1,273 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.impl; + +import java.io.Serializable; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.logging.Log; + +/** + * Implementation of the org.apache.commons.logging.Log + * interface that wraps the standard JDK logging mechanisms that were + * introduced in the Merlin release (JDK 1.4). + * + * @version $Id: Jdk14Logger.java 1448063 2013-02-20 10:01:41Z tn $ + */ +public class Jdk14Logger implements Log, Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 4784713551416303804L; + + /** + * This member variable simply ensures that any attempt to initialise + * this class in a pre-1.4 JVM will result in an ExceptionInInitializerError. + * It must not be private, as an optimising compiler could detect that it + * is not used and optimise it away. + */ + protected static final Level dummyLevel = Level.FINE; + + // ----------------------------------------------------------- Constructors + + /** + * Construct a named instance of this Logger. + * + * @param name Name of the logger to be constructed + */ + public Jdk14Logger(String name) { + this.name = name; + logger = getLogger(); + } + + // ----------------------------------------------------- Instance Variables + + /** + * The underlying Logger implementation we are using. + */ + protected transient Logger logger = null; + + /** + * The name of the logger we are wrapping. + */ + protected String name = null; + + // --------------------------------------------------------- Protected Methods + + protected void log( Level level, String msg, Throwable ex ) { + Logger logger = getLogger(); + if (logger.isLoggable(level)) { + // Hack (?) to get the stack trace. + Throwable dummyException = new Throwable(); + StackTraceElement locations[] = dummyException.getStackTrace(); + // LOGGING-132: use the provided logger name instead of the class name + String cname = name; + String method = "unknown"; + // Caller will be the third element + if( locations != null && locations.length > 2 ) { + StackTraceElement caller = locations[2]; + method = caller.getMethodName(); + } + if( ex == null ) { + logger.logp( level, cname, method, msg ); + } else { + logger.logp( level, cname, method, msg, ex ); + } + } + } + + // --------------------------------------------------------- Public Methods + + /** + * Logs a message with java.util.logging.Level.FINE. + * + * @param message to log + * @see org.apache.commons.logging.Log#debug(Object) + */ + public void debug(Object message) { + log(Level.FINE, String.valueOf(message), null); + } + + /** + * Logs a message with java.util.logging.Level.FINE. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#debug(Object, Throwable) + */ + public void debug(Object message, Throwable exception) { + log(Level.FINE, String.valueOf(message), exception); + } + + /** + * Logs a message with java.util.logging.Level.SEVERE. + * + * @param message to log + * @see org.apache.commons.logging.Log#error(Object) + */ + public void error(Object message) { + log(Level.SEVERE, String.valueOf(message), null); + } + + /** + * Logs a message with java.util.logging.Level.SEVERE. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#error(Object, Throwable) + */ + public void error(Object message, Throwable exception) { + log(Level.SEVERE, String.valueOf(message), exception); + } + + /** + * Logs a message with java.util.logging.Level.SEVERE. + * + * @param message to log + * @see org.apache.commons.logging.Log#fatal(Object) + */ + public void fatal(Object message) { + log(Level.SEVERE, String.valueOf(message), null); + } + + /** + * Logs a message with java.util.logging.Level.SEVERE. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#fatal(Object, Throwable) + */ + public void fatal(Object message, Throwable exception) { + log(Level.SEVERE, String.valueOf(message), exception); + } + + /** + * Return the native Logger instance we are using. + */ + public Logger getLogger() { + if (logger == null) { + logger = Logger.getLogger(name); + } + return logger; + } + + /** + * Logs a message with java.util.logging.Level.INFO. + * + * @param message to log + * @see org.apache.commons.logging.Log#info(Object) + */ + public void info(Object message) { + log(Level.INFO, String.valueOf(message), null); + } + + /** + * Logs a message with java.util.logging.Level.INFO. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#info(Object, Throwable) + */ + public void info(Object message, Throwable exception) { + log(Level.INFO, String.valueOf(message), exception); + } + + /** + * Is debug logging currently enabled? + */ + public boolean isDebugEnabled() { + return getLogger().isLoggable(Level.FINE); + } + + /** + * Is error logging currently enabled? + */ + public boolean isErrorEnabled() { + return getLogger().isLoggable(Level.SEVERE); + } + + /** + * Is fatal logging currently enabled? + */ + public boolean isFatalEnabled() { + return getLogger().isLoggable(Level.SEVERE); + } + + /** + * Is info logging currently enabled? + */ + public boolean isInfoEnabled() { + return getLogger().isLoggable(Level.INFO); + } + + /** + * Is trace logging currently enabled? + */ + public boolean isTraceEnabled() { + return getLogger().isLoggable(Level.FINEST); + } + + /** + * Is warn logging currently enabled? + */ + public boolean isWarnEnabled() { + return getLogger().isLoggable(Level.WARNING); + } + + /** + * Logs a message with java.util.logging.Level.FINEST. + * + * @param message to log + * @see org.apache.commons.logging.Log#trace(Object) + */ + public void trace(Object message) { + log(Level.FINEST, String.valueOf(message), null); + } + + /** + * Logs a message with java.util.logging.Level.FINEST. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#trace(Object, Throwable) + */ + public void trace(Object message, Throwable exception) { + log(Level.FINEST, String.valueOf(message), exception); + } + + /** + * Logs a message with java.util.logging.Level.WARNING. + * + * @param message to log + * @see org.apache.commons.logging.Log#warn(Object) + */ + public void warn(Object message) { + log(Level.WARNING, String.valueOf(message), null); + } + + /** + * Logs a message with java.util.logging.Level.WARNING. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#warn(Object, Throwable) + */ + public void warn(Object message, Throwable exception) { + log(Level.WARNING, String.valueOf(message), exception); + } +} diff --git a/src/main/java/org/apache/commons/logging/impl/Log4JLogger.java b/src/main/java/org/apache/commons/logging/impl/Log4JLogger.java new file mode 100644 index 0000000..de3b140 --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/Log4JLogger.java @@ -0,0 +1,312 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.impl; + +import java.io.Serializable; +import org.apache.commons.logging.Log; +import org.apache.log4j.Logger; +import org.apache.log4j.Priority; +import org.apache.log4j.Level; + +/** + * Implementation of {@link Log} that maps directly to a + * Logger for log4J version 1.2. + *

+ * Initial configuration of the corresponding Logger instances should be done + * in the usual manner, as outlined in the Log4J documentation. + *

+ * The reason this logger is distinct from the 1.3 logger is that in version 1.2 + * of Log4J: + *

    + *
  • class Logger takes Priority parameters not Level parameters. + *
  • class Level extends Priority + *
+ * Log4J1.3 is expected to change Level so it no longer extends Priority, which is + * a non-binary-compatible change. The class generated by compiling this code against + * log4j 1.2 will therefore not run against log4j 1.3. + * + * @version $Id: Log4JLogger.java 1448119 2013-02-20 12:28:04Z tn $ + */ +public class Log4JLogger implements Log, Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 5160705895411730424L; + + // ------------------------------------------------------------- Attributes + + /** The fully qualified name of the Log4JLogger class. */ + private static final String FQCN = Log4JLogger.class.getName(); + + /** Log to this logger */ + private transient volatile Logger logger = null; + + /** Logger name */ + private final String name; + + private static final Priority traceLevel; + + // ------------------------------------------------------------ + // Static Initializer. + // + // Note that this must come after the static variable declarations + // otherwise initialiser expressions associated with those variables + // will override any settings done here. + // + // Verify that log4j is available, and that it is version 1.2. + // If an ExceptionInInitializerError is generated, then LogFactoryImpl + // will treat that as meaning that the appropriate underlying logging + // library is just not present - if discovery is in progress then + // discovery will continue. + // ------------------------------------------------------------ + + static { + if (!Priority.class.isAssignableFrom(Level.class)) { + // nope, this is log4j 1.3, so force an ExceptionInInitializerError + throw new InstantiationError("Log4J 1.2 not available"); + } + + // Releases of log4j1.2 >= 1.2.12 have Priority.TRACE available, earlier + // versions do not. If TRACE is not available, then we have to map + // calls to Log.trace(...) onto the DEBUG level. + + Priority _traceLevel; + try { + _traceLevel = (Priority) Level.class.getDeclaredField("TRACE").get(null); + } catch(Exception ex) { + // ok, trace not available + _traceLevel = Level.DEBUG; + } + traceLevel = _traceLevel; + } + + // ------------------------------------------------------------ Constructor + + public Log4JLogger() { + name = null; + } + + /** + * Base constructor. + */ + public Log4JLogger(String name) { + this.name = name; + this.logger = getLogger(); + } + + /** + * For use with a log4j factory. + */ + public Log4JLogger(Logger logger) { + if (logger == null) { + throw new IllegalArgumentException( + "Warning - null logger in constructor; possible log4j misconfiguration."); + } + this.name = logger.getName(); + this.logger = logger; + } + + /** + * Logs a message with org.apache.log4j.Priority.TRACE. + * When using a log4j version that does not support the TRACE + * level, the message will be logged at the DEBUG level. + * + * @param message to log + * @see org.apache.commons.logging.Log#trace(Object) + */ + public void trace(Object message) { + getLogger().log(FQCN, traceLevel, message, null); + } + + /** + * Logs a message with org.apache.log4j.Priority.TRACE. + * When using a log4j version that does not support the TRACE + * level, the message will be logged at the DEBUG level. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#trace(Object, Throwable) + */ + public void trace(Object message, Throwable t) { + getLogger().log(FQCN, traceLevel, message, t); + } + + /** + * Logs a message with org.apache.log4j.Priority.DEBUG. + * + * @param message to log + * @see org.apache.commons.logging.Log#debug(Object) + */ + public void debug(Object message) { + getLogger().log(FQCN, Level.DEBUG, message, null); + } + + /** + * Logs a message with org.apache.log4j.Priority.DEBUG. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#debug(Object, Throwable) + */ + public void debug(Object message, Throwable t) { + getLogger().log(FQCN, Level.DEBUG, message, t); + } + + /** + * Logs a message with org.apache.log4j.Priority.INFO. + * + * @param message to log + * @see org.apache.commons.logging.Log#info(Object) + */ + public void info(Object message) { + getLogger().log(FQCN, Level.INFO, message, null); + } + + /** + * Logs a message with org.apache.log4j.Priority.INFO. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#info(Object, Throwable) + */ + public void info(Object message, Throwable t) { + getLogger().log(FQCN, Level.INFO, message, t); + } + + /** + * Logs a message with org.apache.log4j.Priority.WARN. + * + * @param message to log + * @see org.apache.commons.logging.Log#warn(Object) + */ + public void warn(Object message) { + getLogger().log(FQCN, Level.WARN, message, null); + } + + /** + * Logs a message with org.apache.log4j.Priority.WARN. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#warn(Object, Throwable) + */ + public void warn(Object message, Throwable t) { + getLogger().log(FQCN, Level.WARN, message, t); + } + + /** + * Logs a message with org.apache.log4j.Priority.ERROR. + * + * @param message to log + * @see org.apache.commons.logging.Log#error(Object) + */ + public void error(Object message) { + getLogger().log(FQCN, Level.ERROR, message, null); + } + + /** + * Logs a message with org.apache.log4j.Priority.ERROR. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#error(Object, Throwable) + */ + public void error(Object message, Throwable t) { + getLogger().log(FQCN, Level.ERROR, message, t); + } + + /** + * Logs a message with org.apache.log4j.Priority.FATAL. + * + * @param message to log + * @see org.apache.commons.logging.Log#fatal(Object) + */ + public void fatal(Object message) { + getLogger().log(FQCN, Level.FATAL, message, null); + } + + /** + * Logs a message with org.apache.log4j.Priority.FATAL. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#fatal(Object, Throwable) + */ + public void fatal(Object message, Throwable t) { + getLogger().log(FQCN, Level.FATAL, message, t); + } + + /** + * Return the native Logger instance we are using. + */ + public Logger getLogger() { + Logger result = logger; + if (result == null) { + synchronized(this) { + result = logger; + if (result == null) { + logger = result = Logger.getLogger(name); + } + } + } + return result; + } + + /** + * Check whether the Log4j Logger used is enabled for DEBUG priority. + */ + public boolean isDebugEnabled() { + return getLogger().isDebugEnabled(); + } + + /** + * Check whether the Log4j Logger used is enabled for ERROR priority. + */ + public boolean isErrorEnabled() { + return getLogger().isEnabledFor(Level.ERROR); + } + + /** + * Check whether the Log4j Logger used is enabled for FATAL priority. + */ + public boolean isFatalEnabled() { + return getLogger().isEnabledFor(Level.FATAL); + } + + /** + * Check whether the Log4j Logger used is enabled for INFO priority. + */ + public boolean isInfoEnabled() { + return getLogger().isInfoEnabled(); + } + + /** + * Check whether the Log4j Logger used is enabled for TRACE priority. + * When using a log4j version that does not support the TRACE level, this call + * will report whether DEBUG is enabled or not. + */ + public boolean isTraceEnabled() { + return getLogger().isEnabledFor(traceLevel); + } + + /** + * Check whether the Log4j Logger used is enabled for WARN priority. + */ + public boolean isWarnEnabled() { + return getLogger().isEnabledFor(Level.WARN); + } +} diff --git a/src/main/java/org/apache/commons/logging/impl/LogFactoryImpl.java b/src/main/java/org/apache/commons/logging/impl/LogFactoryImpl.java new file mode 100644 index 0000000..7ec3c27 --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/LogFactoryImpl.java @@ -0,0 +1,1393 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.impl; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Hashtable; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogConfigurationException; +import org.apache.commons.logging.LogFactory; + +/** + * Concrete subclass of {@link LogFactory} that implements the + * following algorithm to dynamically select a logging implementation + * class to instantiate a wrapper for: + *
    + *
  • Use a factory configuration attribute named + * org.apache.commons.logging.Log to identify the + * requested implementation class.
  • + *
  • Use the org.apache.commons.logging.Log system property + * to identify the requested implementation class.
  • + *
  • If Log4J is available, return an instance of + * org.apache.commons.logging.impl.Log4JLogger.
  • + *
  • If JDK 1.4 or later is available, return an instance of + * org.apache.commons.logging.impl.Jdk14Logger.
  • + *
  • Otherwise, return an instance of + * org.apache.commons.logging.impl.SimpleLog.
  • + *
+ *

+ * If the selected {@link Log} implementation class has a + * setLogFactory() method that accepts a {@link LogFactory} + * parameter, this method will be called on each newly created instance + * to identify the associated factory. This makes factory configuration + * attributes available to the Log instance, if it so desires. + *

+ * This factory will remember previously created Log instances + * for the same name, and will return them on repeated requests to the + * getInstance() method. + * + * @version $Id: LogFactoryImpl.java 1449064 2013-02-22 14:49:22Z tn $ + */ +public class LogFactoryImpl extends LogFactory { + + /** Log4JLogger class name */ + private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger"; + /** Jdk14Logger class name */ + private static final String LOGGING_IMPL_JDK14_LOGGER = "org.apache.commons.logging.impl.Jdk14Logger"; + /** Jdk13LumberjackLogger class name */ + private static final String LOGGING_IMPL_LUMBERJACK_LOGGER = + "org.apache.commons.logging.impl.Jdk13LumberjackLogger"; + + /** SimpleLog class name */ + private static final String LOGGING_IMPL_SIMPLE_LOGGER = "org.apache.commons.logging.impl.SimpleLog"; + + private static final String PKG_IMPL="org.apache.commons.logging.impl."; + private static final int PKG_LEN = PKG_IMPL.length(); + + // ----------------------------------------------------------- Constructors + + /** + * Public no-arguments constructor required by the lookup mechanism. + */ + public LogFactoryImpl() { + super(); + initDiagnostics(); // method on this object + if (isDiagnosticsEnabled()) { + logDiagnostic("Instance created."); + } + } + + // ----------------------------------------------------- Manifest Constants + + /** + * The name (org.apache.commons.logging.Log) of the system + * property identifying our {@link Log} implementation class. + */ + public static final String LOG_PROPERTY = "org.apache.commons.logging.Log"; + + /** + * The deprecated system property used for backwards compatibility with + * old versions of JCL. + */ + protected static final String LOG_PROPERTY_OLD = "org.apache.commons.logging.log"; + + /** + * The name (org.apache.commons.logging.Log.allowFlawedContext) + * of the system property which can be set true/false to + * determine system behaviour when a bad context-classloader is encountered. + * When set to false, a LogConfigurationException is thrown if + * LogFactoryImpl is loaded via a child classloader of the TCCL (this + * should never happen in sane systems). + * + * Default behaviour: true (tolerates bad context classloaders) + * + * See also method setAttribute. + */ + public static final String ALLOW_FLAWED_CONTEXT_PROPERTY = + "org.apache.commons.logging.Log.allowFlawedContext"; + + /** + * The name (org.apache.commons.logging.Log.allowFlawedDiscovery) + * of the system property which can be set true/false to + * determine system behaviour when a bad logging adapter class is + * encountered during logging discovery. When set to false, an + * exception will be thrown and the app will fail to start. When set + * to true, discovery will continue (though the user might end up + * with a different logging implementation than they expected). + *

+ * Default behaviour: true (tolerates bad logging adapters) + * + * See also method setAttribute. + */ + public static final String ALLOW_FLAWED_DISCOVERY_PROPERTY = + "org.apache.commons.logging.Log.allowFlawedDiscovery"; + + /** + * The name (org.apache.commons.logging.Log.allowFlawedHierarchy) + * of the system property which can be set true/false to + * determine system behaviour when a logging adapter class is + * encountered which has bound to the wrong Log class implementation. + * When set to false, an exception will be thrown and the app will fail + * to start. When set to true, discovery will continue (though the user + * might end up with a different logging implementation than they expected). + *

+ * Default behaviour: true (tolerates bad Log class hierarchy) + * + * See also method setAttribute. + */ + public static final String ALLOW_FLAWED_HIERARCHY_PROPERTY = + "org.apache.commons.logging.Log.allowFlawedHierarchy"; + + /** + * The names of classes that will be tried (in order) as logging + * adapters. Each class is expected to implement the Log interface, + * and to throw NoClassDefFound or ExceptionInInitializerError when + * loaded if the underlying logging library is not available. Any + * other error indicates that the underlying logging library is available + * but broken/unusable for some reason. + */ + private static final String[] classesToDiscover = { + LOGGING_IMPL_LOG4J_LOGGER, + "org.apache.commons.logging.impl.Jdk14Logger", + "org.apache.commons.logging.impl.Jdk13LumberjackLogger", + "org.apache.commons.logging.impl.SimpleLog" + }; + + // ----------------------------------------------------- Instance Variables + + /** + * Determines whether logging classes should be loaded using the thread-context + * classloader, or via the classloader that loaded this LogFactoryImpl class. + */ + private boolean useTCCL = true; + + /** + * The string prefixed to every message output by the logDiagnostic method. + */ + private String diagnosticPrefix; + + /** + * Configuration attributes. + */ + protected Hashtable attributes = new Hashtable(); + + /** + * The {@link org.apache.commons.logging.Log} instances that have + * already been created, keyed by logger name. + */ + protected Hashtable instances = new Hashtable(); + + /** + * Name of the class implementing the Log interface. + */ + private String logClassName; + + /** + * The one-argument constructor of the + * {@link org.apache.commons.logging.Log} + * implementation class that will be used to create new instances. + * This value is initialized by getLogConstructor(), + * and then returned repeatedly. + */ + protected Constructor logConstructor = null; + + /** + * The signature of the Constructor to be used. + */ + protected Class logConstructorSignature[] = { java.lang.String.class }; + + /** + * The one-argument setLogFactory method of the selected + * {@link org.apache.commons.logging.Log} method, if it exists. + */ + protected Method logMethod = null; + + /** + * The signature of the setLogFactory method to be used. + */ + protected Class logMethodSignature[] = { LogFactory.class }; + + /** + * See getBaseClassLoader and initConfiguration. + */ + private boolean allowFlawedContext; + + /** + * See handleFlawedDiscovery and initConfiguration. + */ + private boolean allowFlawedDiscovery; + + /** + * See handleFlawedHierarchy and initConfiguration. + */ + private boolean allowFlawedHierarchy; + + // --------------------------------------------------------- Public Methods + + /** + * Return the configuration attribute with the specified name (if any), + * or null if there is no such attribute. + * + * @param name Name of the attribute to return + */ + public Object getAttribute(String name) { + return attributes.get(name); + } + + /** + * Return an array containing the names of all currently defined + * configuration attributes. If there are no such attributes, a zero + * length array is returned. + */ + public String[] getAttributeNames() { + return (String[]) attributes.keySet().toArray(new String[attributes.size()]); + } + + /** + * Convenience method to derive a name from the specified class and + * call getInstance(String) with it. + * + * @param clazz Class for which a suitable Log name will be derived + * + * @exception LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public Log getInstance(Class clazz) throws LogConfigurationException { + return getInstance(clazz.getName()); + } + + /** + *

Construct (if necessary) and return a Log instance, + * using the factory's current set of configuration attributes.

+ * + *

NOTE - Depending upon the implementation of + * the LogFactory you are using, the Log + * instance you are returned may or may not be local to the current + * application, and may or may not be returned again on a subsequent + * call with the same name argument.

+ * + * @param name Logical name of the Log instance to be + * returned (the meaning of this name is only known to the underlying + * logging implementation that is being wrapped) + * + * @exception LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public Log getInstance(String name) throws LogConfigurationException { + Log instance = (Log) instances.get(name); + if (instance == null) { + instance = newInstance(name); + instances.put(name, instance); + } + return instance; + } + + /** + * Release any internal references to previously created + * {@link org.apache.commons.logging.Log} + * instances returned by this factory. This is useful in environments + * like servlet containers, which implement application reloading by + * throwing away a ClassLoader. Dangling references to objects in that + * class loader would prevent garbage collection. + */ + public void release() { + + logDiagnostic("Releasing all known loggers"); + instances.clear(); + } + + /** + * Remove any configuration attribute associated with the specified name. + * If there is no such attribute, no action is taken. + * + * @param name Name of the attribute to remove + */ + public void removeAttribute(String name) { + attributes.remove(name); + } + + /** + * Set the configuration attribute with the specified name. Calling + * this with a null value is equivalent to calling + * removeAttribute(name). + *

+ * This method can be used to set logging configuration programmatically + * rather than via system properties. It can also be used in code running + * within a container (such as a webapp) to configure behaviour on a + * per-component level instead of globally as system properties would do. + * To use this method instead of a system property, call + *

+     * LogFactory.getFactory().setAttribute(...)
+     * 
+ * This must be done before the first Log object is created; configuration + * changes after that point will be ignored. + *

+ * This method is also called automatically if LogFactory detects a + * commons-logging.properties file; every entry in that file is set + * automatically as an attribute here. + * + * @param name Name of the attribute to set + * @param value Value of the attribute to set, or null + * to remove any setting for this attribute + */ + public void setAttribute(String name, Object value) { + if (logConstructor != null) { + logDiagnostic("setAttribute: call too late; configuration already performed."); + } + + if (value == null) { + attributes.remove(name); + } else { + attributes.put(name, value); + } + + if (name.equals(TCCL_KEY)) { + useTCCL = value != null && Boolean.valueOf(value.toString()).booleanValue(); + } + } + + // ------------------------------------------------------ + // Static Methods + // + // These methods only defined as workarounds for a java 1.2 bug; + // theoretically none of these are needed. + // ------------------------------------------------------ + + /** + * Gets the context classloader. + * This method is a workaround for a java 1.2 compiler bug. + * @since 1.1 + */ + protected static ClassLoader getContextClassLoader() throws LogConfigurationException { + return LogFactory.getContextClassLoader(); + } + + /** + * Workaround for bug in Java1.2; in theory this method is not needed. + * See LogFactory.isDiagnosticsEnabled. + */ + protected static boolean isDiagnosticsEnabled() { + return LogFactory.isDiagnosticsEnabled(); + } + + /** + * Workaround for bug in Java1.2; in theory this method is not needed. + * See LogFactory.getClassLoader. + * @since 1.1 + */ + protected static ClassLoader getClassLoader(Class clazz) { + return LogFactory.getClassLoader(clazz); + } + + // ------------------------------------------------------ Protected Methods + + /** + * Calculate and cache a string that uniquely identifies this instance, + * including which classloader the object was loaded from. + *

+ * This string will later be prefixed to each "internal logging" message + * emitted, so that users can clearly see any unexpected behaviour. + *

+ * Note that this method does not detect whether internal logging is + * enabled or not, nor where to output stuff if it is; that is all + * handled by the parent LogFactory class. This method just computes + * its own unique prefix for log messages. + */ + private void initDiagnostics() { + // It would be nice to include an identifier of the context classloader + // that this LogFactoryImpl object is responsible for. However that + // isn't possible as that information isn't available. It is possible + // to figure this out by looking at the logging from LogFactory to + // see the context & impl ids from when this object was instantiated, + // in order to link the impl id output as this object's prefix back to + // the context it is intended to manage. + // Note that this prefix should be kept consistent with that + // in LogFactory. + Class clazz = this.getClass(); + ClassLoader classLoader = getClassLoader(clazz); + String classLoaderName; + try { + if (classLoader == null) { + classLoaderName = "BOOTLOADER"; + } else { + classLoaderName = objectId(classLoader); + } + } catch (SecurityException e) { + classLoaderName = "UNKNOWN"; + } + diagnosticPrefix = "[LogFactoryImpl@" + System.identityHashCode(this) + " from " + classLoaderName + "] "; + } + + /** + * Output a diagnostic message to a user-specified destination (if the + * user has enabled diagnostic logging). + * + * @param msg diagnostic message + * @since 1.1 + */ + protected void logDiagnostic(String msg) { + if (isDiagnosticsEnabled()) { + logRawDiagnostic(diagnosticPrefix + msg); + } + } + + /** + * Return the fully qualified Java classname of the {@link Log} + * implementation we will be using. + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected String getLogClassName() { + if (logClassName == null) { + discoverLogImplementation(getClass().getName()); + } + + return logClassName; + } + + + /** + *

Return the Constructor that can be called to instantiate + * new {@link org.apache.commons.logging.Log} instances.

+ * + *

IMPLEMENTATION NOTE - Race conditions caused by + * calling this method from more than one thread are ignored, because + * the same Constructor instance will ultimately be derived + * in all circumstances.

+ * + * @exception LogConfigurationException if a suitable constructor + * cannot be returned + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected Constructor getLogConstructor() + throws LogConfigurationException { + + // Return the previously identified Constructor (if any) + if (logConstructor == null) { + discoverLogImplementation(getClass().getName()); + } + + return logConstructor; + } + + /** + * Is JDK 1.3 with Lumberjack logging available? + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected boolean isJdk13LumberjackAvailable() { + return isLogLibraryAvailable( + "Jdk13Lumberjack", + "org.apache.commons.logging.impl.Jdk13LumberjackLogger"); + } + + /** + * Return true if JDK 1.4 or later logging + * is available. Also checks that the Throwable class + * supports getStackTrace(), which is required by + * Jdk14Logger. + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected boolean isJdk14Available() { + return isLogLibraryAvailable( + "Jdk14", + "org.apache.commons.logging.impl.Jdk14Logger"); + } + + /** + * Is a Log4J implementation available? + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected boolean isLog4JAvailable() { + return isLogLibraryAvailable( + "Log4J", + LOGGING_IMPL_LOG4J_LOGGER); + } + + /** + * Create and return a new {@link org.apache.commons.logging.Log} + * instance for the specified name. + * + * @param name Name of the new logger + * + * @exception LogConfigurationException if a new instance cannot + * be created + */ + protected Log newInstance(String name) throws LogConfigurationException { + Log instance; + try { + if (logConstructor == null) { + instance = discoverLogImplementation(name); + } + else { + Object params[] = { name }; + instance = (Log) logConstructor.newInstance(params); + } + + if (logMethod != null) { + Object params[] = { this }; + logMethod.invoke(instance, params); + } + + return instance; + + } catch (LogConfigurationException lce) { + + // this type of exception means there was a problem in discovery + // and we've already output diagnostics about the issue, etc.; + // just pass it on + throw lce; + + } catch (InvocationTargetException e) { + // A problem occurred invoking the Constructor or Method + // previously discovered + Throwable c = e.getTargetException(); + throw new LogConfigurationException(c == null ? e : c); + } catch (Throwable t) { + handleThrowable(t); // may re-throw t + // A problem occurred invoking the Constructor or Method + // previously discovered + throw new LogConfigurationException(t); + } + } + + // ------------------------------------------------------ Private Methods + + /** + * Calls LogFactory.directGetContextClassLoader under the control of an + * AccessController class. This means that java code running under a + * security manager that forbids access to ClassLoaders will still work + * if this class is given appropriate privileges, even when the caller + * doesn't have such privileges. Without using an AccessController, the + * the entire call stack must have the privilege before the call is + * allowed. + * + * @return the context classloader associated with the current thread, + * or null if security doesn't allow it. + * + * @throws LogConfigurationException if there was some weird error while + * attempting to get the context classloader. + * + * @throws SecurityException if the current java security policy doesn't + * allow this class to access the context classloader. + */ + private static ClassLoader getContextClassLoaderInternal() + throws LogConfigurationException { + return (ClassLoader)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return LogFactory.directGetContextClassLoader(); + } + }); + } + + /** + * Read the specified system property, using an AccessController so that + * the property can be read if JCL has been granted the appropriate + * security rights even if the calling code has not. + *

+ * Take care not to expose the value returned by this method to the + * calling application in any way; otherwise the calling app can use that + * info to access data that should not be available to it. + */ + private static String getSystemProperty(final String key, final String def) + throws SecurityException { + return (String) AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return System.getProperty(key, def); + } + }); + } + + /** + * Fetch the parent classloader of a specified classloader. + *

+ * If a SecurityException occurs, null is returned. + *

+ * Note that this method is non-static merely so logDiagnostic is available. + */ + private ClassLoader getParentClassLoader(final ClassLoader cl) { + try { + return (ClassLoader)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return cl.getParent(); + } + }); + } catch (SecurityException ex) { + logDiagnostic("[SECURITY] Unable to obtain parent classloader"); + return null; + } + + } + + /** + * Utility method to check whether a particular logging library is + * present and available for use. Note that this does not + * affect the future behaviour of this class. + */ + private boolean isLogLibraryAvailable(String name, String classname) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Checking for '" + name + "'."); + } + try { + Log log = createLogFromClass( + classname, + this.getClass().getName(), // dummy category + false); + + if (log == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Did not find '" + name + "'."); + } + return false; + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic("Found '" + name + "'."); + } + return true; + } + } catch (LogConfigurationException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Logging system '" + name + "' is available but not useable."); + } + return false; + } + } + + /** + * Attempt to find an attribute (see method setAttribute) or a + * system property with the provided name and return its value. + *

+ * The attributes associated with this object are checked before + * system properties in case someone has explicitly called setAttribute, + * or a configuration property has been set in a commons-logging.properties + * file. + * + * @return the value associated with the property, or null. + */ + private String getConfigurationValue(String property) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Trying to get configuration for item " + property); + } + + Object valueObj = getAttribute(property); + if (valueObj != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Found LogFactory attribute [" + valueObj + "] for " + property); + } + return valueObj.toString(); + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] No LogFactory attribute found for " + property); + } + + try { + // warning: minor security hole here, in that we potentially read a system + // property that the caller cannot, then output it in readable form as a + // diagnostic message. However it's only ever JCL-specific properties + // involved here, so the harm is truly trivial. + String value = getSystemProperty(property, null); + if (value != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Found system property [" + value + "] for " + property); + } + return value; + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] No system property found for property " + property); + } + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Security prevented reading system property " + property); + } + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] No configuration defined for item " + property); + } + + return null; + } + + /** + * Get the setting for the user-configurable behaviour specified by key. + * If nothing has explicitly been set, then return dflt. + */ + private boolean getBooleanConfiguration(String key, boolean dflt) { + String val = getConfigurationValue(key); + if (val == null) { + return dflt; + } + return Boolean.valueOf(val).booleanValue(); + } + + /** + * Initialize a number of variables that control the behaviour of this + * class and that can be tweaked by the user. This is done when the first + * logger is created, not in the constructor of this class, because we + * need to give the user a chance to call method setAttribute in order to + * configure this object. + */ + private void initConfiguration() { + allowFlawedContext = getBooleanConfiguration(ALLOW_FLAWED_CONTEXT_PROPERTY, true); + allowFlawedDiscovery = getBooleanConfiguration(ALLOW_FLAWED_DISCOVERY_PROPERTY, true); + allowFlawedHierarchy = getBooleanConfiguration(ALLOW_FLAWED_HIERARCHY_PROPERTY, true); + } + + /** + * Attempts to create a Log instance for the given category name. + * Follows the discovery process described in the class javadoc. + * + * @param logCategory the name of the log category + * + * @throws LogConfigurationException if an error in discovery occurs, + * or if no adapter at all can be instantiated + */ + private Log discoverLogImplementation(String logCategory) + throws LogConfigurationException { + if (isDiagnosticsEnabled()) { + logDiagnostic("Discovering a Log implementation..."); + } + + initConfiguration(); + + Log result = null; + + // See if the user specified the Log implementation to use + String specifiedLogClassName = findUserSpecifiedLogClassName(); + + if (specifiedLogClassName != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Attempting to load user-specified log class '" + + specifiedLogClassName + "'..."); + } + + result = createLogFromClass(specifiedLogClassName, + logCategory, + true); + if (result == null) { + StringBuffer messageBuffer = new StringBuffer("User-specified log class '"); + messageBuffer.append(specifiedLogClassName); + messageBuffer.append("' cannot be found or is not useable."); + + // Mistyping or misspelling names is a common fault. + // Construct a good error message, if we can + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER); + throw new LogConfigurationException(messageBuffer.toString()); + } + + return result; + } + + // No user specified log; try to discover what's on the classpath + // + // Note that we deliberately loop here over classesToDiscover and + // expect method createLogFromClass to loop over the possible source + // classloaders. The effect is: + // for each discoverable log adapter + // for each possible classloader + // see if it works + // + // It appears reasonable at first glance to do the opposite: + // for each possible classloader + // for each discoverable log adapter + // see if it works + // + // The latter certainly has advantages for user-installable logging + // libraries such as log4j; in a webapp for example this code should + // first check whether the user has provided any of the possible + // logging libraries before looking in the parent classloader. + // Unfortunately, however, Jdk14Logger will always work in jvm>=1.4, + // and SimpleLog will always work in any JVM. So the loop would never + // ever look for logging libraries in the parent classpath. Yet many + // users would expect that putting log4j there would cause it to be + // detected (and this is the historical JCL behaviour). So we go with + // the first approach. A user that has bundled a specific logging lib + // in a webapp should use a commons-logging.properties file or a + // service file in META-INF to force use of that logging lib anyway, + // rather than relying on discovery. + + if (isDiagnosticsEnabled()) { + logDiagnostic( + "No user-specified Log implementation; performing discovery" + + " using the standard supported logging implementations..."); + } + for(int i=0; iStringBuffer the message should be appended to, + * not null + * @param name the (trimmed) name to be test against the candidate, not null + * @param candidate the candidate name (not null) + */ + private void informUponSimilarName(final StringBuffer messageBuffer, final String name, + final String candidate) { + if (name.equals(candidate)) { + // Don't suggest a name that is exactly the same as the one the + // user tried... + return; + } + + // If the user provides a name that is in the right package, and gets + // the first 5 characters of the adapter class right (ignoring case), + // then suggest the candidate adapter class name. + if (name.regionMatches(true, 0, candidate, 0, PKG_LEN + 5)) { + messageBuffer.append(" Did you mean '"); + messageBuffer.append(candidate); + messageBuffer.append("'?"); + } + } + + /** + * Checks system properties and the attribute map for + * a Log implementation specified by the user under the + * property names {@link #LOG_PROPERTY} or {@link #LOG_PROPERTY_OLD}. + * + * @return classname specified by the user, or null + */ + private String findUserSpecifiedLogClassName() { + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from attribute '" + LOG_PROPERTY + "'"); + } + String specifiedClass = (String) getAttribute(LOG_PROPERTY); + + if (specifiedClass == null) { // @deprecated + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from attribute '" + + LOG_PROPERTY_OLD + "'"); + } + specifiedClass = (String) getAttribute(LOG_PROPERTY_OLD); + } + + if (specifiedClass == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from system property '" + + LOG_PROPERTY + "'"); + } + try { + specifiedClass = getSystemProperty(LOG_PROPERTY, null); + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("No access allowed to system property '" + + LOG_PROPERTY + "' - " + e.getMessage()); + } + } + } + + if (specifiedClass == null) { // @deprecated + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from system property '" + + LOG_PROPERTY_OLD + "'"); + } + try { + specifiedClass = getSystemProperty(LOG_PROPERTY_OLD, null); + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("No access allowed to system property '" + + LOG_PROPERTY_OLD + "' - " + e.getMessage()); + } + } + } + + // Remove any whitespace; it's never valid in a classname so its + // presence just means a user mistake. As we know what they meant, + // we may as well strip the spaces. + if (specifiedClass != null) { + specifiedClass = specifiedClass.trim(); + } + + return specifiedClass; + } + + /** + * Attempts to load the given class, find a suitable constructor, + * and instantiate an instance of Log. + * + * @param logAdapterClassName classname of the Log implementation + * @param logCategory argument to pass to the Log implementation's constructor + * @param affectState true if this object's state should + * be affected by this method call, false otherwise. + * @return an instance of the given class, or null if the logging + * library associated with the specified adapter is not available. + * @throws LogConfigurationException if there was a serious error with + * configuration and the handleFlawedDiscovery method decided this + * problem was fatal. + */ + private Log createLogFromClass(String logAdapterClassName, + String logCategory, + boolean affectState) + throws LogConfigurationException { + + if (isDiagnosticsEnabled()) { + logDiagnostic("Attempting to instantiate '" + logAdapterClassName + "'"); + } + + Object[] params = { logCategory }; + Log logAdapter = null; + Constructor constructor = null; + + Class logAdapterClass = null; + ClassLoader currentCL = getBaseClassLoader(); + + for(;;) { + // Loop through the classloader hierarchy trying to find + // a viable classloader. + logDiagnostic("Trying to load '" + logAdapterClassName + "' from classloader " + objectId(currentCL)); + try { + if (isDiagnosticsEnabled()) { + // Show the location of the first occurrence of the .class file + // in the classpath. This is the location that ClassLoader.loadClass + // will load the class from -- unless the classloader is doing + // something weird. + URL url; + String resourceName = logAdapterClassName.replace('.', '/') + ".class"; + if (currentCL != null) { + url = currentCL.getResource(resourceName ); + } else { + url = ClassLoader.getSystemResource(resourceName + ".class"); + } + + if (url == null) { + logDiagnostic("Class '" + logAdapterClassName + "' [" + resourceName + "] cannot be found."); + } else { + logDiagnostic("Class '" + logAdapterClassName + "' was found at '" + url + "'"); + } + } + + Class c; + try { + c = Class.forName(logAdapterClassName, true, currentCL); + } catch (ClassNotFoundException originalClassNotFoundException) { + // The current classloader was unable to find the log adapter + // in this or any ancestor classloader. There's no point in + // trying higher up in the hierarchy in this case.. + String msg = originalClassNotFoundException.getMessage(); + logDiagnostic("The log adapter '" + logAdapterClassName + "' is not available via classloader " + + objectId(currentCL) + ": " + msg.trim()); + try { + // Try the class classloader. + // This may work in cases where the TCCL + // does not contain the code executed or JCL. + // This behaviour indicates that the application + // classloading strategy is not consistent with the + // Java 1.2 classloading guidelines but JCL can + // and so should handle this case. + c = Class.forName(logAdapterClassName); + } catch (ClassNotFoundException secondaryClassNotFoundException) { + // no point continuing: this adapter isn't available + msg = secondaryClassNotFoundException.getMessage(); + logDiagnostic("The log adapter '" + logAdapterClassName + + "' is not available via the LogFactoryImpl class classloader: " + msg.trim()); + break; + } + } + + constructor = c.getConstructor(logConstructorSignature); + Object o = constructor.newInstance(params); + + // Note that we do this test after trying to create an instance + // [rather than testing Log.class.isAssignableFrom(c)] so that + // we don't complain about Log hierarchy problems when the + // adapter couldn't be instantiated anyway. + if (o instanceof Log) { + logAdapterClass = c; + logAdapter = (Log) o; + break; + } + + // Oops, we have a potential problem here. An adapter class + // has been found and its underlying lib is present too, but + // there are multiple Log interface classes available making it + // impossible to cast to the type the caller wanted. We + // certainly can't use this logger, but we need to know whether + // to keep on discovering or terminate now. + // + // The handleFlawedHierarchy method will throw + // LogConfigurationException if it regards this problem as + // fatal, and just return if not. + handleFlawedHierarchy(currentCL, c); + } catch (NoClassDefFoundError e) { + // We were able to load the adapter but it had references to + // other classes that could not be found. This simply means that + // the underlying logger library is not present in this or any + // ancestor classloader. There's no point in trying higher up + // in the hierarchy in this case.. + String msg = e.getMessage(); + logDiagnostic("The log adapter '" + logAdapterClassName + + "' is missing dependencies when loaded via classloader " + objectId(currentCL) + + ": " + msg.trim()); + break; + } catch (ExceptionInInitializerError e) { + // A static initializer block or the initializer code associated + // with a static variable on the log adapter class has thrown + // an exception. + // + // We treat this as meaning the adapter's underlying logging + // library could not be found. + String msg = e.getMessage(); + logDiagnostic("The log adapter '" + logAdapterClassName + + "' is unable to initialize itself when loaded via classloader " + objectId(currentCL) + + ": " + msg.trim()); + break; + } catch (LogConfigurationException e) { + // call to handleFlawedHierarchy above must have thrown + // a LogConfigurationException, so just throw it on + throw e; + } catch (Throwable t) { + handleThrowable(t); // may re-throw t + // handleFlawedDiscovery will determine whether this is a fatal + // problem or not. If it is fatal, then a LogConfigurationException + // will be thrown. + handleFlawedDiscovery(logAdapterClassName, currentCL, t); + } + + if (currentCL == null) { + break; + } + + // try the parent classloader + // currentCL = currentCL.getParent(); + currentCL = getParentClassLoader(currentCL); + } + + if (logAdapterClass != null && affectState) { + // We've succeeded, so set instance fields + this.logClassName = logAdapterClassName; + this.logConstructor = constructor; + + // Identify the setLogFactory method (if there is one) + try { + this.logMethod = logAdapterClass.getMethod("setLogFactory", logMethodSignature); + logDiagnostic("Found method setLogFactory(LogFactory) in '" + logAdapterClassName + "'"); + } catch (Throwable t) { + handleThrowable(t); // may re-throw t + this.logMethod = null; + logDiagnostic("[INFO] '" + logAdapterClassName + "' from classloader " + objectId(currentCL) + + " does not declare optional method " + "setLogFactory(LogFactory)"); + } + + logDiagnostic("Log adapter '" + logAdapterClassName + "' from classloader " + + objectId(logAdapterClass.getClassLoader()) + " has been selected for use."); + } + + return logAdapter; + } + + /** + * Return the classloader from which we should try to load the logging + * adapter classes. + *

+ * This method usually returns the context classloader. However if it + * is discovered that the classloader which loaded this class is a child + * of the context classloader and the allowFlawedContext option + * has been set then the classloader which loaded this class is returned + * instead. + *

+ * The only time when the classloader which loaded this class is a + * descendant (rather than the same as or an ancestor of the context + * classloader) is when an app has created custom classloaders but + * failed to correctly set the context classloader. This is a bug in + * the calling application; however we provide the option for JCL to + * simply generate a warning rather than fail outright. + * + */ + private ClassLoader getBaseClassLoader() throws LogConfigurationException { + ClassLoader thisClassLoader = getClassLoader(LogFactoryImpl.class); + + if (!useTCCL) { + return thisClassLoader; + } + + ClassLoader contextClassLoader = getContextClassLoaderInternal(); + + ClassLoader baseClassLoader = getLowestClassLoader( + contextClassLoader, thisClassLoader); + + if (baseClassLoader == null) { + // The two classloaders are not part of a parent child relationship. + // In some classloading setups (e.g. JBoss with its + // UnifiedLoaderRepository) this can still work, so if user hasn't + // forbidden it, just return the contextClassLoader. + if (allowFlawedContext) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[WARNING] the context classloader is not part of a" + + " parent-child relationship with the classloader that" + + " loaded LogFactoryImpl."); + } + // If contextClassLoader were null, getLowestClassLoader() would + // have returned thisClassLoader. The fact we are here means + // contextClassLoader is not null, so we can just return it. + return contextClassLoader; + } + else { + throw new LogConfigurationException("Bad classloader hierarchy; LogFactoryImpl was loaded via" + + " a classloader that is not related to the current context" + + " classloader."); + } + } + + if (baseClassLoader != contextClassLoader) { + // We really should just use the contextClassLoader as the starting + // point for scanning for log adapter classes. However it is expected + // that there are a number of broken systems out there which create + // custom classloaders but fail to set the context classloader so + // we handle those flawed systems anyway. + if (allowFlawedContext) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "Warning: the context classloader is an ancestor of the" + + " classloader that loaded LogFactoryImpl; it should be" + + " the same or a descendant. The application using" + + " commons-logging should ensure the context classloader" + + " is used correctly."); + } + } else { + throw new LogConfigurationException( + "Bad classloader hierarchy; LogFactoryImpl was loaded via" + + " a classloader that is not related to the current context" + + " classloader."); + } + } + + return baseClassLoader; + } + + /** + * Given two related classloaders, return the one which is a child of + * the other. + *

+ * @param c1 is a classloader (including the null classloader) + * @param c2 is a classloader (including the null classloader) + * + * @return c1 if it has c2 as an ancestor, c2 if it has c1 as an ancestor, + * and null if neither is an ancestor of the other. + */ + private ClassLoader getLowestClassLoader(ClassLoader c1, ClassLoader c2) { + // TODO: use AccessController when dealing with classloaders here + + if (c1 == null) { + return c2; + } + + if (c2 == null) { + return c1; + } + + ClassLoader current; + + // scan c1's ancestors to find c2 + current = c1; + while (current != null) { + if (current == c2) { + return c1; + } + // current = current.getParent(); + current = getParentClassLoader(current); + } + + // scan c2's ancestors to find c1 + current = c2; + while (current != null) { + if (current == c1) { + return c2; + } + // current = current.getParent(); + current = getParentClassLoader(current); + } + + return null; + } + + /** + * Generates an internal diagnostic logging of the discovery failure and + * then throws a LogConfigurationException that wraps + * the passed Throwable. + * + * @param logAdapterClassName is the class name of the Log implementation + * that could not be instantiated. Cannot be null. + * + * @param classLoader is the classloader that we were trying to load the + * logAdapterClassName from when the exception occurred. + * + * @param discoveryFlaw is the Throwable created by the classloader + * + * @throws LogConfigurationException ALWAYS + */ + private void handleFlawedDiscovery(String logAdapterClassName, + ClassLoader classLoader, // USED? + Throwable discoveryFlaw) { + + if (isDiagnosticsEnabled()) { + logDiagnostic("Could not instantiate Log '" + + logAdapterClassName + "' -- " + + discoveryFlaw.getClass().getName() + ": " + + discoveryFlaw.getLocalizedMessage()); + + if (discoveryFlaw instanceof InvocationTargetException ) { + // Ok, the lib is there but while trying to create a real underlying + // logger something failed in the underlying lib; display info about + // that if possible. + InvocationTargetException ite = (InvocationTargetException)discoveryFlaw; + Throwable cause = ite.getTargetException(); + if (cause != null) { + logDiagnostic("... InvocationTargetException: " + + cause.getClass().getName() + ": " + + cause.getLocalizedMessage()); + + if (cause instanceof ExceptionInInitializerError) { + ExceptionInInitializerError eiie = (ExceptionInInitializerError)cause; + Throwable cause2 = eiie.getException(); + if (cause2 != null) { + final StringWriter sw = new StringWriter(); + cause2.printStackTrace(new PrintWriter(sw, true)); + logDiagnostic("... ExceptionInInitializerError: " + sw.toString()); + } + } + } + } + } + + if (!allowFlawedDiscovery) { + throw new LogConfigurationException(discoveryFlaw); + } + } + + /** + * Report a problem loading the log adapter, then either return + * (if the situation is considered recoverable) or throw a + * LogConfigurationException. + *

+ * There are two possible reasons why we successfully loaded the + * specified log adapter class then failed to cast it to a Log object: + *

    + *
  1. the specific class just doesn't implement the Log interface + * (user screwed up), or + *
  2. the specified class has bound to a Log class loaded by some other + * classloader; Log@classloaderX cannot be cast to Log@classloaderY. + *
+ *

+ * Here we try to figure out which case has occurred so we can give the + * user some reasonable feedback. + * + * @param badClassLoader is the classloader we loaded the problem class from, + * ie it is equivalent to badClass.getClassLoader(). + * + * @param badClass is a Class object with the desired name, but which + * does not implement Log correctly. + * + * @throws LogConfigurationException when the situation + * should not be recovered from. + */ + private void handleFlawedHierarchy(ClassLoader badClassLoader, Class badClass) + throws LogConfigurationException { + + boolean implementsLog = false; + String logInterfaceName = Log.class.getName(); + Class interfaces[] = badClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + if (logInterfaceName.equals(interfaces[i].getName())) { + implementsLog = true; + break; + } + } + + if (implementsLog) { + // the class does implement an interface called Log, but + // it is in the wrong classloader + if (isDiagnosticsEnabled()) { + try { + ClassLoader logInterfaceClassLoader = getClassLoader(Log.class); + logDiagnostic("Class '" + badClass.getName() + "' was found in classloader " + + objectId(badClassLoader) + ". It is bound to a Log interface which is not" + + " the one loaded from classloader " + objectId(logInterfaceClassLoader)); + } catch (Throwable t) { + handleThrowable(t); // may re-throw t + logDiagnostic("Error while trying to output diagnostics about" + " bad class '" + badClass + "'"); + } + } + + if (!allowFlawedHierarchy) { + StringBuffer msg = new StringBuffer(); + msg.append("Terminating logging for this context "); + msg.append("due to bad log hierarchy. "); + msg.append("You have more than one version of '"); + msg.append(Log.class.getName()); + msg.append("' visible."); + if (isDiagnosticsEnabled()) { + logDiagnostic(msg.toString()); + } + throw new LogConfigurationException(msg.toString()); + } + + if (isDiagnosticsEnabled()) { + StringBuffer msg = new StringBuffer(); + msg.append("Warning: bad log hierarchy. "); + msg.append("You have more than one version of '"); + msg.append(Log.class.getName()); + msg.append("' visible."); + logDiagnostic(msg.toString()); + } + } else { + // this is just a bad adapter class + if (!allowFlawedDiscovery) { + StringBuffer msg = new StringBuffer(); + msg.append("Terminating logging for this context. "); + msg.append("Log class '"); + msg.append(badClass.getName()); + msg.append("' does not implement the Log interface."); + if (isDiagnosticsEnabled()) { + logDiagnostic(msg.toString()); + } + + throw new LogConfigurationException(msg.toString()); + } + + if (isDiagnosticsEnabled()) { + StringBuffer msg = new StringBuffer(); + msg.append("[WARNING] Log class '"); + msg.append(badClass.getName()); + msg.append("' does not implement the Log interface."); + logDiagnostic(msg.toString()); + } + } + } +} diff --git a/src/main/java/org/apache/commons/logging/impl/LogKitLogger.java b/src/main/java/org/apache/commons/logging/impl/LogKitLogger.java new file mode 100644 index 0000000..6feaa31 --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/LogKitLogger.java @@ -0,0 +1,269 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.impl; + +import java.io.Serializable; +import org.apache.log.Logger; +import org.apache.log.Hierarchy; +import org.apache.commons.logging.Log; + +/** + * Implementation of org.apache.commons.logging.Log + * that wraps the avalon-logkit + * logging system. Configuration of LogKit is left to the user. + *

+ * LogKit accepts only String messages. + * Therefore, this implementation converts object messages into strings + * by called their toString() method before logging them. + * + * @version $Id: LogKitLogger.java 1448119 2013-02-20 12:28:04Z tn $ + */ +public class LogKitLogger implements Log, Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 3768538055836059519L; + + // ------------------------------------------------------------- Attributes + + /** Logging goes to this LogKit logger */ + protected transient volatile Logger logger = null; + + /** Name of this logger */ + protected String name = null; + + // ------------------------------------------------------------ Constructor + + /** + * Construct LogKitLogger which wraps the LogKit + * logger with given name. + * + * @param name log name + */ + public LogKitLogger(String name) { + this.name = name; + this.logger = getLogger(); + } + + // --------------------------------------------------------- Public Methods + + /** + * Return the underlying Logger we are using. + */ + public Logger getLogger() { + Logger result = logger; + if (result == null) { + synchronized(this) { + result = logger; + if (result == null) { + logger = result = Hierarchy.getDefaultHierarchy().getLoggerFor(name); + } + } + } + return result; + } + + // ----------------------------------------------------- Log Implementation + + /** + * Logs a message with org.apache.log.Priority.DEBUG. + * + * @param message to log + * @see org.apache.commons.logging.Log#trace(Object) + */ + public void trace(Object message) { + debug(message); + } + + /** + * Logs a message with org.apache.log.Priority.DEBUG. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#trace(Object, Throwable) + */ + public void trace(Object message, Throwable t) { + debug(message, t); + } + + /** + * Logs a message with org.apache.log.Priority.DEBUG. + * + * @param message to log + * @see org.apache.commons.logging.Log#debug(Object) + */ + public void debug(Object message) { + if (message != null) { + getLogger().debug(String.valueOf(message)); + } + } + + /** + * Logs a message with org.apache.log.Priority.DEBUG. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#debug(Object, Throwable) + */ + public void debug(Object message, Throwable t) { + if (message != null) { + getLogger().debug(String.valueOf(message), t); + } + } + + /** + * Logs a message with org.apache.log.Priority.INFO. + * + * @param message to log + * @see org.apache.commons.logging.Log#info(Object) + */ + public void info(Object message) { + if (message != null) { + getLogger().info(String.valueOf(message)); + } + } + + /** + * Logs a message with org.apache.log.Priority.INFO. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#info(Object, Throwable) + */ + public void info(Object message, Throwable t) { + if (message != null) { + getLogger().info(String.valueOf(message), t); + } + } + + /** + * Logs a message with org.apache.log.Priority.WARN. + * + * @param message to log + * @see org.apache.commons.logging.Log#warn(Object) + */ + public void warn(Object message) { + if (message != null) { + getLogger().warn(String.valueOf(message)); + } + } + + /** + * Logs a message with org.apache.log.Priority.WARN. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#warn(Object, Throwable) + */ + public void warn(Object message, Throwable t) { + if (message != null) { + getLogger().warn(String.valueOf(message), t); + } + } + + /** + * Logs a message with org.apache.log.Priority.ERROR. + * + * @param message to log + * @see org.apache.commons.logging.Log#error(Object) + */ + public void error(Object message) { + if (message != null) { + getLogger().error(String.valueOf(message)); + } + } + + /** + * Logs a message with org.apache.log.Priority.ERROR. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#error(Object, Throwable) + */ + public void error(Object message, Throwable t) { + if (message != null) { + getLogger().error(String.valueOf(message), t); + } + } + + /** + * Logs a message with org.apache.log.Priority.FATAL_ERROR. + * + * @param message to log + * @see org.apache.commons.logging.Log#fatal(Object) + */ + public void fatal(Object message) { + if (message != null) { + getLogger().fatalError(String.valueOf(message)); + } + } + + /** + * Logs a message with org.apache.log.Priority.FATAL_ERROR. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#fatal(Object, Throwable) + */ + public void fatal(Object message, Throwable t) { + if (message != null) { + getLogger().fatalError(String.valueOf(message), t); + } + } + + /** + * Checks whether the LogKit logger will log messages of priority DEBUG. + */ + public boolean isDebugEnabled() { + return getLogger().isDebugEnabled(); + } + + /** + * Checks whether the LogKit logger will log messages of priority ERROR. + */ + public boolean isErrorEnabled() { + return getLogger().isErrorEnabled(); + } + + /** + * Checks whether the LogKit logger will log messages of priority FATAL_ERROR. + */ + public boolean isFatalEnabled() { + return getLogger().isFatalErrorEnabled(); + } + + /** + * Checks whether the LogKit logger will log messages of priority INFO. + */ + public boolean isInfoEnabled() { + return getLogger().isInfoEnabled(); + } + + /** + * Checks whether the LogKit logger will log messages of priority DEBUG. + */ + public boolean isTraceEnabled() { + return getLogger().isDebugEnabled(); + } + + /** + * Checks whether the LogKit logger will log messages of priority WARN. + */ + public boolean isWarnEnabled() { + return getLogger().isWarnEnabled(); + } +} diff --git a/src/main/java/org/apache/commons/logging/impl/NoOpLog.java b/src/main/java/org/apache/commons/logging/impl/NoOpLog.java new file mode 100644 index 0000000..ef0de24 --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/NoOpLog.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.impl; + +import java.io.Serializable; +import org.apache.commons.logging.Log; + +/** + * Trivial implementation of Log that throws away all messages. No + * configurable system properties are supported. + * + * @version $Id: NoOpLog.java 1432663 2013-01-13 17:24:18Z tn $ + */ +public class NoOpLog implements Log, Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 561423906191706148L; + + /** Convenience constructor */ + public NoOpLog() { } + /** Base constructor */ + public NoOpLog(String name) { } + /** Do nothing */ + public void trace(Object message) { } + /** Do nothing */ + public void trace(Object message, Throwable t) { } + /** Do nothing */ + public void debug(Object message) { } + /** Do nothing */ + public void debug(Object message, Throwable t) { } + /** Do nothing */ + public void info(Object message) { } + /** Do nothing */ + public void info(Object message, Throwable t) { } + /** Do nothing */ + public void warn(Object message) { } + /** Do nothing */ + public void warn(Object message, Throwable t) { } + /** Do nothing */ + public void error(Object message) { } + /** Do nothing */ + public void error(Object message, Throwable t) { } + /** Do nothing */ + public void fatal(Object message) { } + /** Do nothing */ + public void fatal(Object message, Throwable t) { } + + /** + * Debug is never enabled. + * + * @return false + */ + public final boolean isDebugEnabled() { return false; } + + /** + * Error is never enabled. + * + * @return false + */ + public final boolean isErrorEnabled() { return false; } + + /** + * Fatal is never enabled. + * + * @return false + */ + public final boolean isFatalEnabled() { return false; } + + /** + * Info is never enabled. + * + * @return false + */ + public final boolean isInfoEnabled() { return false; } + + /** + * Trace is never enabled. + * + * @return false + */ + public final boolean isTraceEnabled() { return false; } + + /** + * Warn is never enabled. + * + * @return false + */ + public final boolean isWarnEnabled() { return false; } +} diff --git a/src/main/java/org/apache/commons/logging/impl/ServletContextCleaner.java b/src/main/java/org/apache/commons/logging/impl/ServletContextCleaner.java new file mode 100644 index 0000000..c7c2162 --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/ServletContextCleaner.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.impl; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.apache.commons.logging.LogFactory; + +/** + * This class is capable of receiving notifications about the undeployment of + * a webapp, and responds by ensuring that commons-logging releases all + * memory associated with the undeployed webapp. + *

+ * In general, the WeakHashtable support added in commons-logging release 1.1 + * ensures that logging classes do not hold references that prevent an + * undeployed webapp's memory from being garbage-collected even when multiple + * copies of commons-logging are deployed via multiple classloaders (a + * situation that earlier versions had problems with). However there are + * some rare cases where the WeakHashtable approach does not work; in these + * situations specifying this class as a listener for the web application will + * ensure that all references held by commons-logging are fully released. + *

+ * To use this class, configure the webapp deployment descriptor to call + * this class on webapp undeploy; the contextDestroyed method will tell + * every accessible LogFactory class that the entry in its map for the + * current webapp's context classloader should be cleared. + * + * @version $Id: ServletContextCleaner.java 1432580 2013-01-13 10:41:05Z tn $ + * @since 1.1 + */ +public class ServletContextCleaner implements ServletContextListener { + + private static final Class[] RELEASE_SIGNATURE = {ClassLoader.class}; + + /** + * Invoked when a webapp is undeployed, this tells the LogFactory + * class to release any logging information related to the current + * contextClassloader. + */ + public void contextDestroyed(ServletContextEvent sce) { + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + + Object[] params = new Object[1]; + params[0] = tccl; + + // Walk up the tree of classloaders, finding all the available + // LogFactory classes and releasing any objects associated with + // the tccl (ie the webapp). + // + // When there is only one LogFactory in the classpath, and it + // is within the webapp being undeployed then there is no problem; + // garbage collection works fine. + // + // When there are multiple LogFactory classes in the classpath but + // parent-first classloading is used everywhere, this loop is really + // short. The first instance of LogFactory found will + // be the highest in the classpath, and then no more will be found. + // This is ok, as with this setup this will be the only LogFactory + // holding any data associated with the tccl being released. + // + // When there are multiple LogFactory classes in the classpath and + // child-first classloading is used in any classloader, then multiple + // LogFactory instances may hold info about this TCCL; whenever the + // webapp makes a call into a class loaded via an ancestor classloader + // and that class calls LogFactory the tccl gets registered in + // the LogFactory instance that is visible from the ancestor + // classloader. However the concrete logging library it points + // to is expected to have been loaded via the TCCL, so the + // underlying logging lib is only initialised/configured once. + // These references from ancestor LogFactory classes down to + // TCCL classloaders are held via weak references and so should + // be released but there are circumstances where they may not. + // Walking up the classloader ancestry ladder releasing + // the current tccl at each level tree, though, will definitely + // clear any problem references. + ClassLoader loader = tccl; + while (loader != null) { + // Load via the current loader. Note that if the class is not accessible + // via this loader, but is accessible via some ancestor then that class + // will be returned. + try { + Class logFactoryClass = loader.loadClass("org.apache.commons.logging.LogFactory"); + Method releaseMethod = logFactoryClass.getMethod("release", RELEASE_SIGNATURE); + releaseMethod.invoke(null, params); + loader = logFactoryClass.getClassLoader().getParent(); + } catch(ClassNotFoundException ex) { + // Neither the current classloader nor any of its ancestors could find + // the LogFactory class, so we can stop now. + loader = null; + } catch(NoSuchMethodException ex) { + // This is not expected; every version of JCL has this method + System.err.println("LogFactory instance found which does not support release method!"); + loader = null; + } catch(IllegalAccessException ex) { + // This is not expected; every ancestor class should be accessible + System.err.println("LogFactory instance found which is not accessable!"); + loader = null; + } catch(InvocationTargetException ex) { + // This is not expected + System.err.println("LogFactory instance release method failed!"); + loader = null; + } + } + + // Just to be sure, invoke release on the LogFactory that is visible from + // this ServletContextCleaner class too. This should already have been caught + // by the above loop but just in case... + LogFactory.release(tccl); + } + + /** + * Invoked when a webapp is deployed. Nothing needs to be done here. + */ + public void contextInitialized(ServletContextEvent sce) { + // do nothing + } +} diff --git a/src/main/java/org/apache/commons/logging/impl/SimpleLog.java b/src/main/java/org/apache/commons/logging/impl/SimpleLog.java new file mode 100644 index 0000000..d4756af --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/SimpleLog.java @@ -0,0 +1,649 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.impl; + +import java.io.InputStream; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogConfigurationException; + +/** + * Simple implementation of Log that sends all enabled log messages, + * for all defined loggers, to System.err. The following system properties + * are supported to configure the behavior of this logger: + *

    + *
  • org.apache.commons.logging.simplelog.defaultlog - + * Default logging detail level for all instances of SimpleLog. + * Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). + * If not specified, defaults to "info".
  • + *
  • org.apache.commons.logging.simplelog.log.xxxxx - + * Logging detail level for a SimpleLog instance named "xxxxx". + * Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). + * If not specified, the default logging detail level is used.
  • + *
  • org.apache.commons.logging.simplelog.showlogname - + * Set to true if you want the Log instance name to be + * included in output messages. Defaults to false.
  • + *
  • org.apache.commons.logging.simplelog.showShortLogname - + * Set to true if you want the last component of the name to be + * included in output messages. Defaults to true.
  • + *
  • org.apache.commons.logging.simplelog.showdatetime - + * Set to true if you want the current date and time + * to be included in output messages. Default is false.
  • + *
  • org.apache.commons.logging.simplelog.dateTimeFormat - + * The date and time format to be used in the output messages. + * The pattern describing the date and time format is the same that is + * used in java.text.SimpleDateFormat. If the format is not + * specified or is invalid, the default format is used. + * The default format is yyyy/MM/dd HH:mm:ss:SSS zzz.
  • + *
+ *

+ * In addition to looking for system properties with the names specified + * above, this implementation also checks for a class loader resource named + * "simplelog.properties", and includes any matching definitions + * from this resource (if it exists). + * + * @version $Id: SimpleLog.java 1435115 2013-01-18 12:40:19Z tn $ + */ +public class SimpleLog implements Log, Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 136942970684951178L; + + // ------------------------------------------------------- Class Attributes + + /** All system properties used by SimpleLog start with this */ + static protected final String systemPrefix = "org.apache.commons.logging.simplelog."; + + /** Properties loaded from simplelog.properties */ + static protected final Properties simpleLogProps = new Properties(); + + /** The default format to use when formating dates */ + static protected final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz"; + + /** Include the instance name in the log message? */ + static volatile protected boolean showLogName = false; + + /** Include the short name ( last component ) of the logger in the log + * message. Defaults to true - otherwise we'll be lost in a flood of + * messages without knowing who sends them. + */ + static volatile protected boolean showShortName = true; + + /** Include the current time in the log message */ + static volatile protected boolean showDateTime = false; + + /** The date and time format to use in the log message */ + static volatile protected String dateTimeFormat = DEFAULT_DATE_TIME_FORMAT; + + /** + * Used to format times. + *

+ * Any code that accesses this object should first obtain a lock on it, + * ie use synchronized(dateFormatter); this requirement was introduced + * in 1.1.1 to fix an existing thread safety bug (SimpleDateFormat.format + * is not thread-safe). + */ + static protected DateFormat dateFormatter = null; + + // ---------------------------------------------------- Log Level Constants + + /** "Trace" level logging. */ + public static final int LOG_LEVEL_TRACE = 1; + /** "Debug" level logging. */ + public static final int LOG_LEVEL_DEBUG = 2; + /** "Info" level logging. */ + public static final int LOG_LEVEL_INFO = 3; + /** "Warn" level logging. */ + public static final int LOG_LEVEL_WARN = 4; + /** "Error" level logging. */ + public static final int LOG_LEVEL_ERROR = 5; + /** "Fatal" level logging. */ + public static final int LOG_LEVEL_FATAL = 6; + + /** Enable all logging levels */ + public static final int LOG_LEVEL_ALL = LOG_LEVEL_TRACE - 1; + + /** Enable no logging levels */ + public static final int LOG_LEVEL_OFF = LOG_LEVEL_FATAL + 1; + + // ------------------------------------------------------------ Initializer + + private static String getStringProperty(String name) { + String prop = null; + try { + prop = System.getProperty(name); + } catch (SecurityException e) { + // Ignore + } + return prop == null ? simpleLogProps.getProperty(name) : prop; + } + + private static String getStringProperty(String name, String dephault) { + String prop = getStringProperty(name); + return prop == null ? dephault : prop; + } + + private static boolean getBooleanProperty(String name, boolean dephault) { + String prop = getStringProperty(name); + return prop == null ? dephault : "true".equalsIgnoreCase(prop); + } + + // Initialize class attributes. + // Load properties file, if found. + // Override with system properties. + static { + // Add props from the resource simplelog.properties + InputStream in = getResourceAsStream("simplelog.properties"); + if(null != in) { + try { + simpleLogProps.load(in); + in.close(); + } catch(java.io.IOException e) { + // ignored + } + } + + showLogName = getBooleanProperty(systemPrefix + "showlogname", showLogName); + showShortName = getBooleanProperty(systemPrefix + "showShortLogname", showShortName); + showDateTime = getBooleanProperty(systemPrefix + "showdatetime", showDateTime); + + if(showDateTime) { + dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat", + dateTimeFormat); + try { + dateFormatter = new SimpleDateFormat(dateTimeFormat); + } catch(IllegalArgumentException e) { + // If the format pattern is invalid - use the default format + dateTimeFormat = DEFAULT_DATE_TIME_FORMAT; + dateFormatter = new SimpleDateFormat(dateTimeFormat); + } + } + } + + // ------------------------------------------------------------- Attributes + + /** The name of this simple log instance */ + protected volatile String logName = null; + /** The current log level */ + protected volatile int currentLogLevel; + /** The short name of this simple log instance */ + private volatile String shortLogName = null; + + // ------------------------------------------------------------ Constructor + + /** + * Construct a simple log with given name. + * + * @param name log name + */ + public SimpleLog(String name) { + logName = name; + + // Set initial log level + // Used to be: set default log level to ERROR + // IMHO it should be lower, but at least info ( costin ). + setLevel(SimpleLog.LOG_LEVEL_INFO); + + // Set log level from properties + String lvl = getStringProperty(systemPrefix + "log." + logName); + int i = String.valueOf(name).lastIndexOf("."); + while(null == lvl && i > -1) { + name = name.substring(0,i); + lvl = getStringProperty(systemPrefix + "log." + name); + i = String.valueOf(name).lastIndexOf("."); + } + + if(null == lvl) { + lvl = getStringProperty(systemPrefix + "defaultlog"); + } + + if("all".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_ALL); + } else if("trace".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_TRACE); + } else if("debug".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_DEBUG); + } else if("info".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_INFO); + } else if("warn".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_WARN); + } else if("error".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_ERROR); + } else if("fatal".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_FATAL); + } else if("off".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_OFF); + } + } + + // -------------------------------------------------------- Properties + + /** + * Set logging level. + * + * @param currentLogLevel new logging level + */ + public void setLevel(int currentLogLevel) { + this.currentLogLevel = currentLogLevel; + } + + /** + * Get logging level. + */ + public int getLevel() { + return currentLogLevel; + } + + // -------------------------------------------------------- Logging Methods + + /** + * Do the actual logging. + *

+ * This method assembles the message and then calls write() + * to cause it to be written. + * + * @param type One of the LOG_LEVEL_XXX constants defining the log level + * @param message The message itself (typically a String) + * @param t The exception whose stack trace should be logged + */ + protected void log(int type, Object message, Throwable t) { + // Use a string buffer for better performance + final StringBuffer buf = new StringBuffer(); + + // Append date-time if so configured + if(showDateTime) { + final Date now = new Date(); + String dateText; + synchronized(dateFormatter) { + dateText = dateFormatter.format(now); + } + buf.append(dateText); + buf.append(" "); + } + + // Append a readable representation of the log level + switch(type) { + case SimpleLog.LOG_LEVEL_TRACE: buf.append("[TRACE] "); break; + case SimpleLog.LOG_LEVEL_DEBUG: buf.append("[DEBUG] "); break; + case SimpleLog.LOG_LEVEL_INFO: buf.append("[INFO] "); break; + case SimpleLog.LOG_LEVEL_WARN: buf.append("[WARN] "); break; + case SimpleLog.LOG_LEVEL_ERROR: buf.append("[ERROR] "); break; + case SimpleLog.LOG_LEVEL_FATAL: buf.append("[FATAL] "); break; + } + + // Append the name of the log instance if so configured + if(showShortName) { + if(shortLogName == null) { + // Cut all but the last component of the name for both styles + final String slName = logName.substring(logName.lastIndexOf(".") + 1); + shortLogName = slName.substring(slName.lastIndexOf("/") + 1); + } + buf.append(String.valueOf(shortLogName)).append(" - "); + } else if(showLogName) { + buf.append(String.valueOf(logName)).append(" - "); + } + + // Append the message + buf.append(String.valueOf(message)); + + // Append stack trace if not null + if(t != null) { + buf.append(" <"); + buf.append(t.toString()); + buf.append(">"); + + final java.io.StringWriter sw = new java.io.StringWriter(1024); + final java.io.PrintWriter pw = new java.io.PrintWriter(sw); + t.printStackTrace(pw); + pw.close(); + buf.append(sw.toString()); + } + + // Print to the appropriate destination + write(buf); + } + + /** + * Write the content of the message accumulated in the specified + * StringBuffer to the appropriate output destination. The + * default implementation writes to System.err. + * + * @param buffer A StringBuffer containing the accumulated + * text to be logged + */ + protected void write(StringBuffer buffer) { + System.err.println(buffer.toString()); + } + + /** + * Is the given log level currently enabled? + * + * @param logLevel is this level enabled? + */ + protected boolean isLevelEnabled(int logLevel) { + // log level are numerically ordered so can use simple numeric + // comparison + return logLevel >= currentLogLevel; + } + + // -------------------------------------------------------- Log Implementation + + /** + * Logs a message with + * org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG. + * + * @param message to log + * @see org.apache.commons.logging.Log#debug(Object) + */ + public final void debug(Object message) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) { + log(SimpleLog.LOG_LEVEL_DEBUG, message, null); + } + } + + /** + * Logs a message with + * org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#debug(Object, Throwable) + */ + public final void debug(Object message, Throwable t) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) { + log(SimpleLog.LOG_LEVEL_DEBUG, message, t); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE. + * + * @param message to log + * @see org.apache.commons.logging.Log#trace(Object) + */ + public final void trace(Object message) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) { + log(SimpleLog.LOG_LEVEL_TRACE, message, null); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#trace(Object, Throwable) + */ + public final void trace(Object message, Throwable t) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) { + log(SimpleLog.LOG_LEVEL_TRACE, message, t); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO. + * + * @param message to log + * @see org.apache.commons.logging.Log#info(Object) + */ + public final void info(Object message) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) { + log(SimpleLog.LOG_LEVEL_INFO,message,null); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#info(Object, Throwable) + */ + public final void info(Object message, Throwable t) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) { + log(SimpleLog.LOG_LEVEL_INFO, message, t); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN. + * + * @param message to log + * @see org.apache.commons.logging.Log#warn(Object) + */ + public final void warn(Object message) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) { + log(SimpleLog.LOG_LEVEL_WARN, message, null); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#warn(Object, Throwable) + */ + public final void warn(Object message, Throwable t) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) { + log(SimpleLog.LOG_LEVEL_WARN, message, t); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR. + * + * @param message to log + * @see org.apache.commons.logging.Log#error(Object) + */ + public final void error(Object message) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) { + log(SimpleLog.LOG_LEVEL_ERROR, message, null); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#error(Object, Throwable) + */ + public final void error(Object message, Throwable t) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) { + log(SimpleLog.LOG_LEVEL_ERROR, message, t); + } + } + + /** + * Log a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL. + * + * @param message to log + * @see org.apache.commons.logging.Log#fatal(Object) + */ + public final void fatal(Object message) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) { + log(SimpleLog.LOG_LEVEL_FATAL, message, null); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#fatal(Object, Throwable) + */ + public final void fatal(Object message, Throwable t) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) { + log(SimpleLog.LOG_LEVEL_FATAL, message, t); + } + } + + /** + * Are debug messages currently enabled? + *

+ * This allows expensive operations such as String + * concatenation to be avoided when the message will be ignored by the + * logger. + */ + public final boolean isDebugEnabled() { + return isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG); + } + + /** + * Are error messages currently enabled? + *

+ * This allows expensive operations such as String + * concatenation to be avoided when the message will be ignored by the + * logger. + */ + public final boolean isErrorEnabled() { + return isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR); + } + + /** + * Are fatal messages currently enabled? + *

+ * This allows expensive operations such as String + * concatenation to be avoided when the message will be ignored by the + * logger. + */ + public final boolean isFatalEnabled() { + return isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL); + } + + /** + * Are info messages currently enabled? + *

+ * This allows expensive operations such as String + * concatenation to be avoided when the message will be ignored by the + * logger. + */ + public final boolean isInfoEnabled() { + return isLevelEnabled(SimpleLog.LOG_LEVEL_INFO); + } + + /** + * Are trace messages currently enabled? + *

+ * This allows expensive operations such as String + * concatenation to be avoided when the message will be ignored by the + * logger. + */ + public final boolean isTraceEnabled() { + return isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE); + } + + /** + * Are warn messages currently enabled? + *

+ * This allows expensive operations such as String + * concatenation to be avoided when the message will be ignored by the + * logger. + */ + public final boolean isWarnEnabled() { + return isLevelEnabled(SimpleLog.LOG_LEVEL_WARN); + } + + /** + * Return the thread context class loader if available. + * Otherwise return null. + * + * The thread context class loader is available for JDK 1.2 + * or later, if certain security conditions are met. + * + * @exception LogConfigurationException if a suitable class loader + * cannot be identified. + */ + private static ClassLoader getContextClassLoader() { + ClassLoader classLoader = null; + + try { + // Are we running on a JDK 1.2 or later system? + final Method method = Thread.class.getMethod("getContextClassLoader", (Class[]) null); + + // Get the thread context class loader (if there is one) + try { + classLoader = (ClassLoader)method.invoke(Thread.currentThread(), (Class[]) null); + } catch (IllegalAccessException e) { + // ignore + } catch (InvocationTargetException e) { + /** + * InvocationTargetException is thrown by 'invoke' when + * the method being invoked (getContextClassLoader) throws + * an exception. + * + * getContextClassLoader() throws SecurityException when + * the context class loader isn't an ancestor of the + * calling class's class loader, or if security + * permissions are restricted. + * + * In the first case (not related), we want to ignore and + * keep going. We cannot help but also ignore the second + * with the logic below, but other calls elsewhere (to + * obtain a class loader) will trigger this exception where + * we can make a distinction. + */ + if (e.getTargetException() instanceof SecurityException) { + // ignore + } else { + // Capture 'e.getTargetException()' exception for details + // alternate: log 'e.getTargetException()', and pass back 'e'. + throw new LogConfigurationException + ("Unexpected InvocationTargetException", e.getTargetException()); + } + } + } catch (NoSuchMethodException e) { + // Assume we are running on JDK 1.1 + // ignore + } + + if (classLoader == null) { + classLoader = SimpleLog.class.getClassLoader(); + } + + // Return the selected class loader + return classLoader; + } + + private static InputStream getResourceAsStream(final String name) { + return (InputStream)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + ClassLoader threadCL = getContextClassLoader(); + + if (threadCL != null) { + return threadCL.getResourceAsStream(name); + } else { + return ClassLoader.getSystemResourceAsStream(name); + } + } + }); + } +} + diff --git a/src/main/java/org/apache/commons/logging/impl/WeakHashtable.java b/src/main/java/org/apache/commons/logging/impl/WeakHashtable.java new file mode 100644 index 0000000..c5773f3 --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/WeakHashtable.java @@ -0,0 +1,482 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.impl; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Implementation of Hashtable that uses WeakReference's + * to hold its keys thus allowing them to be reclaimed by the garbage collector. + * The associated values are retained using strong references. + *

+ * This class follows the semantics of Hashtable as closely as + * possible. It therefore does not accept null values or keys. + *

+ * Note: + * This is not intended to be a general purpose hash table replacement. + * This implementation is also tuned towards a particular purpose: for use as a replacement + * for Hashtable in LogFactory. This application requires + * good liveliness for get and put. Various tradeoffs + * have been made with this in mind. + *

+ * Usage: typical use case is as a drop-in replacement + * for the Hashtable used in LogFactory for J2EE environments + * running 1.3+ JVMs. Use of this class in most cases (see below) will + * allow classloaders to be collected by the garbage collector without the need + * to call {@link org.apache.commons.logging.LogFactory#release(ClassLoader) LogFactory.release(ClassLoader)}. + *

+ * org.apache.commons.logging.LogFactory checks whether this class + * can be supported by the current JVM, and if so then uses it to store + * references to the LogFactory implementation it loads + * (rather than using a standard Hashtable instance). + * Having this class used instead of Hashtable solves + * certain issues related to dynamic reloading of applications in J2EE-style + * environments. However this class requires java 1.3 or later (due to its use + * of java.lang.ref.WeakReference and associates). + * And by the way, this extends Hashtable rather than HashMap + * for backwards compatibility reasons. See the documentation + * for method LogFactory.createFactoryStore for more details. + *

+ * The reason all this is necessary is due to a issue which + * arises during hot deploy in a J2EE-like containers. + * Each component running in the container owns one or more classloaders; when + * the component loads a LogFactory instance via the component classloader + * a reference to it gets stored in the static LogFactory.factories member, + * keyed by the component's classloader so different components don't + * stomp on each other. When the component is later unloaded, the container + * sets the component's classloader to null with the intent that all the + * component's classes get garbage-collected. However there's still a + * reference to the component's classloader from a key in the "global" + * LogFactory's factories member! If LogFactory.release() + * is called whenever component is unloaded, the classloaders will be correctly + * garbage collected; this should be done by any container that + * bundles commons-logging by default. However, holding the classloader + * references weakly ensures that the classloader will be garbage collected + * without the container performing this step. + *

+ * Limitations: + * There is still one (unusual) scenario in which a component will not + * be correctly unloaded without an explicit release. Though weak references + * are used for its keys, it is necessary to use strong references for its values. + *

+ * If the abstract class LogFactory is + * loaded by the container classloader but a subclass of + * LogFactory [LogFactory1] is loaded by the component's + * classloader and an instance stored in the static map associated with the + * base LogFactory class, then there is a strong reference from the LogFactory + * class to the LogFactory1 instance (as normal) and a strong reference from + * the LogFactory1 instance to the component classloader via + * getClass().getClassLoader(). This chain of references will prevent + * collection of the child classloader. + *

+ * Such a situation occurs when the commons-logging.jar is + * loaded by a parent classloader (e.g. a server level classloader in a + * servlet container) and a custom LogFactory implementation is + * loaded by a child classloader (e.g. a web app classloader). + *

+ * To avoid this scenario, ensure + * that any custom LogFactory subclass is loaded by the same classloader as + * the base LogFactory. Creating custom LogFactory subclasses is, + * however, rare. The standard LogFactoryImpl class should be sufficient + * for most or all users. + * + * @version $Id: WeakHashtable.java 1435077 2013-01-18 10:51:35Z tn $ + * @since 1.1 + */ +public final class WeakHashtable extends Hashtable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -1546036869799732453L; + + /** + * The maximum number of times put() or remove() can be called before + * the map will be purged of all cleared entries. + */ + private static final int MAX_CHANGES_BEFORE_PURGE = 100; + + /** + * The maximum number of times put() or remove() can be called before + * the map will be purged of one cleared entry. + */ + private static final int PARTIAL_PURGE_COUNT = 10; + + /* ReferenceQueue we check for gc'd keys */ + private final ReferenceQueue queue = new ReferenceQueue(); + /* Counter used to control how often we purge gc'd entries */ + private int changeCount = 0; + + /** + * Constructs a WeakHashtable with the Hashtable default + * capacity and load factor. + */ + public WeakHashtable() {} + + /** + *@see Hashtable + */ + public boolean containsKey(Object key) { + // purge should not be required + Referenced referenced = new Referenced(key); + return super.containsKey(referenced); + } + + /** + *@see Hashtable + */ + public Enumeration elements() { + purge(); + return super.elements(); + } + + /** + *@see Hashtable + */ + public Set entrySet() { + purge(); + Set referencedEntries = super.entrySet(); + Set unreferencedEntries = new HashSet(); + for (Iterator it=referencedEntries.iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Referenced referencedKey = (Referenced) entry.getKey(); + Object key = referencedKey.getValue(); + Object value = entry.getValue(); + if (key != null) { + Entry dereferencedEntry = new Entry(key, value); + unreferencedEntries.add(dereferencedEntry); + } + } + return unreferencedEntries; + } + + /** + *@see Hashtable + */ + public Object get(Object key) { + // for performance reasons, no purge + Referenced referenceKey = new Referenced(key); + return super.get(referenceKey); + } + + /** + *@see Hashtable + */ + public Enumeration keys() { + purge(); + final Enumeration enumer = super.keys(); + return new Enumeration() { + public boolean hasMoreElements() { + return enumer.hasMoreElements(); + } + public Object nextElement() { + Referenced nextReference = (Referenced) enumer.nextElement(); + return nextReference.getValue(); + } + }; + } + + /** + *@see Hashtable + */ + public Set keySet() { + purge(); + Set referencedKeys = super.keySet(); + Set unreferencedKeys = new HashSet(); + for (Iterator it=referencedKeys.iterator(); it.hasNext();) { + Referenced referenceKey = (Referenced) it.next(); + Object keyValue = referenceKey.getValue(); + if (keyValue != null) { + unreferencedKeys.add(keyValue); + } + } + return unreferencedKeys; + } + + /** + *@see Hashtable + */ + public synchronized Object put(Object key, Object value) { + // check for nulls, ensuring semantics match superclass + if (key == null) { + throw new NullPointerException("Null keys are not allowed"); + } + if (value == null) { + throw new NullPointerException("Null values are not allowed"); + } + + // for performance reasons, only purge every + // MAX_CHANGES_BEFORE_PURGE times + if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) { + purge(); + changeCount = 0; + } + // do a partial purge more often + else if (changeCount % PARTIAL_PURGE_COUNT == 0) { + purgeOne(); + } + + Referenced keyRef = new Referenced(key, queue); + return super.put(keyRef, value); + } + + /** + *@see Hashtable + */ + public void putAll(Map t) { + if (t != null) { + Set entrySet = t.entrySet(); + for (Iterator it=entrySet.iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + put(entry.getKey(), entry.getValue()); + } + } + } + + /** + *@see Hashtable + */ + public Collection values() { + purge(); + return super.values(); + } + + /** + *@see Hashtable + */ + public synchronized Object remove(Object key) { + // for performance reasons, only purge every + // MAX_CHANGES_BEFORE_PURGE times + if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) { + purge(); + changeCount = 0; + } + // do a partial purge more often + else if (changeCount % PARTIAL_PURGE_COUNT == 0) { + purgeOne(); + } + return super.remove(new Referenced(key)); + } + + /** + *@see Hashtable + */ + public boolean isEmpty() { + purge(); + return super.isEmpty(); + } + + /** + *@see Hashtable + */ + public int size() { + purge(); + return super.size(); + } + + /** + *@see Hashtable + */ + public String toString() { + purge(); + return super.toString(); + } + + /** + * @see Hashtable + */ + protected void rehash() { + // purge here to save the effort of rehashing dead entries + purge(); + super.rehash(); + } + + /** + * Purges all entries whose wrapped keys + * have been garbage collected. + */ + private void purge() { + final List toRemove = new ArrayList(); + synchronized (queue) { + WeakKey key; + while ((key = (WeakKey) queue.poll()) != null) { + toRemove.add(key.getReferenced()); + } + } + + // LOGGING-119: do the actual removal of the keys outside the sync block + // to prevent deadlock scenarios as purge() may be called from + // non-synchronized methods too + final int size = toRemove.size(); + for (int i = 0; i < size; i++) { + super.remove(toRemove.get(i)); + } + } + + /** + * Purges one entry whose wrapped key + * has been garbage collected. + */ + private void purgeOne() { + synchronized (queue) { + WeakKey key = (WeakKey) queue.poll(); + if (key != null) { + super.remove(key.getReferenced()); + } + } + } + + /** Entry implementation */ + private final static class Entry implements Map.Entry { + + private final Object key; + private final Object value; + + private Entry(Object key, Object value) { + this.key = key; + this.value = value; + } + + public boolean equals(Object o) { + boolean result = false; + if (o != null && o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + result = (getKey()==null ? + entry.getKey() == null : + getKey().equals(entry.getKey())) && + (getValue()==null ? + entry.getValue() == null : + getValue().equals(entry.getValue())); + } + return result; + } + + public int hashCode() { + return (getKey()==null ? 0 : getKey().hashCode()) ^ + (getValue()==null ? 0 : getValue().hashCode()); + } + + public Object setValue(Object value) { + throw new UnsupportedOperationException("Entry.setValue is not supported."); + } + + public Object getValue() { + return value; + } + + public Object getKey() { + return key; + } + } + + /** Wrapper giving correct symantics for equals and hashcode */ + private final static class Referenced { + + private final WeakReference reference; + private final int hashCode; + + /** + * + * @throws NullPointerException if referant is null + */ + private Referenced(Object referant) { + reference = new WeakReference(referant); + // Calc a permanent hashCode so calls to Hashtable.remove() + // work if the WeakReference has been cleared + hashCode = referant.hashCode(); + } + + /** + * + * @throws NullPointerException if key is null + */ + private Referenced(Object key, ReferenceQueue queue) { + reference = new WeakKey(key, queue, this); + // Calc a permanent hashCode so calls to Hashtable.remove() + // work if the WeakReference has been cleared + hashCode = key.hashCode(); + + } + + public int hashCode() { + return hashCode; + } + + private Object getValue() { + return reference.get(); + } + + public boolean equals(Object o) { + boolean result = false; + if (o instanceof Referenced) { + Referenced otherKey = (Referenced) o; + Object thisKeyValue = getValue(); + Object otherKeyValue = otherKey.getValue(); + if (thisKeyValue == null) { + result = otherKeyValue == null; + + // Since our hashcode was calculated from the original + // non-null referant, the above check breaks the + // hashcode/equals contract, as two cleared Referenced + // objects could test equal but have different hashcodes. + // We can reduce (not eliminate) the chance of this + // happening by comparing hashcodes. + result = result && this.hashCode() == otherKey.hashCode(); + // In any case, as our c'tor does not allow null referants + // and Hashtable does not do equality checks between + // existing keys, normal hashtable operations should never + // result in an equals comparison between null referants + } + else + { + result = thisKeyValue.equals(otherKeyValue); + } + } + return result; + } + } + + /** + * WeakReference subclass that holds a hard reference to an + * associated value and also makes accessible + * the Referenced object holding it. + */ + private final static class WeakKey extends WeakReference { + + private final Referenced referenced; + + private WeakKey(Object key, + ReferenceQueue queue, + Referenced referenced) { + super(key, queue); + this.referenced = referenced; + } + + private Referenced getReferenced() { + return referenced; + } + } +} diff --git a/src/main/java/org/apache/commons/logging/impl/package.html b/src/main/java/org/apache/commons/logging/impl/package.html new file mode 100644 index 0000000..5344784 --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/package.html @@ -0,0 +1,22 @@ + + + +

Concrete implementations of commons-logging wrapper APIs.

+ diff --git a/src/main/java/org/apache/commons/logging/package.html b/src/main/java/org/apache/commons/logging/package.html new file mode 100644 index 0000000..e9b32a5 --- /dev/null +++ b/src/main/java/org/apache/commons/logging/package.html @@ -0,0 +1,255 @@ + + + +

Simple wrapper API around multiple logging APIs.

+ + +

Overview

+ +

This package provides an API for logging in server-based applications that +can be used around a variety of different logging implementations, including +prebuilt support for the following:

+
    +
  • Log4J (version 1.2 or later) + from Apache's Logging project. Each named Log + instance is connected to a corresponding Log4J Logger.
  • +
  • + JDK Logging API, included in JDK 1.4 or later systems. Each named + Log instance is connected to a corresponding + java.util.logging.Logger instance.
  • +
  • LogKit from Apache's + Avalon project. Each named Log instance is + connected to a corresponding LogKit Logger.
  • +
  • NoOpLog implementation that simply swallows + all log output, for all named Log instances.
  • +
  • SimpleLog implementation that writes all + log output, for all named Log instances, to + System.err.
  • +
+ + +

Quick Start Guide

+ +

For those impatient to just get on with it, the following example +illustrates the typical declaration and use of a logger that is named (by +convention) after the calling class: + +

+    import org.apache.commons.logging.Log;
+    import org.apache.commons.logging.LogFactory;
+
+    public class Foo {
+
+        private Log log = LogFactory.getLog(Foo.class);
+
+        public void foo() {
+            ...
+            try {
+                if (log.isDebugEnabled()) {
+                    log.debug("About to do something to object " + name);
+                }
+                name.bar();
+            } catch (IllegalStateException e) {
+                log.error("Something bad happened to " + name, e);
+            }
+            ...
+        }
+
+ +

Unless you configure things differently, all log output will be written +to System.err. Therefore, you really will want to review the remainder of +this page in order to understand how to configure logging for your +application.

+ + +

Configuring the Commons Logging Package

+ + +

Choosing a LogFactory Implementation

+ +

From an application perspective, the first requirement is to retrieve an +object reference to the LogFactory instance that will be used +to create Log instances for this +application. This is normally accomplished by calling the static +getFactory() method. This method implements the following +discovery algorithm to select the name of the LogFactory +implementation class this application wants to use:

+
    +
  • Check for a system property named + org.apache.commons.logging.LogFactory.
  • +
  • Use the JDK 1.3 JAR Services Discovery mechanism (see + + http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html for + more information) to look for a resource named + META-INF/services/org.apache.commons.logging.LogFactory + whose first line is assumed to contain the desired class name.
  • +
  • Look for a properties file named commons-logging.properties + visible in the application class path, with a property named + org.apache.commons.logging.LogFactory defining the + desired implementation class name.
  • +
  • Fall back to a default implementation, which is described + further below.
  • +
+ +

If a commons-logging.properties file is found, all of the +properties defined there are also used to set configuration attributes on +the instantiated LogFactory instance.

+ +

Once an implementation class name is selected, the corresponding class is +loaded from the current Thread context class loader (if there is one), or +from the class loader that loaded the LogFactory class itself +otherwise. This allows a copy of commons-logging.jar to be +shared in a multiple class loader environment (such as a servlet container), +but still allow each web application to provide its own LogFactory +implementation, if it so desires. An instance of this class will then be +created, and cached per class loader. + + +

The Default LogFactory Implementation

+ +

The Logging Package APIs include a default LogFactory +implementation class ( +org.apache.commons.logging.impl.LogFactoryImpl) that is selected if no +other implementation class name can be discovered. Its primary purpose is +to create (as necessary) and return Log instances +in response to calls to the getInstance() method. The default +implementation uses the following rules:

+
    +
  • At most one Log instance of the same name will be created. + Subsequent getInstance() calls to the same + LogFactory instance, with the same name or Class + parameter, will return the same Log instance.
  • +
  • When a new Log instance must be created, the default + LogFactory implementation uses the following discovery + process: +
      +
    • Look for a configuration attribute of this factory named + org.apache.commons.logging.Log (for backwards + compatibility to pre-1.0 versions of this API, an attribute + org.apache.commons.logging.log is also consulted).
    • +
    • Look for a system property named + org.apache.commons.logging.Log (for backwards + compatibility to pre-1.0 versions of this API, a system property + org.apache.commons.logging.log is also consulted).
    • +
    • If the Log4J logging system is available in the application + class path, use the corresponding wrapper class + (Log4JLogger).
    • +
    • If the application is executing on a JDK 1.4 system, use + the corresponding wrapper class + (Jdk14Logger).
    • +
    • Fall back to the default simple logging implementation + (SimpleLog).
    • +
  • +
  • Load the class of the specified name from the thread context class + loader (if any), or from the class loader that loaded the + LogFactory class otherwise.
  • +
  • Instantiate an instance of the selected Log + implementation class, passing the specified name as the single + argument to its constructor.
  • +
+ +

See the SimpleLog JavaDocs for detailed +configuration information for this default implementation.

+ + +

Configuring the Underlying Logging System

+ +

The basic principle is that the user is totally responsible for the +configuration of the underlying logging system. +Commons-logging should not change the existing configuration.

+ +

Each individual Log implementation may +support its own configuration properties. These will be documented in the +class descriptions for the corresponding implementation class.

+ +

Finally, some Log implementations (such as the one for Log4J) +require an external configuration file for the entire logging environment. +This file should be prepared in a manner that is specific to the actual logging +technology being used.

+ + +

Using the Logging Package APIs

+ +

Use of the Logging Package APIs, from the perspective of an application +component, consists of the following steps:

+
    +
  1. Acquire a reference to an instance of + org.apache.commons.logging.Log, by calling the + factory method + + LogFactory.getInstance(String name). Your application can contain + references to multiple loggers that are used for different + purposes. A typical scenario for a server application is to have each + major component of the server use its own Log instance.
  2. +
  3. Cause messages to be logged (if the corresponding detail level is enabled) + by calling appropriate methods (trace(), debug(), + info(), warn(), error, and + fatal()).
  4. +
+ +

For convenience, LogFactory also offers a static method +getLog() that combines the typical two-step pattern:

+
+  Log log = LogFactory.getFactory().getInstance(Foo.class);
+
+

into a single method call:

+
+  Log log = LogFactory.getLog(Foo.class);
+
+ +

For example, you might use the following technique to initialize and +use a Log instance in an application component:

+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class MyComponent {
+
+  protected Log log =
+    LogFactory.getLog(MyComponent.class);
+
+  // Called once at startup time
+  public void start() {
+    ...
+    log.info("MyComponent started");
+    ...
+  }
+
+  // Called once at shutdown time
+  public void stop() {
+    ...
+    log.info("MyComponent stopped");
+    ...
+  }
+
+  // Called repeatedly to process a particular argument value
+  // which you want logged if debugging is enabled
+  public void process(String value) {
+    ...
+    // Do the string concatenation only if logging is enabled
+    if (log.isDebugEnabled())
+      log.debug("MyComponent processing " + value);
+    ...
+  }
+
+}
+
+ + diff --git a/src/main/java/overview.html b/src/main/java/overview.html new file mode 100644 index 0000000..4fc581b --- /dev/null +++ b/src/main/java/overview.html @@ -0,0 +1,35 @@ + + + + +Overview Documentation for COMMONS-LOGGING + + +

The Logging Wrapper Library component of the Apache Commons +subproject offers wrappers around an extensible set of concrete logging +implementations, so that application code based on it does not need to be +modified in order to select a different logging implementation.

+ +

See the + +Package Description for the org.apache.commons.logging +package for more information.

+ + diff --git a/src/media/logo.png b/src/media/logo.png new file mode 100644 index 0000000..c4c43f8 Binary files /dev/null and b/src/media/logo.png differ diff --git a/src/media/logo.xcf b/src/media/logo.xcf new file mode 100644 index 0000000..40b5afa Binary files /dev/null and b/src/media/logo.xcf differ diff --git a/src/site/resources/images/logo.png b/src/site/resources/images/logo.png new file mode 100644 index 0000000..c4c43f8 Binary files /dev/null and b/src/site/resources/images/logo.png differ diff --git a/src/site/site.xml b/src/site/site.xml new file mode 100644 index 0000000..73d424a --- /dev/null +++ b/src/site/site.xml @@ -0,0 +1,64 @@ + + + + + + + Commons Logging + /images/logo.png + http://commons.apache.org/logging/ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/site/xdoc/building.xml b/src/site/xdoc/building.xml new file mode 100644 index 0000000..7484ecd --- /dev/null +++ b/src/site/xdoc/building.xml @@ -0,0 +1,74 @@ + + + + + Building + Commons Documentation Team + + + +
+

+ Commons Logging uses Maven 2.0.x as its + primary build system. Ant can also be used. +

+
+ +
+

+ To build the full website, run +

+ mvn site +

+ The result will be in the target/site folder. + You must be online and using JDK 1.4 or higher to successfully complete this target. +

+

+ To build the jar files, run +

+ mvn package +

+ The resulting 4 jar files will be in the target folder. + You must use JDK 1.4 or higher to successfully complete this target. +

+

+ To create a full distribution, run +

+ mvn clean site assembly:assembly +

+ The resulting .zip and .tar.gz files will be in the target folder. + You must use JDK 1.4 or higher to successfully complete this target. +

+

+ Further details can be found in the + commons build instructions. +

+
+ +
+

+ We still use Ant to test the artifacts built my Maven. + Please follow the instructions in the file build-testing.xml. +

+

+ Note: A 1.2 JDK is needed to run the tests. +

+
+ + +
diff --git a/src/site/xdoc/download_logging.xml b/src/site/xdoc/download_logging.xml new file mode 100644 index 0000000..79ccd0d --- /dev/null +++ b/src/site/xdoc/download_logging.xml @@ -0,0 +1,138 @@ + + + + + + Download Apache Commons Logging + Commons Documentation Team + + +
+ +

+ We recommend you use a mirror to download our release + builds, but you must verify the integrity of + the downloaded files using signatures downloaded from our main + distribution directories. Recent releases (48 hours) may not yet + be available from the mirrors. +

+ +

+ You are currently using [preferred]. If you + encounter a problem with this mirror, please select another + mirror. If all mirrors are failing, there are backup + mirrors (at the end of the mirrors list) that should be + available. +

+ [if-any logo][end] +

+ +
+

+ Other mirrors: + + +

+
+ +

+ The KEYS + link links to the code signing keys used to sign the product. + The PGP link downloads the OpenPGP compatible signature from our main site. + The MD5 link downloads the checksum from the main site. +

+
+
+
+ + + + + + + + + + + + +
commons-logging-1.2-bin.tar.gzmd5pgp
commons-logging-1.2-bin.zipmd5pgp
+
+ + + + + + + + + + + + +
commons-logging-1.2-src.tar.gzmd5pgp
commons-logging-1.2-src.zipmd5pgp
+
+
+
+

+ Older releases can be obtained from the archives. +

+ +
+ +
diff --git a/src/site/xdoc/guide.xml b/src/site/xdoc/guide.xml new file mode 100644 index 0000000..4790aba --- /dev/null +++ b/src/site/xdoc/guide.xml @@ -0,0 +1,850 @@ + + + + + + + + User Guide + Commons Documentation Team + + + +
+

+

    +
  1. Introduction
  2. +
  3. Quick Start +
      +
    1. Configuration
    2. +
    3. +Configuring The Underlying Logging System +
    4. +
    5. +Configuring Log4J +
    6. +
    +
  4. +
  5. Developing With JCL +
      +
    1. Obtaining a Log Object
    2. +
    3. Logging a Message
    4. +
    5. Serialization Issues
    6. +
    +
  6. +
  7. Jars Included in the Standard Distribution +
      +
    1. commons-logging.jar
    2. +
    3. commons-logging-api.jar
    4. +
    5. commons-logging-adapters.jar
    6. +
    +
  8. +
  9. JCL Best Practices
  10. +
  11. Best Practices (General) +
      +
    1. Code Guards
    2. +
    3. Message Priorities/Levels
    4. +
    5. Default Message Priority/Level
    6. +
    +
  12. +
  13. Best Practices (Enterprise) +
      +
    1. Logging Exceptions
    2. +
    3. When Info Level Instead of Debug?
    4. +
    5. More Control of Enterprise Exception Logging
    6. +
    7. National Language Support And Internationalization
    8. +
    9. Classloader and Memory Management
    10. +
    +
  14. +
  15. Extending Commons Logging +
      +
    1. Contract
    2. +
    3. Creating a Log Implementation
    4. +
    5. Creating A LogFactory Implementation
    6. +
    +
  16. +
  17. A Quick Guide To Simple Log +
  18. +
  19. Frequently Asked Questions +
  20. +
+

+
+
+

+The Apache Commons Logging (JCL) provides a Log interface that +is intended to be both light-weight and an independent abstraction of other logging toolkits. +It provides the middleware/tooling developer with a simple +logging abstraction, that allows the user (application developer) to plug in +a specific logging implementation. +

+

JCL provides thin-wrapper Log implementations for +other logging tools, including +Log4J, +Avalon LogKit +(the Avalon Framework's logging infrastructure), +JDK 1.4, and an implementation of JDK 1.4 logging APIs (JSR-47) for pre-1.4 +systems. +The interface maps closely to Log4J and LogKit. +

+

+Familiarity with high-level details of the relevant Logging implementations is presumed. +

+
+
+

+As far as possible, JCL tries to be as unobtrusive as possible. +In most cases, including the (full) commons-logging.jar in the classpath +should result in JCL configuring itself in a reasonable manner. +There's a good chance that it'll guess (discover) your preferred logging system and you won't +need to do any configuration of JCL at all! +

+Note, however, that if you have a particular preference then providing a simple +commons-logging.properties file which specifies the concrete logging library to be +used is recommended, since (in this case) JCL will log only to that system +and will report any configuration problems that prevent that system being used. +

+

+When no particular logging library is specified then JCL will silently ignore any logging library +that it finds but cannot initialise and continue to look for other alternatives. This is a deliberate +design decision; no application should fail to run because a "guessed" logging library cannot be +used. To ensure an exception is reported when a particular logging library cannot be used, use one +of the available JCL configuration mechanisms to force that library to be selected (ie disable +JCL's discovery process). +

+ +

+There are two base abstractions used by JCL: Log +(the basic logger) and LogFactory (which knows how to create Log +instances). Specifying a particular Log implementation is very useful (whether that is +one provided by commons-logging or a user-defined one). Specifying a +LogFactory implementation other than the default is a subject for +advanced users only, so will not be addressed here. +

+

+The default LogFactory implementation uses the following discovery process +to determine what type of Log implementation it should use +(the process terminates when the first positive match - in order - is found): +

+ +
    +
  1. +Look for a configuration attribute of this factory named +org.apache.commons.logging.Log (for backwards compatibility to +pre-1.0 versions of this API, an attribute +org.apache.commons.logging.log is also consulted). +

    +Configuration attributes can be set explicitly by java code, but they are more +commonly set by placing a file named commons-logging.properties in the classpath. +When such a file exists, every entry in the properties file becomes an "attribute" +of the LogFactory. When there is more than one such file in the classpath, releases +of commons-logging prior to 1.1 simply use the first one found. From release 1.1, +each file may define a priority key, and the file with +the highest priority is used (no priority definition implies priority of zero). +When multiple files have the same priority, the first one found is used. +

    +

    +Defining this property in a commons-logging.properties file is the recommended +way of explicitly selecting a Log implementation. +

    +
  2. +
  3. +Look for a system property named +org.apache.commons.logging.Log (for backwards +compatibility to pre-1.0 versions of this API, a system property +org.apache.commons.logging.log is also consulted). +
  4. +
  5. +If the Log4J logging system is available in the application +class path, use the corresponding wrapper class +(Log4JLogger). +
  6. +
  7. +If the application is executing on a JDK 1.4 system, use +the corresponding wrapper class +(Jdk14Logger). +
  8. +
  9. +Fall back to the default simple logging wrapper +(SimpleLog). +
  10. +
+

+Consult the JCL javadocs for details of the various Log +implementations that ship with the component. (The discovery process is also covered in more +detail there.) +

+
+ +

+The JCL SPI +can be configured to use different logging toolkits (see above). +JCL provides only a bridge for writing log messages. It does not (and will not) support any +sort of configuration API for the underlying logging system. +

+

+Configuration of the behavior of the JCL ultimately depends upon the +logging toolkit being used. Please consult the documentation for the chosen logging system. +

+

+JCL is NOT responsible for initialisation, configuration or shutdown of the underlying logging library. +In many cases logging libraries will automatically initialise/configure themselves when first used, and +need no explicit shutdown process. In these situations an application can simply use JCL and not depend +directly on the API of the underlying logging system in any way. However if the logging library being used +requires special initialisation, configuration or shutdown then some logging-library-specific code will +be required in the application. JCL simply forwards logging method calls to the correct underlying +implementation. When writing library code this issue is of course not relevant as the calling application +is responsible for handling such issues. +

+ +

+Log4J is a very commonly used logging implementation (as well as being the JCL primary default), +so a few details are presented herein to get the developer/integrator going. +Please see the Log4J Home for more details +on Log4J and it's configuration. +

+

+Configure Log4J using system properties and/or a properties file: +

+
    +
  • +log4j.configuration=log4j.properties +Use this system property to specify the name of a Log4J configuration file. +If not specified, the default configuration file is log4j.properties. +
  • +
  • +log4j.rootCategory=priority [, appender]* +
  • +Set the default (root) logger priority. +
  • +log4j.logger.logger.name=priority +Set the priority for the named logger +and all loggers hierarchically lower than, or below, the +named logger. +logger.name corresponds to the parameter of +LogFactory.getLog(logger.name), +used to create the logger instance. Priorities are: +DEBUG, +INFO, +WARN, +ERROR, +or FATAL. +
    +Log4J understands hierarchical names, +enabling control by package or high-level qualifiers: +log4j.logger.org.apache.component=DEBUG +will enable debug messages for all classes in both +org.apache.component +and +org.apache.component.sub. +Likewise, setting +log4j.logger.org.apache.component=DEBUG +will enable debug message for all 'component' classes, +but not for other Apache projects. +
  • +
  • +log4j.appender.appender.Threshold=priority +
  • +Log4J appenders correspond to different output devices: +console, files, sockets, and others. +If appender's threshold +is less than or equal to the message priority then +the message is written by that appender. +This allows different levels of detail to be appear +at different log destinations. +For example: one can capture DEBUG (and higher) level information in a logfile, +while limiting console output to INFO (and higher). +
+
+
+
+
+ +

+To use the JCL SPI from a Java class, +include the following import statements: +

+
    + +import org.apache.commons.logging.Log; +
    +import org.apache.commons.logging.LogFactory; +
    +
    +
+

+Note that some components using JCL may +either extend Log, +or provide a component-specific LogFactory implementation. +Review the component documentation for guidelines +on how commons-logging should be used in such components. +

+

+For each class definition, declare and initialize a +log attribute as follows: +

+
    + +public class CLASS +{ + private Log log = LogFactory.getLog(CLASS.class); + ... + ; + +
+

+Note that for application code, declaring the log member as "static" is more +efficient as one Log object is created per class, and is recommended. +However this is not safe to do for a class which may be deployed via a "shared" +classloader in a servlet or j2ee container or similar environment. If the class +may end up invoked with different thread-context-classloader values set then the +member must not be declared static. The use of "static" should therefore +be avoided in code within any "library" type project. +

+
+ +

+Messages are logged to a logger, such as log +by invoking a method corresponding to priority. +The org.apache.commons.logging.Log interface defines the +following methods for use +in writing log/trace messages to the log: +

+
    + + log.fatal(Object message); + log.fatal(Object message, Throwable t); + log.error(Object message); + log.error(Object message, Throwable t); + log.warn(Object message); + log.warn(Object message, Throwable t); + log.info(Object message); + log.info(Object message, Throwable t); + log.debug(Object message); + log.debug(Object message, Throwable t); + log.trace(Object message); + log.trace(Object message, Throwable t); + +
+

+Semantics for these methods are such that it is expected +that the severity, from highest to lowest, of messages is ordered as above. +

+

+In addition to the logging methods, the following are provided for code guards: +

+
    + + log.isFatalEnabled(); + log.isErrorEnabled(); + log.isWarnEnabled(); + log.isInfoEnabled(); + log.isDebugEnabled(); + log.isTraceEnabled(); + +
+
+ +

Prior to release 1.0.4, none of the standard Log implementations were + Serializable. If you are using such a release and have a Serializable class + with a member that is of type Log then it is necessary to declare + that member to be transient and to ensure that the value is restored on + deserialization. The recommended approach is to define a custom + readObject method on the class which reinitializes that member.

+

In release 1.0.4, all standard Log implementations are Serializable. This + means that class members of type Log do not need to be declared transient; + on deserialization the Log object will "rebind" to the same category for the + same logging library. Note that the same underlying logging library will be + used on deserialization as was used in the original object, even if the + application the object was deserialized into is using a different logging + library. There is one exception; LogKitLogger (adapter for the Avalon LogKit + library) is not Serializable for technical reasons.

+

Custom Log implementations not distributed with commons-logging may + or may not be Serializable. If you wish your code to be compatible with + any arbitrary log adapter then you should follow the advice given above + for pre-1.0.4 releases.

+
+
+
+ +

+The commons-logging.jar file includes the JCL API, the default +LogFactory implementation and thin-wrapper Log +implementations for +Log4J, +Avalon LogKit, +the Avalon Framework's logging infrastructure, +JDK 1.4, as well as an implementation of JDK 1.4 logging APIs (JSR-47) for +pre-1.4 systems. +

+

+In most cases, including commons-logging.jar and your preferred +logging implementation in the classpath should be all that is required to +use JCL. +

+
+ +

+The commons-logging-api.jar file includes the JCL API and the +default LogFactory implementation as well as the built-in +Log implementations SimpleLog and NoOpLog. However it does not +include the wrapper Log implementations that require additional +libraries such as Log4j, Avalon and +Lumberjack. +

+

+This jar is intended for use by projects that recompile the commons-logging +source using alternate java environments, and cannot compile against all of +the optional libraries that the Apache release of commons-logging supports. +Because of the reduced dependencies of this jarfile, such projects should be +able to create an equivalent of this library with fewer difficulties. +

+

+This jar is also useful for build environments that automatically track +dependencies, and thus have difficulty with the concept that the main +commons-logging.jar has "optional" dependencies on various logging +implementations that can safely go unsatisfied at runtime. +

+
+ +

+The commons-logging-adapters.jar file includes only adapters +to third-party logging implementations, and none of the core commons-logging +framework. As such, it cannot be used alone; either commons-logging.jar or +commons-logging-api.jar must also be present in the classpath. +

+

+This library will not often be used; it is only intended for situations where +a container has deployed commons-logging-api.jar in a shared classpath but a +webapp wants to bind logging to one of the external logging implementations +that the api jar does not include. In this situation, deploying the +commons-logging.jar file within the webapp can cause problems as this leads to +duplicates of the core commons-logging classes (Log, LogFactory, etc) in +the classpath which in turn can cause unpleasant ClassCastException exceptions +to occur. Deploying only the adapters avoids this problem. +

+
+
+
+

+Best practices for JCL are presented in two categories: +General and Enterprise. +The general principles are fairly clear.Enterprise practices are a bit more involved +and it is not always as clear as to why they are important. +

+

+Enterprise best-practice principles apply to middleware components +and tooling that is expected to execute in an "Enterprise" level +environment. +These issues relate to Logging as Internationalization, +and fault detection. +Enterprise requires more effort and planning, but are strongly encouraged (if not required) +in production level systems. Different corporate enterprises/environments have different +requirements, so being flexible always helps. +

+
+
+ +

+Code guards are typically used to guard code that +only needs to execute in support of logging, +that otherwise introduces undesirable runtime overhead +in the general case (logging disabled). +Examples are multiple parameters, or expressions (e.g. string + " more") for parameters. +Use the guard methods of the form log.is<Priority>() to verify +that logging should be performed, before incurring the overhead of the logging method call. +Yes, the logging methods will perform the same check, but only after resolving parameters. +

+
+ +

+It is important to ensure that log message are +appropriate in content and severity. +The following guidelines are suggested: +

+
    +
  • +fatal - Severe errors that cause premature termination. +Expect these to be immediately visible on a status console. +See also +Internationalization. +
  • +
  • +error - Other runtime errors or unexpected conditions. +Expect these to be immediately visible on a status console. +See also +Internationalization. +
  • +
  • +warn - Use of deprecated APIs, poor use of API, 'almost' errors, +other runtime situations that are undesirable or unexpected, but not +necessarily "wrong". +Expect these to be immediately visible on a status console. +See also +Internationalization. +
  • +
  • +info - Interesting runtime events (startup/shutdown). +Expect these to be immediately visible on a console, +so be conservative and keep to a minimum. +See also +Internationalization. +
  • +
  • +debug - detailed information on the flow through the system. +Expect these to be written to logs only. +
  • +
  • +trace - more detailed information. +Expect these to be written to logs only. +
  • +
+
+ +

+By default the message priority should be no lower than info. +That is, by default debug message should not be seen in the logs. +

+
+
+
+ +

+The general rule in dealing with exceptions is to assume that +the user (developer using a tooling/middleware API) isn't going +to follow the rules. +Since any problems that result are going to be assigned to you, +it's in your best interest to be prepared with the proactive +tools necessary to demonstrate that your component works correctly, +or at worst that the problem can be analyzed from your logs. +For this discussion, we must make a distinction between different types of exceptions +based on what kind of boundaries they cross: +

+
    +
  • +External Boundaries - Expected Exceptions. +This classification includes exceptions such as FileNotFoundException +that cross API/SPI boundaries, and are exposed to the user of a component/toolkit. +These are listed in the 'throws' clause of a method signature. +
    +Appropriate handling of these exceptions depends upon the type +of code you are developing. +API's for utility functions and tools should log these at the debug level, +if they are caught at all by internal code. +
    +For higher level frameworks and middleware components, +these exceptions should be caught immediately prior to crossing +the API/SPI interface back to user code-space, +logged with full stack trace at info level, +and rethrown. +The assures that the log contains a record of the root cause for +future analysis in the event that the exception is not caught and resolved +as expected by the user's code. +
    +
  • +
  • +External Boundaries - Unexpected Exceptions. +This classification includes exceptions such as NullPointerException +that cross API/SPI boundaries, and are exposed to the user of a component/toolkit. +These are runtime exceptions/error that are NOT +listed in the 'throws' clause of a method signature. +
    +Appropriate handling of these exceptions depends upon the type +of code you are developing. +APIs for utility functions and tools should log these at the debug level, +if they are caught at all. +
    +For higher level frameworks and middleware components, +these exceptions should be caught immediately prior to crossing +the API/SPI interface back to user code-space, +logged with full stack trace at info level, +and rethrown/wrapped as ComponentInternalError. +This ensures that the log contains a record of the root cause for +future analysis in the event that the exception is not caught and +logged/reported as expected by the user's code. +
  • +
  • +Internal Boundaries. +Exceptions that occur internally and are resolved internally. +These should be logged when caught as debug or info messages, +at the programmer's discretion. +
  • +
  • +Significant Internal Boundaries. +This typically only applies to middleware components that span networks or runtime processes. +Exceptions that cross over significant internal component boundaries such as networks +should be logged when caught as info messages. +Do not assume that such a (process/network) boundary will deliver exceptions to the 'other side'. +
  • +
+
+ +

+You want to have exception/problem information available for +first-pass problem determination in a production level +enterprise application without turning on debug +as a default log level. There is simply too much information +in debug to be appropriate for day-to-day operations. +

+
+ +

+If more control is desired for the level of detail of these +'enterprise' exceptions, then consider creating a special +logger just for these exceptions: +

+
    + + Log log = LogFactory.getLog("org.apache.component.enterprise"); + +
+

+This allows the 'enterprise' level information to be turned on/off explicitly +by most logger implementations. +

+
+ +

+NLS internationalization involves looking up messages from +a message file by a message key, and using that message for logging. +There are various tools in Java, and provided by other components, +for working with NLS messages. +

+

+NLS enabled components are particularly appreciated +(that's an open-source-correct term for 'required by corporate end-users' :-) +for tooling and middleware components. +

+

+NLS internationalization SHOULD be strongly considered for used for +fatal, error, warn, and info messages. +It is generally considered optional for debug and trace messages. +

+

+Perhaps more direct support for internationalizing log messages +can be introduced in a future or alternate version of the Log interface. +

+
+ +

+The LogFactory discovery process (see +Configuration above) is a fairly expensive +operation, so JCL certainly should not perform it each time user code +invokes: +

+LogFactory.getLog() +

+Instead JCL caches the +LogFactory implementation created as a result of the discovery +process and uses the cached factory to return Log objects. +Since in J2EE and similar multi-classloader environments, the result of the +discovery process can vary depending on the thread context classloader +(e.g. one webapp in a web container may be configured to use Log4j and +another to use JDK 1.4 logging), JCL internally caches the +LogFactory instances in a static hashtable, keyed by classloader. +

+

+While this approach is efficient, it can lead to memory leaks if container +implementors are not careful to call +

+LogFactory.release() +

+whenever a classloader that has utilized JCL is undeployed. If +release() is not called, a reference to the undeployed +classloader (and thus to all the classes loaded by it) will be +held in LogFactory's static hashtable. +

+

+Beginning with JCL 1.1, LogFactory caches factory implementations in a +"WeakHashtable". This class is similar to java.util.WeakHashMap in +that it holds a WeakReference to each key (but a strong reference +to each value), thus allowing classloaders to be GC'd even if +LogFactory.release() is never invoked. +

+

+Because WeakHashtable depends on JDK 1.3+ features, it is dynamically +loaded depending on the JVM version; when commons-logging is run on java versions +prior to 1.3 the code defaults to a standard Hashtable instead. +

+

+If a custom LogFactory implementation is used, however, then a +WeakHashtable alone can be insufficient to allow garbage collection +of a classloader without a call to release. If the abstract class +LogFactory is loaded by a parent classloader and a concrete +subclass implementation of LogFactory is loaded by a child +classloader, the WeakHashtable's key is a weak reference to the TCCL (child +classloader), but the value is a strong reference to the LogFactory instance, +which in turn contains a strong reference to its class and thus loading +classloader - the child classloader. This chain of strong references prevents +the child loader from being garbage collected. +

+

+If use of a custom LogFactory subclass is desired, ensuring that +the custom subclass is loaded by the same classloader as LogFactory +will prevent problems. In normal deployments, the standard implementations +of LogFactory found in package org.apache.commons.logging.impl +will be loaded by the same classloader that loads LogFactory +itself, so use of the standard LogFactory implementation +should not pose problems. Alternatively, use the provided ServletContextCleaner +to ensure this reference is explicitly released on webapp unload. +

+
+
+
+

+JCL is designed to encourage extensions to be created that add functionality. +Typically, extensions to JCL fall into two categories: +

+
    +
  • new Log implementations that provide new bridges to logging systems
  • +
  • +new LogFactory implementations that provide alternative discovery strategies +
  • +
+ +

+When creating new implementations for Log and LogFactory, +it is important to understand the implied contract between the factory +and the log implementations: +

    +
  • Life cycle +
    +The JCL LogFactory implementation must assume responsibility for +either connecting/disconnecting to a logging toolkit, +or instantiating/initializing/destroying a logging toolkit. +
    +
  • +
  • Exception handling +
    +The JCL Log interface doesn't specify any exceptions to be handled, +the implementation must catch any exceptions. +
    +
  • +
  • Multiple threads +
    +The JCL Log and LogFactory implementations must ensure +that any synchronization required by the logging toolkit +is met. +
    +
  • +
+

+
+ +

+The minimum requirement to integrate with another logger +is to provide an implementation of the +org.apache.commons.logging.Log interface. +In addition, an implementation of the +org.apache.commons.logging.LogFactory interface +can be provided to meet +specific requirements for connecting to, or instantiating, a logger. +

+

+The default LogFactory provided by JCL +can be configured to instantiate a specific implementation of the +org.apache.commons.logging.Log interface +by setting the property of the same name (org.apache.commons.logging.Log). +This property can be specified as a system property, +or in the commons-logging.properties file, +which must exist in the CLASSPATH. +

+
+ +

+If desired, the default implementation of the +org.apache.commons.logging.LogFactory +interface can be overridden, +allowing the JDK 1.3 Service Provider discovery process +to locate and create a LogFactory specific to the needs of the application. +Review the Javadoc for the LogFactoryImpl.java +for details. +

+
+
+
+

+JCL is distributed with a very simple Log implementation named +org.apache.commons.logging.impl.SimpleLog. This is intended to be a minimal +implementation and those requiring a fully functional open source logging system are +directed to Log4J. +

+

+ SimpleLog sends all (enabled) log messages, + for all defined loggers, to System.err. The following system properties + are supported to configure the behavior of this logger:

+
    +
  • org.apache.commons.logging.simplelog.defaultlog - + Default logging detail level for all instances of SimpleLog. + Must be one of: +
      +
    • trace
    • +
    • debug
    • +
    • info
    • +
    • warn
    • +
    • error
    • +
    • fatal
    • +
    + If not specified, defaults to info.
  • +
  • org.apache.commons.logging.simplelog.log.xxxxx - + Logging detail level for a SimpleLog instance named "xxxxx". + Must be one of: +
      +
    • trace
    • +
    • debug
    • +
    • info
    • +
    • warn
    • +
    • error
    • +
    • fatal
    • +
    + If not specified, the default logging detail level is used.
  • +
  • org.apache.commons.logging.simplelog.showlogname - + Set to true if you want the Log instance name to be + included in output messages. Defaults to false.
  • +
  • org.apache.commons.logging.simplelog.showShortLogname - + Set to true if you want the last component of the name to be + included in output messages. Defaults to true.
  • +
  • org.apache.commons.logging.simplelog.showdatetime - + Set to true if you want the current date and time + to be included in output messages. Default is false.
  • +
  • org.apache.commons.logging.simplelog.dateTimeFormat - + The date and time format to be used in the output messages. + The pattern describing the date and time format is the same that is + used in java.text.SimpleDateFormat. If the format is not + specified or is invalid, the default format is used. + The default format is yyyy/MM/dd HH:mm:ss:SSS zzz.
  • +
+ +

+In addition to looking for system properties with the names specified +above, this implementation also checks for a class loader resource named +"simplelog.properties", and includes any matching definitions +from this resource (if it exists). +

+
+
+

+See the FAQ document +on the commons-logging wiki site +

+
+ + +
diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml new file mode 100644 index 0000000..4219096 --- /dev/null +++ b/src/site/xdoc/index.xml @@ -0,0 +1,144 @@ + + + + + + + + Overview + Commons Documentation Team + + + + +
+ +

When writing a library it is very useful to log information. However there +are many logging implementations out there, and a library cannot impose the use +of a particular one on the overall application that the library is a part of.

+ +

The Logging package is an ultra-thin bridge between different logging +implementations. A library that uses the commons-logging API can be used with +any logging implementation at runtime. Commons-logging comes with support for a +number of popular logging implementations, and writing adapters for others is a +reasonably simple task.

+ +

Applications (rather than libraries) may also choose to use commons-logging. +While logging-implementation independence is not as important for applications +as it is for libraries, using commons-logging does allow the application to +change to a different logging implementation without recompiling code. +

+Note that commons-logging does not attempt to initialise or terminate the underlying +logging implementation that is used at runtime; that is the responsibility of +the application. However many popular logging implementations do automatically +initialise themselves; in this case an application may be able to avoid +containing any code that is specific to the logging implementation used.

+ +
+ + +
+ +

The +Release Notes document the new features and bug fixes that have been +included in the latest release.

+ +

The +JavaDoc API documents for the latest release are available online. +In particular, you should read the package overview of the org.apache.commons.logging +package. In addition, there is a (short) +User Guide.

+ +

The Wiki site has +the latest updates, an FAQ and much other useful information.

+

+Users needing to become experts or wanting to help develop JCL should +(in addition) consult the Tech Guide. +This gives short introductions to topics such as advanced class loading. +

+
+ + +
+

+Binary and source distributions are available + here. +

+ +

The main purpose of the 1.2 release is to drop support for Java 1.1.

+

For a full list of changes since the 1.1.3 release, please refer to the + change-report.

+
+ +

The 1.1.3 release only updates the Bundle-SymbolicName in the manifest + to "org.apache.commons.logging".

+

For a full list of changes since the 1.1.1 release, please refer to the + change-report.

+
+ +

The 1.1.2 release is a packaging of bug fixes since release 1.1.1.

+

For the full details, see the release notes for this version.

+
+ +

+ This release is a minor update to the 1.1 release that fixes a number of bugs, and + resolves packaging issues for maven 1.x and maven 2.x users. +

+

For the full details, see the release notes for this version.

+
+ +

This release makes several changes that are intended to resolve issues that + have been encountered when using commons-logging in servlet containers or j2ee + containers where complex classpaths are present and multiple copies of + commons-logging libraries are present at different levels.

+

This release also adds support for the TRACE level added to log4j in the + 1.2.12 release. In former commons-logging versions, the log.trace method + caused log4j to output the message at the DEBUG level (the lowest level + supported by log4j at that time).

+

For the full details, see the release notes for this version.

+
+ +

+ Note: the 1.0.5 release was abandoned at alpha status. +

+

+ The next JCL release will be designated 1.1 since we feel this more + accurately reflects the improvements made to the codebase.

+
+ +

The 1.0.4 release of commons-logging is a service release containing support + for both the 1.2.x and 1.3.x series of Log4J releases.

+
+ +

The 1.0.3 release is primarily a maintenance and code cleanup release with minimal new features.

+
+ +

The 1.0.2 release is a packaging of bug fixes since release 1.0.1.

+
+ +

The 1.0.1 release is a packaging of bug fixes and minor enhancements since release 1.0.

+
+
+
+

Regular builds of the current SVN HEAD code are made available. See the + wiki for details.

+
+ +
diff --git a/src/site/xdoc/issue-tracking.xml b/src/site/xdoc/issue-tracking.xml new file mode 100644 index 0000000..829a90c --- /dev/null +++ b/src/site/xdoc/issue-tracking.xml @@ -0,0 +1,102 @@ + + + + + + Commons Logging Issue tracking + Commons Documentation Team + + + +
+

+ Commons Logging uses ASF JIRA for tracking issues. + See the Commons Logging JIRA project page. +

+ +

+ To use JIRA you may need to create an account + (if you have previously created/updated Commons issues using Bugzilla an account will have been automatically + created and you can use the Forgot Password + page to get a new password). +

+ +

+ If you would like to report a bug, or raise an enhancement request with + Commons Logging please do the following: +

    +
  1. Search existing open bugs. + If you find your issue listed then please add a comment with your details.
  2. +
  3. Search the mailing list archive(s). + You may find your issue or idea has already been discussed.
  4. +
  5. Decide if your issue is a bug or an enhancement.
  6. +
  7. Submit either a bug report + or enhancement request.
  8. +
+

+ +

+ Please also remember these points: +

    +
  • the more information you provide, the better we can help you
  • +
  • test cases are vital, particularly for any proposed enhancements
  • +
  • the developers of Commons Logging are all unpaid volunteers
  • +
+

+ +

+ For more information on subversion and creating patches see the + Apache Contributors Guide. +

+ +

+ You may also find these links useful: +

+

+
+ +
diff --git a/src/site/xdoc/junit-report.xml b/src/site/xdoc/junit-report.xml new file mode 100644 index 0000000..786c93c --- /dev/null +++ b/src/site/xdoc/junit-report.xml @@ -0,0 +1,39 @@ + + + + + + JUnit Test Results + Commons Documentation Team + + + + +
+

+ The Apache Commons Logging test cases make extensive use of + sophisticated classloader configurations in order to simulate the + behaviour of various containers. It is difficult to run these tests + under Maven in the default "test" phase. As a consequence the tests + are executed via the failsafe-plugin as part of the "integration-test" + phase. The reports are available here. +

+
+ + +
diff --git a/src/site/xdoc/mail-lists.xml b/src/site/xdoc/mail-lists.xml new file mode 100644 index 0000000..c5bf9b9 --- /dev/null +++ b/src/site/xdoc/mail-lists.xml @@ -0,0 +1,202 @@ + + + + + + Commons Logging Mailing Lists + Commons Documentation Team + + + +
+

+ Commons Logging shares mailing lists with all the other + Commons Components. + To make it easier for people to only read messages related to components they are interested in, + the convention in Commons is to prefix the subject line of messages with the component's name, + for example: +

    +
  • [logging] Problem with the ...
  • +
+

+

+ Questions related to the usage of Commons Logging should be posted to the + User List. +
+ The Developer List + is for questions and discussion related to the development of Commons Logging. +
+ Please do not cross-post; developers are also subscribed to the user list. +

+

+ Note: please don't send patches or attachments to any of the mailing lists. + Patches are best handled via the Issue Tracking system. + Otherwise, please upload the file to a public server and include the URL in the mail. +

+
+ +
+

+ Please prefix the subject line of any messages for Commons Logging + with [logging] - thanks! +
+
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameSubscribeUnsubscribePostArchiveOther Archives
+ Commons User List +

+ Questions on using Commons Logging. +

+
SubscribeUnsubscribePostmail-archives.apache.orgmarkmail.org
+ www.mail-archive.com
+ news.gmane.org +
+ Commons Developer List +

+ Discussion of development of Commons Logging. +

+
SubscribeUnsubscribePostmail-archives.apache.orgmarkmail.org
+ www.mail-archive.com
+ news.gmane.org +
+ Commons Issues List +

+ Only for e-mails automatically generated by the issue tracking system. +

+
SubscribeUnsubscriberead onlymail-archives.apache.orgmarkmail.org
+ www.mail-archive.com +
+ Commons Commits List +

+ Only for e-mails automatically generated by the source control sytem. +

+
SubscribeUnsubscriberead onlymail-archives.apache.orgmarkmail.org
+ www.mail-archive.com +
+ +
+
+

+ Other mailing lists which you may find useful include: +

+ + + + + + + + + + + + + + + + + + +
NameSubscribeUnsubscribePostArchiveOther Archives
+ Apache Announce List +

+ General announcements of Apache project releases. +

+
SubscribeUnsubscriberead onlymail-archives.apache.orgmarkmail.org
+ old.nabble.com
+ www.mail-archive.com
+ news.gmane.org +
+ +
+ +
diff --git a/src/site/xdoc/proposal.xml b/src/site/xdoc/proposal.xml new file mode 100644 index 0000000..bd97a59 --- /dev/null +++ b/src/site/xdoc/proposal.xml @@ -0,0 +1,127 @@ + + + + +Proposal for Logging Library Package + + + + +
+ + + + + +

There is a great need for debugging and logging information inside of +Commons components such as HTTPClient and dbcp. However, there are many +logging APIs out there and it is difficult to choose among them. +

+ +

The Logging package will be an ultra-thin bridge between different logging +libraries. Commons components may use the Logging JAR to remove +compile-time/runtime dependencies on any particular logging package, +and contributors may write Log implementations for the library of their choice. +

+ +
+ + +

The package shall create and maintain a package that provides extremely +basic logging functionality and bridges to other, more sophisticated logging +implementations. +

+ +

+The package should : +

    +
  • Have an API which should be as simple to use as possible
  • +
  • Provide support for log4j
  • +
  • Provide pluggable support for other logging APIs
  • +
+

+ +

+Non-goals: +

    +
  • This package will not perform logging itself, except at the most basic + level.
  • +
  • We do not seek to become a "standard" API.
  • +
+

+ +
+ + +

Logging relies on: +

+ +
    +
  • Java Development Kit (Version 1.1 or later)
  • +
  • Avalon Framework (compile-time dependency only unless this Log + implementation is selected at runtime)
  • +
  • Avalon LogKit (compile-time dependency only unless this Log + implementation is selected at runtime)
  • +
  • JDK 1.4 (compile-time dependency only unless this log implementation + is selected at runtime).
  • +
  • Log4J (compile-time dependency only unless this Log + implementation is selected at runtime)
  • +
  • Lumberjack + (compile-time dependency only unless this Log + implementation is selected at runtime)
  • +
+ +
+ + +
    +
  • CVS Repository - New directory logging in the +jakarta-commons CVS repository.
  • + +
  • Initial Committers - The list is provided below.
  • + +
  • Mailing List - Discussions will take place on the general +dev@commons.apache.org mailing list. To help list +subscribers identify messages of interest, it is suggested that the +message subject of messages about this component be prefixed with +[Logging].
  • + +
  • Bugzilla - New component "Logging" under the "Commons" product +category, with appropriate version identifiers as needed.
  • + +
  • Jyve FAQ - New category "commons-logging" (when available).
  • +
+ + +
+ + +

The initial committers on the Logging component shall be:

+ +
    +
  • Morgan Delagrange
  • +
  • Rodney Waldhoff
  • +
  • Craig McClanahan
  • +
+ +
+
+ +
diff --git a/src/site/xdoc/tech.xml b/src/site/xdoc/tech.xml new file mode 100644 index 0000000..119bdf9 --- /dev/null +++ b/src/site/xdoc/tech.xml @@ -0,0 +1,653 @@ + + + + + + + + Technology Guide + Commons Documentation Team + + + +
+ + + + +

+ This guide is aimed at describing the technologies that JCL developers and expert users + (and users who need to become experts) + should be familiar with. The aim is to give an understanding whilst being precise but brief. + Details which are not relevant for JCL have been suppressed. + References have been included. +

+

+ These topics are a little difficult and it's easy for even experienced developers to make + mistakes. We need you to help us get it right! Please submit corrections, comments, additional references + and requests for clarification + by either: +

+ +

+ TIA +

+
+ +
+
+ +

+ This is intended to present a guide to the process by which Java bytecode uses bytecode in other classes + from the perspective of the language and virtual machine specifications. The focus will be on deciding + which bytecode will be used (rather than the mechanics of the usage). It focuses on facts and terminology. +

+

+ The process is recursive: it is therefore difficult to pick a starting point. + Sun's documentation starts from the perspective of the startup of a new application. + This guide starts from the perspective of an executing application. +

+

+ During this discussion, please assume that each time that class is mentioned, + the comments applied equally well to interfaces. +

+

+ This document is targeted at Java 1.2 and above. +

+
+ +

+ (LangSpec 12.3.3) + The bytecode representation of a class contains symbolic names for other classes referenced. +

+

+ + In practical development terms: If a class is imported (either explicitly in the list of imports at the top of + the source file or implicitly through a fully qualified name in the source code) it is referenced symbolically. + +

+

+ (VMSpec 5.4.3) + Resolution of a symbolic reference occurs dynamically at runtime and is carried out by + the Java Virtual Machine. Resolution of a symbolic reference requires loading and linking of the new class. +

+

+ + Note: references are not statically resolved at compile time. + +

+
+ +

+ (VMSpec 2.17.2) + Loading is the name given to the process by which a binary form of a class is obtained + by the Java Virtual Machine. + Java classes are always loaded and linked dynamically by the Java Virtual Machine + (rather than statically by the compiler). +

+

+ + In practical development terms: + This means that the developer has no certain knowledge about the actual + bytecode that will be used to execute any external call (one made outside the class). This is determined only + at execution time and is affected by the way that the code is deployed. + +

+
+ +

+ (VMSpec 2.17.3) + Linking is the name used for combining the + binary form of a class into the Java Virtual Machine. This must happen before the class can be used. +

+

+ (VMSpec 2.17.3) + Linking is composed of verification, preparation and resolution (of symbolic references). + Flexibility is allowed over the timing of resolution. (Within limit) this may happen at any time after + preparation and before that reference is used. +

+

+ + In practical development terms: This means that different JVMs may realize that a reference cannot be + resolved at different times during execution. Consequently, the actual behaviour cannot be precisely predicted + without intimate knowledge of the JVM (on which the bytecode will be executed). + This makes it hard to give universal guidance to users. + +

+
+ + +

+ (VMSpec 2.17.2) + The loading process is performed by a ClassLoader. +

+

+ (VMSpec 5.3) + A classloader may create a class either by delegation or by defining it directly. + The classloader that initiates loading of a class is known as the initiating loader. + The classloader that defines the class is known as the defining loader. +

+

+ + In practical terms: understanding and appreciating this distinction is crucial when debugging issues + concerning classloaders. + +

+
+ + +

+ (VMSPEC 5.3) + The bootstrap is the base ClassLoader supplied by the Java Virtual Machine. + All others are user (also known as application) ClassLoader instances. +

+

+ + In practical development terms: The System classloader returned by Classloader.getSystemClassLoader() + will be either the bootstrap classloader or a direct descendant of the bootstrap classloader. + Only when debugging issues concerning the system classloader should there be any need to consider the detailed + differences between the bootstrap classloader and the system classloader. + +

+
+ +

+ (VMSpec 5.3) + At runtime, a class (or interface) is determined by its fully qualified name + and by the classloader that defines it. This is known as the class's runtime package. +

+

+ (VMSpec 5.4.4) + Only classes in the same runtime package are mutually accessible. +

+

+ + In practical development terms: two classes with the same symbolic name can only be used interchangeably + if they are defined by the same classloader. A classic symptom indicative of a classloader issue is that + two classes with the same fully qualified name are found to be incompatible during a method call. + This may happen when a member is expecting an interface which is (seemingly) implemented by a class + but the class is in a different runtime package after being defined by a different classloader. This is a + fundamental java language security feature. + +

+
+ + +

+ (VMSpec 5.3) + The classloader which defines the class (whose reference is being resolved) is the one + used to initiate loading of the class referred to. +

+

+ + In practical development terms: This is very important to bear in mind when trying to solve classloader issues. + A classic misunderstanding is this: suppose class A defined by classloader C has a symbolic reference to + class B and further that when C initiates loading of B, this is delegated to classloader D which defines B. + Class B can now only resolve symbols that can be loaded by D, rather than all those which can be loaded by C. + This is a classic recipe for classloader problems. + +

+
+ + +
    +
  • + VMSpec The Java Virtual Machine Specification, Second Edition +
  • +
  • + LangSpec The Java Language Specification, Second Edition +
  • +
+
+
+
+ +

+ When asked to load a class, a class loader may either define the class itself or delegate. + The base ClassLoader class insists that every implementation has a parent class loader. + This delegation model therefore naturally forms a tree structure rooted in the bootstrap classloader. +

+

+ Containers (i.e. applications such as servlet engines or application servers + that manage and provide support services for a number of "contained" applications + that run inside of them) often use complex trees to allow isolation of different applications + running within the container. This is particularly true of J2EE containers. +

+
+ + +

+ When a classloader is asked to load a class, a question presents itself: should it immediately + delegate the loading to its parent (and thus only define those classes not defined by its parent) + or should it try to define it first itself (and only delegate to its parent those classes it does + not itself define). Classloaders which universally adopt the first approach are termed parent-first + and the second child-first. +

+

+ Note: the term child-first (though commonly used) is misleading. + A better term (and one which may be encountered on the mailing list) is parent-last. + This more accurately describes the actual process of classloading performed + by such a classloader. +

+

+ Parent-first loading has been the standard mechanism in the JDK + class loader, at least since Java 1.2 introduced hierarchical classloaders. +

+

+ Child-first classloading has the advantage of helping to improve isolation + between containers and the applications inside them. If an application + uses a library jar that is also used by the container, but the version of + the jar used by the two is different, child-first classloading allows the + contained application to load its version of the jar without affecting the + container. +

+

+ The ability for a servlet container to offer child-first classloading + is made available, as an option, by language in the servlet spec (Section + 9.7.2) that allows a container to offer child-first loading with + certain restrictions, such as not allowing replacement of java.* or + javax.* classes, or the container's implementation classes. +

+

+ Though child-first and parent-first are not the only strategies possible, + they are by far the most common. + All other strategies are rare. + However, it is not uncommon to be faced with a mixture of parent-first and child-first + classloaders within the same hierarchy. +

+
+ + +

+ The class loader used to define a class is available programmatically by calling + the getClassLoader method + on the class in question. This is often known as the class classloader. +

+
+ + +

+ Java 1.2 introduces a mechanism which allows code to access classloaders + which are not the class classloader or one of its parents. + A thread may have a class loader associated with it by its creator for use + by code running in the thread when loading resources and classes. + This classloader is accessed by the getContextClassLoader + method on Thread. It is therefore often known as the context classloader. +

+

+ Note that the quality and appropriateness of the context classloader depends on the + care with which the thread's owner manages it. +

+
+ + +

+ The Javadoc for + + Thread.setContextClassLoader emphasizes the setting of the + context classloader as an aspect of thread creation. However, in many + applications the context classloader is not fixed at thread creation but + rather is changed throughout the life of a thread as thread execution moves + from one context to another. This usage of the context classloader is + particularly important in container applications. +

+

+ For example, in a hypothetical servlet container, a pool of threads + is created to handle HTTP requests. When created these threads have their + context classloader set to a classloader that loads container classes. + After the thread is assigned to handle a request, container code parses + the request and then determines which of the deployed web applications + should handle it. Only when the container is about to call code associated + with a particular web application (i.e. is about to cross an "application + boundary") is the context classloader set to the classloader used to load + the web app's classes. When the web application finishes handling the + request and the call returns, the context classloader is set back to the + container classloader. +

+

+ In a properly managed container, changes in the context classloader are + made when code execution crosses an application boundary. When contained + application A is handling a request, the context classloader + should be the one used to load A's resources. When application + B is handling a request, the context classloader should be + B's. +

+

+ While a contained application is handling a request, it is not + unusual for it to call system or library code loaded by the container. + For example, a contained application may wish to call a utility function + provided by a shared library. This kind of call is considered to be + within the "application boundary", so the context classloader remains + the contained application's classloader. If the system or library code + needs to load classes or other resources only visible to the contained + application's classloader, it can use the context classloader to access + these resources. +

+

+ If the context classloader is properly managed, system and library code + that can be accessed by multiple applications can not only use it to load + application-specific resources, but also can use it to detect which + application is making a call and thereby provided services tailored to the + caller. +

+
+ + +

+ In practice, context classloaders vary in quality and issues sometimes arise + when using them. + The owner of the thread is responsible for setting the classloader. + If the context classloader is not set then it will default to the system + classloader. + Any container doing so will cause difficulties for any code using the context classloader. +

+

+ The owner is also at liberty to set the classloader as they wish. + Containers may set the context classloader so that it is neither a child nor a parent + of the classloader that defines the class using that loader. + Again, this will cause difficulties. +

+

+ Introduced in Java J2EE 1.3 + is a requirement for vendors to appropriately set the context classloader. + Section 6.2.4.8 (1.4 text): +

+ +This specification requires that J2EE containers provide a per thread +context class loader for the use of system or library classes in +dynamically loading classes provided by the application. The EJB +specification requires that all EJB client containers provide a per +thread context class loader for dynamically loading system value classes. +The per thread context class loader is accessed using the Thread method +getContextClassLoader. + +The classes used by an application will typically be loaded by a +hierarchy of class loaders. There may be a top level application class +loader, an extension class loader, and so on, down to a system class +loader. The top level application class loader delegates to the lower +class loaders as needed. Classes loaded by lower class loaders, such as +portable EJB system value classes, need to be able to discover the top +level application class loader used to dynamically load application +classes. + +We require that containers provide a per thread context class loader +that can be used to load top level application classes as described +above. + +

+ This specification leaves quite a lot of freedom for vendors. + (As well as using unconventional terminology and containing the odd typo.) + It is a difficult passage (to say the least). +

+
+ + +

+ Reflection cannot bypass restrictions imposed by the java language security model, but, by avoiding symbolic + references, reflection can be used to load classes which could not otherwise be loaded. Another ClassLoader + can be used to load a class and then reflection used to create an instance. +

+

+ Recall that the runtime packaging is used to determine accessibility. + Reflection cannot be used to avoid basic java security. + Therefore, the runtime packaging becomes an issue when attempting to cast classes + created by reflection using other class loaders. + When using this strategy, various modes of failure are possible + when common class references are defined by the different class loaders. +

+

+ Reflection is often used with the context classloader. In theory, this allows a class defined in + a parent classloader to load any class that is loadable by the application. + In practice, this only works well when the context classloader is set carefully. +

+
+ + + + +
+ +
+ +

+ JCL takes the view that different context class loader indicate boundaries between applications + running in a container environment. Isolation requires that JCL honours these boundaries + and therefore allows different isolated applications to configure their logging systems + independently. +

+
+ +

+ Performance dictates that symbolic references to these classes are present in the calling application code + (reflection would simply be too slow). Therefore, these classes must be loadable by the classloader + that loads the application code. +

+
+ +

+ Performance dictates that symbolic references to the logging systems are present in the implementation + classes (again, reflection would simply be too slow). So, for an implementation to be able to function, + it is necessary for the logging system to be loadable by the classloader that defines the implementing class. +

+
+ + +

+ However, there is actually no reason why LogFactory requires symbolic references to particular Log + implementations. Reflection can be used to load these from an appropriate classloader + without unacceptable performance degradation. + This is the strategy adopted by JCL. +

+

+ JCL uses the context classloader to load the Log implementation. +

+
+
+ +
diff --git a/src/site/xdoc/troubleshooting.xml b/src/site/xdoc/troubleshooting.xml new file mode 100644 index 0000000..7ebe48a --- /dev/null +++ b/src/site/xdoc/troubleshooting.xml @@ -0,0 +1,467 @@ + + + + + + + + Troubleshooting Guide + Commons Documentation Team + + + +
+
+
+

+Diagnostics is a feature introduced in JCL 1.1 as an aid to debugging problems +with JCL configurations. When diagnostics are switched on, messages are logged +to a stream (specified by the user) by the two main classes involved in discovery +in JCL (LogFactory and LogFactoryImpl). +

+

+Diagnostics are intended to be used in conjunction with the source. The source +contains numerous and lengthy comments. Often these are intended to help explain +the meaning of the messages. +

+ +

+Diagnostic logging is intended only to be used when debugging a problematic +configuration. It should be switched off for production. +

+
+ +

+Diagnostic logging is controlled through the system property +org.apache.commons.logging.diagnostics.dest. Setting the property value +to the special strings STDOUT or STDERR (case-sensitive) +will output messages to System.out and System.err respectively. +Setting the property value to a valid file name will result in the messages being logged +to that file. +

+
+ +

+Diagnostics uses the concept of an Object ID (OID). This allows the identity of objects +to be tracked without relying on useful toString implementations. +These are of the form: +

+
+classname@system identity hash code
+
+

+The system identity hash code is found by calling System.identityHashCode() +which should uniquely identify a particular instance. The classname is usually the fully qualified +class name though in a few cases, org.apache.commons.logging.impl.LogFactoryImpl may be +shortened to LogFactoryImpl to increase ease of reading. For example: +

+
+sun.misc.Launcher$AppClassLoader@20120943
+LogFactoryImpl@1671711
+
+

+OIDs are intended to be used to cross-reference. They allow particular instances of classloaders +and JCL classes to be tracked in different contexts. This plays a vital role in building +up the understanding of the classloader environment required to diagnose JCL problems. +

+
+ +

+Each diagnostic message is prefixed with details of the relevant class in a standard format. +This takes the form: +

+
+[class-identifier from ClassLoader OID]
+
+

+ClassLoader OID is the OID of a classloader which loaded +the class issuing the message. +class-identifier identifies the object issuing the message. +

+

+In the case of +LogFactory, this is just LogFactory. For example (line split): +

+
+[LogFactory 
+   from sun.misc.Launcher$AppClassLoader@20120943] BOOTSTRAP COMPLETED
+
+

+In the case of +LogFactoryImpl, the prefix is the instance OID. This can be cross referenced +to discover the details of the TCCL used to manage this instance. For example (line split): +

+
+[LogFactoryImpl@1671711 
+   from sun.misc.Launcher$AppClassLoader@20120943] Instance created.
+
+
+ +

+Understanding the relationships between classloaders is vital when debugging JCL. +At various points, JCL will print to the diagnostic log the hierarchy for important +classloaders. This is obtained by walking the tree using getParent. +Each classloader is represented (visually) by an OID (to allow cross referencing) +and the relationship indicated in child --> parent fashion. +For example (line split for easy reading): +

+
+ClassLoader tree:java.net.URLClassLoader@3526198  
+      --> sun.misc.Launcher$AppClassLoader@20120943 (SYSTEM) 
+      --> sun.misc.Launcher$ExtClassLoader@11126876 
+      --> BOOT
+
+

+Represents a hierarchy with four elements ending in the boot classloader. +

+
+ +

+Whenever the LogFactory class is initialized, diagnostic messages about +the classloader environment are logged. The content of each of these messages is prefixed by +[ENV] to help distinguish them. The extension directories, application classpath, +details of the classloader (including the OID and toString +value) used to load LogFactory and the +classloader tree for that classloader +are logged. +

+

+Many Sun classloaders have confusing toString values. For example, the OID may be +

+
+sun.misc.Launcher$AppClassLoader@20120943
+
+

+with a toString value of +

+
+sun.misc.Launcher$AppClassLoader@133056f
+
+

+Other classloader implementations may give very useful information (such as the local classpath). +

+

+Finally, once initialization is complete a BOOTSTRAP COMPLETED message is issued. +

+
+ +

+LogFactoryImpl is the standard and default LogFactory implementation. +This section obviously only applies to configurations using this implementation. +

+

+Before assigning a Log instance, LogFactory loads a +LogFactory implementation. The content is prefixed by [LOOKUP] +for each diagnostic message logged by this process. +

+

+The implementation used can vary per Thread context classloader (TCCL). If this is the first time +that a Log has been requested for a particular TCCL a new instance will be created. +

+

+Information of particular interest is logged at this stage. Details of the TCCL are logged +allowing the OID later to be cross-referenced to the toString value +and the classloader tree. For example, the +following log snippet details the TCCL (lines split): +

+
+[LogFactory from sun.misc.Launcher$AppClassLoader@20120943] 
+    [LOOKUP] LogFactory implementation requested for the first time for context 
+        classloader java.net.URLClassLoader@3526198
+[LogFactory from sun.misc.Launcher$AppClassLoader@20120943] 
+    [LOOKUP] java.net.URLClassLoader@3526198 == 'java.net.URLClassLoader@35ce36'
+[LogFactory from sun.misc.Launcher$AppClassLoader@20120943] 
+    [LOOKUP] ClassLoader tree:java.net.URLClassLoader@3526198 
+        --> sun.misc.Launcher$AppClassLoader@20120943 (SYSTEM)  
+          --> sun.misc.Launcher$ExtClassLoader@11126876 
+            --> BOOT
+
+
+ +

+The standard LogFactoryImpl issues many diagnostic messages when discovering +the Log implementation to be used. +

+

+During discovery, environment variables are loaded and values set. This content is prefixed by +[ENV] to make it easier to distinguish this material. +

+

+The possible messages issued during discovery are numerous. To understand them, the source +should be consulted. Attention should be paid to the classloader hierarchy trees for the +classloader used to load LogFactory and to the TCCL. +

+
+
+
+

+ Some containers use a custom LogFactory implementation to adapt JCL to their particular + logging system. This has some important consequences for the deployment of applications using JCL within + these containers. +

+

+ Containers known to use this mechanism: +

+ +

+ Containers suspected to use this mechanism: +

+
    +
  • WebSphere Application Server (other versions).
  • +
+

+The Apache Commons team would be grateful if reports were posted to the development list +of other containers using a custom implementation. +

+ + +

+ An exception is thrown by JCL with a message similar to: +

+
+  The chosen LogFactory implementation does not extend LogFactory. Please check your configuration. 
+  (Caused by java.lang.ClassCastException: The application has specified that a custom LogFactory 
+  implementation should be used but Class 'com.ibm.ws.commons.logging.TrLogFactory' cannot be converted 
+  to 'org.apache.commons.logging.LogFactory'. The conflict is caused by the presence of multiple 
+  LogFactory classes in incompatible classloaders. Background can be found in 
+  http://commons.apache.org/logging/tech.html. If you have not explicitly specified a custom
+  LogFactory then it is likely that the container has set one without your knowledge. 
+  In this case, consider using the commons-logging-adapters.jar file or specifying the standard 
+  LogFactory from the command line. Help can be found @http://commons.apache.org/logging.
+  
+

+ This is a WebSphere example so the name of the custom LogFactory is + com.ibm.ws.commons.logging.TrLogFactory. For other containers, this class name will + differ. +

+
+ +

+ A custom LogFactory implementation can only be used if the implementation class loaded + dynamically at runtime can be cast to the LogFactory class that loaded it. There are + several ways in which this cast can fail. The most obvious is that the source code may not actually + extend LogFactory. The source may be compatible but if the LogFactory class + against which the source is compiled is not binary compatible then the cast will also fail. +

+

+ There is also another more unusual way in which this cast can fail: even when the binary is compatible, + the implementation class loaded at runtime may be linked to a different instance of the + LogFactory class. For more information, see the tech guide. +

+

+ This situation may be encountered in containers which use a custom LogFactory implementation. + The implementation will typically be provided in a shared, high level classloader together with JCL. + When an application classloader contains LogFactory, the implementation will be loaded + from that higher level classloader. The implementation class will be linked to the LogFactory + class loaded by the higher level classloader. Even if the + LogFactory implementations are binary compatible, since they are loaded by different classloaders + the two LogFactory Class instances are not equal and so the cast must fail. +

+

+The policy adopted by JCL in this situation is to re-throw this exception. Additional information +is included in the message to help diagnosis. The reasoning behind this choice is that a +particular LogFactory implementation has been actively specified and this +choice should not be ignored. This policy has unfortunate consequences when running in +containers which have custom implementations: the above runtime exception may be thrown +under certain classloading policies without the user knowingly specifying a custom +implementation. +

+
+ +

+ There are various ways to fix this problem. Which fix is right depends on the circumstances. +

+

+ If you are happy using another classloading policy for the application, select a + classloading policy which ensures that LogFactory will be loaded from the + shared classloader containing the custom implementation. +

+

+ If you want to bypass the container adaption mechanism then set the appropriate system property + to the default value when the container is started: +

+
+ -Dorg.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl
+ 
+

+ If you want to continue to use the default container mechanism then: +

+
    +
  • + Find and replace the commons-logging implementation used by the container with + the most modern release +
  • +
  • + Replace the commons-logging jar in the application with the commons-logging-adapters jar. + This will ensure that application classloader will delegate to it's parent when loading + LogFactory. +
  • +
+

+ If you encounter difficulties when applying the fixes recommended, please turn on + diagnostics and consult the logs. +

+
+
+
+
+

+ Because commons-logging is such a fundamental library, some containers modify the way + in which classloading behaves for commons-logging classes. +

+ +

+ At the current date, Tomcat 5.5.16 is the current release. All releases from version + 4.1.x through 5.5.16 have a startup process that places jarfile + ${tomcat.home}/bin/commons-logging-api.jar in the system classpath and then + prevents any webapp from overriding the classes in that jarfile. Effectively, all + webapps behave as if "parent-first" classloading were enabled for those classes. +

+

+ This has some benefits; in particular it means that there are no problems in + these Tomcat versions with having multiple copies of the commons-logging Log + interface in the classpath (which avoids the "Log does not implement Log" + problem described elsewhere). +

+

+ However it also means that no webapp can override the core commons-logging + classes by including an updated commons-logging jarfile in WEB-INF/lib; any + class already loaded via the container takes priority. In particular, as + Tomcat bundles logging 1.0.4 only, the new diagnostics and memory-leak-prevention + features of the 1.1 release will not be available unless the container's + library version is updated. +

+

+ Because the commons-logging-api.jar in the container does not contain any + log-library-adapter classes, updated behaviour for these will be + seen when logging 1.1 is bundled in WEB-INF/lib. In particular, the + support for log4j's TRACE level will take effect without having to update + the container. +

+

+ If you do wish to update Tomcat's version of commons-logging, then you + must use the commons-logging-1.1-api jar only, not the full jar. + Classes in the webapp cannot override classes loaded from the system + classpath set up during Tomcat's startup process, and logging adapters + can only see their matching concrete logging library if that library is + available in the same classpath. Bundling the full commons-logging jarfile + (with adapters) into the system classpath therefore means that logging + libraries (eg log4j) within WEB-INF/lib are not accessible. +

+

+ Note that the behaviour described here only applies if the standard Tomcat + startup process is run. When Tomcat is embedded in a larger + framework (eg run embedded within an IDE) this may not apply. +

+
+ +

+ The JBoss Application Server can be configured to prevent deployed + code from overriding classes higher in the hierarchy, effectively + forcing "parent-first" behaviour for selected classes. By default, + commons-logging is in this list (at least for some JBoss versions + starting with 4.0.2), and therefore including an updated version + of commons-logging in WEB-INF/lib or similar will have no effect. + See the JBoss classloading documentation for more details. +

+
+ +

+ As more information becomes available on this topic, it may be added + to the commons-logging wiki site. +

+
+
+ +
diff --git a/src/test/java/org/apache/commons/logging/AbstractLogTest.java b/src/test/java/org/apache/commons/logging/AbstractLogTest.java new file mode 100644 index 0000000..1a72164 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/AbstractLogTest.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import junit.framework.TestCase; + + +/** + * Generic tests that can be applied to any log adapter by + * subclassing this class and defining method getLogObject + * appropriately. + * + * @author Sean C. Sullivan + * @version $Revision: 1432587 $ + */ +public abstract class AbstractLogTest extends TestCase { + + public abstract Log getLogObject(); + + public void testLoggingWithNullParameters() + { + Log log = this.getLogObject(); + + assertNotNull(log); + + + log.debug(null); + + log.debug(null, null); + + log.debug(log.getClass().getName() + ": debug statement"); + + log.debug(log.getClass().getName() + ": debug statement w/ null exception", new RuntimeException()); + + + log.error(null); + + log.error(null, null); + + log.error(log.getClass().getName() + ": error statement"); + + log.error(log.getClass().getName() + ": error statement w/ null exception", new RuntimeException()); + + + log.fatal(null); + + log.fatal(null, null); + + log.fatal(log.getClass().getName() + ": fatal statement"); + + log.fatal(log.getClass().getName() + ": fatal statement w/ null exception", new RuntimeException()); + + + log.info(null); + + log.info(null, null); + + log.info(log.getClass().getName() + ": info statement"); + + log.info(log.getClass().getName() + ": info statement w/ null exception", new RuntimeException()); + + + log.trace(null); + + log.trace(null, null); + + log.trace(log.getClass().getName() + ": trace statement"); + + log.trace(log.getClass().getName() + ": trace statement w/ null exception", new RuntimeException()); + + + log.warn(null); + + log.warn(null, null); + + log.warn(log.getClass().getName() + ": warn statement"); + + log.warn(log.getClass().getName() + ": warn statement w/ null exception", new RuntimeException()); + } +} diff --git a/src/test/java/org/apache/commons/logging/AltHashtable.java b/src/test/java/org/apache/commons/logging/AltHashtable.java new file mode 100644 index 0000000..231cda7 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/AltHashtable.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import java.util.Hashtable; + +public class AltHashtable extends Hashtable { + + /** + * Generated serial version ID. + */ + private static final long serialVersionUID = 8927996458633688095L; + + public static Object lastKey; + public static Object lastValue; + + public Object put(Object key, Object value) { + lastKey = key; + lastValue = value; + return super.put(key, value); + } +} diff --git a/src/test/java/org/apache/commons/logging/AltHashtableTestCase.java b/src/test/java/org/apache/commons/logging/AltHashtableTestCase.java new file mode 100644 index 0000000..8c8e01c --- /dev/null +++ b/src/test/java/org/apache/commons/logging/AltHashtableTestCase.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import junit.framework.Test; +import junit.framework.TestCase; + +/** + * Test the ability to force the LogFactory class to use some + * arbitrary Hashtable implementation to store its mapping from + * context-classloader -> LogFactory object. + */ +public class AltHashtableTestCase extends TestCase { + + public static Test suite() throws Exception { + Class thisClass = AltHashtableTestCase.class; + ClassLoader thisClassLoader = thisClass.getClassLoader(); + + PathableClassLoader loader = new PathableClassLoader(null); + loader.useExplicitLoader("junit.", thisClassLoader); + loader.addLogicalLib("testclasses"); + loader.addLogicalLib("commons-logging"); + + Class testClass = loader.loadClass(thisClass.getName()); + return new PathableTestSuite(testClass, loader); + } + + /** + * Set up before each test. + *

+ * This method ensures that the appropriate system property is defined + * to force the LogFactory class to use the AltHashtable class as its + * Hashtable implementation for storing factories in. + *

+ * This does make the assumption that whatever JVM we are running in + * doesn't initialise classes until they are actually referenced (ie the + * LogFactory class hasn't been initialised before this method is called). + * This is true of all JVMs I know of; and if it isn't then this test will + * fail and someone will tell us. + */ + public void setUp() { + System.setProperty( + "org.apache.commons.logging.LogFactory.HashtableImpl", + AltHashtable.class.getName()); + } + + /** + * Verify that initialising the LogFactory class will cause it + * to instantiate an object of type specified in system property + * "org.apache.commons.logging.LogFactory.HashtableImpl". + */ + public void testType() { + // Here, the reference to the LogFactory class should cause the + // class to be loaded and initialised. It will see the property + // set and use the AltHashtable class. If other tests in this + // class have already been run within the same classloader then + // LogFactory will already have been initialised, but that + // doesn't change the effectiveness of this test. + assertTrue(LogFactory.factories instanceof AltHashtable); + } + + /** + * Verify that when LogFactory sees a context-classloader for the + * first time that it creates a new entry in the LogFactory.factories + * hashmap. In particular, this checks that this process works ok when + * a system property has been used to specify an alternative Hashtable + * implementation for LogFactory to use. + */ + public void testPutCalled() throws Exception { + AltHashtable.lastKey = null; + AltHashtable.lastValue = null; + + LogFactory.getLog(AltHashtableTestCase.class); + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + assertEquals(contextLoader, AltHashtable.lastKey); + assertNotNull(AltHashtable.lastValue); + } +} diff --git a/src/test/java/org/apache/commons/logging/BadHashtablePropertyTestCase.java b/src/test/java/org/apache/commons/logging/BadHashtablePropertyTestCase.java new file mode 100644 index 0000000..eda6e3f --- /dev/null +++ b/src/test/java/org/apache/commons/logging/BadHashtablePropertyTestCase.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import junit.framework.TestCase; +import java.util.Hashtable; + +/** + * Tests behaviour when the property is misconfigured. + */ +public class BadHashtablePropertyTestCase extends TestCase { + + public void testType() { + assertTrue(LogFactory.factories instanceof Hashtable); + } + + public void testPutCalled() throws Exception { + LogFactory.getLog(BadHashtablePropertyTestCase.class); + } +} diff --git a/src/test/java/org/apache/commons/logging/BasicOperationsTestCase.java b/src/test/java/org/apache/commons/logging/BasicOperationsTestCase.java new file mode 100644 index 0000000..dfadab2 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/BasicOperationsTestCase.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import junit.framework.TestCase; + +/** + * Tests the basic logging operations to ensure that they all function + * without exception failure. In other words, that they do no fail by + * throwing exceptions. + * This is the minimum requirement for any well behaved logger + * and so this test should be run for each kind. + */ +public class BasicOperationsTestCase extends TestCase +{ + public void testIsEnabledClassLog() + { + Log log = LogFactory.getLog(BasicOperationsTestCase.class); + executeIsEnabledTest(log); + } + + public void testIsEnabledNamedLog() + { + Log log = LogFactory.getLog(BasicOperationsTestCase.class.getName()); + executeIsEnabledTest(log); + } + + public void executeIsEnabledTest(Log log) + { + try + { + log.isTraceEnabled(); + log.isDebugEnabled(); + log.isInfoEnabled(); + log.isWarnEnabled(); + log.isErrorEnabled(); + log.isFatalEnabled(); + } + catch (Throwable t) + { + t.printStackTrace(); + fail("Exception thrown: " + t); + } + } + + public void testMessageWithoutExceptionClassLog() + { + Log log = LogFactory.getLog(BasicOperationsTestCase.class); + executeMessageWithoutExceptionTest(log); + } + + public void testMessageWithoutExceptionNamedLog() + { + Log log = LogFactory.getLog(BasicOperationsTestCase.class.getName()); + executeMessageWithoutExceptionTest(log); + } + + public void executeMessageWithoutExceptionTest(Log log) + { + try + { + log.trace("Hello, Mum"); + log.debug("Hello, Mum"); + log.info("Hello, Mum"); + log.warn("Hello, Mum"); + log.error("Hello, Mum"); + log.fatal("Hello, Mum"); + } + catch (Throwable t) + { + t.printStackTrace(); + fail("Exception thrown: " + t); + } + } + + public void testMessageWithExceptionClassLog() + { + Log log = LogFactory.getLog(BasicOperationsTestCase.class); + executeMessageWithExceptionTest(log); + } + + public void testMessageWithExceptionNamedLog() + { + Log log = LogFactory.getLog(BasicOperationsTestCase.class.getName()); + executeMessageWithExceptionTest(log); + } + + public void executeMessageWithExceptionTest(Log log) + { + try + { + log.trace("Hello, Mum", new ArithmeticException()); + log.debug("Hello, Mum", new ArithmeticException()); + log.info("Hello, Mum", new ArithmeticException()); + log.warn("Hello, Mum", new ArithmeticException()); + log.error("Hello, Mum", new ArithmeticException()); + log.fatal("Hello, Mum", new ArithmeticException()); + } + catch (Throwable t) + { + t.printStackTrace(); + fail("Exception thrown: " + t); + } + } +} diff --git a/src/test/java/org/apache/commons/logging/DummyException.java b/src/test/java/org/apache/commons/logging/DummyException.java new file mode 100644 index 0000000..2f5fdd4 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/DummyException.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +/** + * Dummy exception that unit tests create instances of when they want to test + * logging of an Exception object. + */ +public class DummyException extends Exception { + private static final long serialVersionUID = 1L; + public DummyException() { + // super("Dummy Exception for unit testing"); + } +} diff --git a/src/test/java/org/apache/commons/logging/LoadTestCase.java b/src/test/java/org/apache/commons/logging/LoadTestCase.java new file mode 100644 index 0000000..ac3365b --- /dev/null +++ b/src/test/java/org/apache/commons/logging/LoadTestCase.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import junit.framework.TestCase; + +/** + * testcase to emulate container and application isolated from container + * @author baliuka + * @version $Id: LoadTestCase.java 1432587 2013-01-13 11:11:32Z tn $ + */ +public class LoadTestCase extends TestCase{ + //TODO: need some way to add service provider packages + static private String LOG_PCKG[] = {"org.apache.commons.logging", + "org.apache.commons.logging.impl"}; + + /** + * A custom classloader which "duplicates" logging classes available + * in the parent classloader into itself. + *

+ * When asked to load a class that is in one of the LOG_PCKG packages, + * it loads the class itself (child-first). This class doesn't need + * to be set up with a classpath, as it simply uses the same classpath + * as the classloader that loaded it. + */ + static class AppClassLoader extends ClassLoader{ + + java.util.Map classes = new java.util.HashMap(); + + AppClassLoader(ClassLoader parent){ + super(parent); + } + + private Class def(String name)throws ClassNotFoundException{ + + Class result = (Class)classes.get(name); + if(result != null){ + return result; + } + + try{ + + ClassLoader cl = this.getClass().getClassLoader(); + String classFileName = name.replace('.','/') + ".class"; + java.io.InputStream is = cl.getResourceAsStream(classFileName); + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + + while(is.available() > 0){ + out.write(is.read()); + } + + byte data [] = out.toByteArray(); + + result = super.defineClass(name, data, 0, data.length ); + classes.put(name,result); + + return result; + + }catch(java.io.IOException ioe){ + + throw new ClassNotFoundException( name + " caused by " + + ioe.getMessage() ); + } + + + } + + // not very trivial to emulate we must implement "findClass", + // but it will delegete to junit class loder first + public Class loadClass(String name)throws ClassNotFoundException{ + + //isolates all logging classes, application in the same classloader too. + //filters exeptions to simlify handling in test + for(int i = 0; i < LOG_PCKG.length; i++ ){ + if( name.startsWith( LOG_PCKG[i] ) && + name.indexOf("Exception") == -1 ){ + return def(name); + } + } + return super.loadClass(name); + } + + } + + + /** + * Call the static setAllowFlawedContext method on the specified class + * (expected to be a UserClass loaded via a custom classloader), passing + * it the specified state parameter. + */ + private void setAllowFlawedContext(Class c, String state) throws Exception { + Class[] params = {String.class}; + java.lang.reflect.Method m = c.getDeclaredMethod("setAllowFlawedContext", params); + m.invoke(null, new Object[] {state}); + } + + /** + * Test what happens when we play various classloader tricks like those + * that happen in web and j2ee containers. + *

+ * Note that this test assumes that commons-logging.jar and log4j.jar + * are available via the system classpath. + */ + public void testInContainer()throws Exception{ + + //problem can be in this step (broken app container or missconfiguration) + //1. Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); + //2. Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); + // we expect this : + // 1. Thread.currentThread().setContextClassLoader(appLoader); + // 2. Thread.currentThread().setContextClassLoader(null); + + // Context classloader is same as class calling into log + Class cls = reload(); + Thread.currentThread().setContextClassLoader(cls.getClassLoader()); + execute(cls); + + // Context classloader is the "bootclassloader". This is technically + // bad, but LogFactoryImpl.ALLOW_FLAWED_CONTEXT defaults to true so + // this test should pass. + cls = reload(); + Thread.currentThread().setContextClassLoader(null); + execute(cls); + + // Context classloader is the "bootclassloader". This is same as above + // except that ALLOW_FLAWED_CONTEXT is set to false; an error should + // now be reported. + cls = reload(); + Thread.currentThread().setContextClassLoader(null); + try { + setAllowFlawedContext(cls, "false"); + execute(cls); + fail("Logging config succeeded when context classloader was null!"); + } catch(LogConfigurationException ex) { + // expected; the boot classloader doesn't *have* JCL available + } + + // Context classloader is the system classloader. + // + // This is expected to cause problems, as LogFactoryImpl will attempt + // to use the system classloader to load the Log4JLogger class, which + // will then be unable to cast that object to the Log interface loaded + // via the child classloader. However as ALLOW_FLAWED_CONTEXT defaults + // to true this test should pass. + cls = reload(); + Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); + execute(cls); + + // Context classloader is the system classloader. This is the same + // as above except that ALLOW_FLAWED_CONTEXT is set to false; an error + // should now be reported. + cls = reload(); + Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); + try { + setAllowFlawedContext(cls, "false"); + execute(cls); + fail("Error: somehow downcast a Logger loaded via system classloader" + + " to the Log interface loaded via a custom classloader"); + } catch(LogConfigurationException ex) { + // expected + } + } + + /** + * Load class UserClass via a temporary classloader which is a child of + * the classloader used to load this test class. + */ + private Class reload()throws Exception{ + + Class testObjCls = null; + + AppClassLoader appLoader = new AppClassLoader( + this.getClass().getClassLoader()); + try{ + + testObjCls = appLoader.loadClass(UserClass.class.getName()); + + }catch(ClassNotFoundException cnfe){ + throw cnfe; + }catch(Throwable t){ + t.printStackTrace(); + fail("AppClassLoader failed "); + } + + assertTrue( "app isolated" ,testObjCls.getClassLoader() == appLoader ); + + + return testObjCls; + + + } + + + private void execute(Class cls)throws Exception{ + + cls.newInstance(); + + } + + public void setUp() { + // save state before test starts so we can restore it when test ends + origContextClassLoader = Thread.currentThread().getContextClassLoader(); + } + + public void tearDown() { + // restore original state so a test can't stuff up later tests. + Thread.currentThread().setContextClassLoader(origContextClassLoader); + } + + private ClassLoader origContextClassLoader; +} diff --git a/src/test/java/org/apache/commons/logging/LogTestCase.java b/src/test/java/org/apache/commons/logging/LogTestCase.java new file mode 100644 index 0000000..0ac2a6f --- /dev/null +++ b/src/test/java/org/apache/commons/logging/LogTestCase.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + + +public class LogTestCase extends AbstractLogTest +{ + + public Log getLogObject() + { + /** + * Pickup whatever is found/configured! + */ + return LogFactory.getLog(this.getClass().getName()); + } + +} diff --git a/src/test/java/org/apache/commons/logging/NullClassLoaderTestCase.java b/src/test/java/org/apache/commons/logging/NullClassLoaderTestCase.java new file mode 100644 index 0000000..0e84c1c --- /dev/null +++ b/src/test/java/org/apache/commons/logging/NullClassLoaderTestCase.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import junit.framework.TestCase; + +/** + * Test cases for situations where getClassLoader or getContextClassLoader + * return null. This can happen when using JDK 1.1. It can also happen when + * JCL is deployed via the bootclassloader - something that could be done when + * using java in embedded systems. + */ +public class NullClassLoaderTestCase extends TestCase { + + //---------------------- unit tests --------------------------------- + + /** + * This tests that when getContextClassLoader returns null, the + * LogFactory.getLog(name) method still correctly returns the same + * log object when called multiple times with the same name. + */ + public void testSameLogObject() throws Exception { + // unfortunately, there just isn't any way to emulate JCL being + // accessable via the null classloader in "standard" systems, so + // we can't include this test in our standard unit tests. + } +} diff --git a/src/test/java/org/apache/commons/logging/PathableClassLoader.java b/src/test/java/org/apache/commons/logging/PathableClassLoader.java new file mode 100644 index 0000000..830f652 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/PathableClassLoader.java @@ -0,0 +1,436 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * A ClassLoader which sees only specified classes, and which can be + * set to do parent-first or child-first path lookup. + *

+ * Note that this classloader is not "industrial strength"; users + * looking for such a class may wish to look at the Tomcat sourcecode + * instead. In particular, this class may not be threadsafe. + *

+ * Note that the ClassLoader.getResources method isn't overloaded here. + * It would be nice to ensure that when child-first lookup is set the + * resources from the child are returned earlier in the list than the + * resources from the parent. However overriding this method isn't possible + * as the java 1.4 version of ClassLoader declares this method final + * (though the java 1.5 version has removed the final qualifier). As the + * ClassLoader javadoc doesn't specify the order in which resources + * are returned, it's valid to return the resources in any order (just + * untidy) so the inherited implementation is technically ok. + */ + +public class PathableClassLoader extends URLClassLoader { + + private static final URL[] NO_URLS = new URL[0]; + + /** + * A map of package-prefix to ClassLoader. Any class which is in + * this map is looked up via the specified classloader instead of + * the classpath associated with this classloader or its parents. + *

+ * This is necessary in order for the rest of the world to communicate + * with classes loaded via a custom classloader. As an example, junit + * testcases which are loaded via a custom classloader needs to see + * the same junit classes as the code invoking the testcase, otherwise + * they can't pass result objects back. + *

+ * Normally, only a classloader created with a null parent needs to + * have any lookasides defined. + */ + private HashMap lookasides = null; + + /** + * See setParentFirst. + */ + private boolean parentFirst = true; + + /** + * Constructor. + *

+ * Often, null is passed as the parent, ie the parent of the new + * instance is the bootloader. This ensures that the classpath is + * totally clean; nothing but the standard java library will be + * present. + *

+ * When using a null parent classloader with a junit testcase, it *is* + * necessary for the junit library to also be visible. In this case, it + * is recommended that the following code be used: + *

+     * pathableLoader.useExplicitLoader(
+     *   "junit.",
+     *   junit.framework.Test.class.getClassLoader());
+     * 
+ * Note that this works regardless of whether junit is on the system + * classpath, or whether it has been loaded by some test framework that + * creates its own classloader to run unit tests in (eg maven2's + * Surefire plugin). + */ + public PathableClassLoader(ClassLoader parent) { + super(NO_URLS, parent); + } + + /** + * Allow caller to explicitly add paths. Generally this not a good idea; + * use addLogicalLib instead, then define the location for that logical + * library in the build.xml file. + */ + public void addURL(URL url) { + super.addURL(url); + } + + /** + * Specify whether this classloader should ask the parent classloader + * to resolve a class first, before trying to resolve it via its own + * classpath. + *

+ * Checking with the parent first is the normal approach for java, but + * components within containers such as servlet engines can use + * child-first lookup instead, to allow the components to override libs + * which are visible in shared classloaders provided by the container. + *

+ * Note that the method getResources always behaves as if parentFirst=true, + * because of limitations in java 1.4; see the javadoc for method + * getResourcesInOrder for details. + *

+ * This value defaults to true. + */ + public void setParentFirst(boolean state) { + parentFirst = state; + } + + /** + * For classes with the specified prefix, get them from the system + * classpath which is active at the point this method is called. + *

+ * This method is just a shortcut for + *

+     * useExplicitLoader(prefix, ClassLoader.getSystemClassLoader());
+     * 
+ *

+ * Of course, this assumes that the classes of interest are already + * in the classpath of the system classloader. + */ + public void useSystemLoader(String prefix) { + useExplicitLoader(prefix, ClassLoader.getSystemClassLoader()); + + } + + /** + * Specify a classloader to use for specific java packages. + *

+ * The specified classloader is normally a loader that is NOT + * an ancestor of this classloader. In particular, this loader + * may have the bootloader as its parent, but be configured to + * see specific other classes (eg the junit library loaded + * via the system classloader). + *

+ * The differences between using this method, and using + * addLogicalLib are: + *

    + *
  • If code calls getClassLoader on a class loaded via + * "lookaside", then traces up its inheritance chain, it + * will see the "real" classloaders. When the class is remapped + * into this classloader via addLogicalLib, the classloader + * chain seen is this object plus ancestors. + *
  • If two different jars contain classes in the same + * package, then it is not possible to load both jars into + * the same "lookaside" classloader (eg the system classloader) + * then map one of those subsets from here. Of course they could + * be loaded into two different "lookaside" classloaders and + * then a prefix used to map from here to one of those classloaders. + *
+ */ + public void useExplicitLoader(String prefix, ClassLoader loader) { + if (lookasides == null) { + lookasides = new HashMap(); + } + lookasides.put(prefix, loader); + } + + /** + * Specify a collection of logical libraries. See addLogicalLib. + */ + public void addLogicalLib(String[] logicalLibs) { + for(int i=0; i + * The specified lib name is used as a key into the system properties; + * there is expected to be a system property defined with that name + * whose value is a url that indicates where that logical library can + * be found. Typically this is the name of a jar file, or a directory + * containing class files. + *

+ * If there is no system property, but the classloader that loaded + * this class is a URLClassLoader then the set of URLs that the + * classloader uses for its classpath is scanned; any jar in the + * URL set whose name starts with the specified string is added to + * the classpath managed by this instance. + *

+ * Using logical library names allows the calling code to specify its + * desired classpath without knowing the exact location of the necessary + * classes. + */ + public void addLogicalLib(String logicalLib) { + // first, check the system properties + String filename = System.getProperty(logicalLib); + if (filename != null) { + try { + URL libUrl = new File(filename).toURL(); + addURL(libUrl); + return; + } catch(java.net.MalformedURLException e) { + throw new UnknownError( + "Invalid file [" + filename + "] for logical lib [" + logicalLib + "]"); + } + } + + // now check the classpath for a similar-named lib + URL libUrl = libFromClasspath(logicalLib); + if (libUrl != null) { + addURL(libUrl); + return; + } + + // lib not found + throw new UnknownError( + "Logical lib [" + logicalLib + "] is not defined" + + " as a System property."); + } + + /** + * If the classloader that loaded this class has this logical lib in its + * path, then return the matching URL otherwise return null. + *

+ * This only works when the classloader loading this class is an instance + * of URLClassLoader and thus has a getURLs method that returns the classpath + * it uses when loading classes. However in practice, the vast majority of the + * time this type is the classloader used. + *

+ * The classpath of the classloader for this instance is scanned, and any + * jarfile in the path whose name starts with the logicalLib string is + * considered a match. For example, passing "foo" will match a url + * of file:///some/where/foo-2.7.jar. + *

+ * When multiple classpath entries match the specified logicalLib string, + * the one with the shortest filename component is returned. This means that + * if "foo-1.1.jar" and "foobar-1.1.jar" are in the path, then a logicalLib + * name of "foo" will match the first entry above. + */ + private URL libFromClasspath(String logicalLib) { + ClassLoader cl = this.getClass().getClassLoader(); + if (cl instanceof URLClassLoader == false) { + return null; + } + + URLClassLoader ucl = (URLClassLoader) cl; + URL[] path = ucl.getURLs(); + URL shortestMatch = null; + int shortestMatchLen = Integer.MAX_VALUE; + for(int i=0; i= 0) { + filename = filename.substring(lastSlash+1); + } + + if (filename.startsWith(logicalLib)) { + // ok, this is a candidate + if (filename.length() < shortestMatchLen) { + shortestMatch = u; + shortestMatchLen = filename.length(); + } + } + } + + return shortestMatch; + } + + /** + * Override ClassLoader method. + *

+ * For each explicitly mapped package prefix, if the name matches the + * prefix associated with that entry then attempt to load the class via + * that entries' classloader. + */ + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + // just for performance, check java and javax + if (name.startsWith("java.") || name.startsWith("javax.")) { + return super.loadClass(name, resolve); + } + + if (lookasides != null) { + for(Iterator i = lookasides.entrySet().iterator(); i.hasNext(); ) { + Map.Entry entry = (Map.Entry) i.next(); + String prefix = (String) entry.getKey(); + if (name.startsWith(prefix) == true) { + ClassLoader loader = (ClassLoader) entry.getValue(); + Class clazz = Class.forName(name, resolve, loader); + return clazz; + } + } + } + + if (parentFirst) { + return super.loadClass(name, resolve); + } else { + // Implement child-first. + // + // It appears that the findClass method doesn't check whether the + // class has already been loaded. This seems odd to me, but without + // first checking via findLoadedClass we can get java.lang.LinkageError + // with message "duplicate class definition" which isn't good. + + try { + Class clazz = findLoadedClass(name); + if (clazz == null) { + clazz = super.findClass(name); + } + if (resolve) { + resolveClass(clazz); + } + return clazz; + } catch(ClassNotFoundException e) { + return super.loadClass(name, resolve); + } + } + } + + /** + * Same as parent class method except that when parentFirst is false + * the resource is looked for in the local classpath before the parent + * loader is consulted. + */ + public URL getResource(String name) { + if (parentFirst) { + return super.getResource(name); + } else { + URL local = super.findResource(name); + if (local != null) { + return local; + } + return super.getResource(name); + } + } + + /** + * Emulate a proper implementation of getResources which respects the + * setting for parentFirst. + *

+ * Note that it's not possible to override the inherited getResources, as + * it's declared final in java1.4 (thought that's been removed for 1.5). + * The inherited implementation always behaves as if parentFirst=true. + */ + public Enumeration getResourcesInOrder(String name) throws IOException { + if (parentFirst) { + return super.getResources(name); + } else { + Enumeration localUrls = super.findResources(name); + + ClassLoader parent = getParent(); + if (parent == null) { + // Alas, there is no method to get matching resources + // from a null (BOOT) parent classloader. Calling + // ClassLoader.getSystemClassLoader isn't right. Maybe + // calling Class.class.getResources(name) would do? + // + // However for the purposes of unit tests, we can + // simply assume that no relevant resources are + // loadable from the parent; unit tests will never be + // putting any of their resources in a "boot" classloader + // path! + return localUrls; + } + Enumeration parentUrls = parent.getResources(name); + + ArrayList localItems = toList(localUrls); + ArrayList parentItems = toList(parentUrls); + localItems.addAll(parentItems); + return Collections.enumeration(localItems); + } + } + + /** + * + * Clean implementation of list function of + * {@link java.utils.Collection} added in JDK 1.4 + * @param en Enumeration, possibly null + * @return ArrayList containing the enumerated + * elements in the enumerated order, not null + */ + private ArrayList toList(Enumeration en) { + ArrayList results = new ArrayList(); + if (en != null) { + while (en.hasMoreElements()){ + Object element = en.nextElement(); + results.add(element); + } + } + return results; + } + + /** + * Same as parent class method except that when parentFirst is false + * the resource is looked for in the local classpath before the parent + * loader is consulted. + */ + public InputStream getResourceAsStream(String name) { + if (parentFirst) { + return super.getResourceAsStream(name); + } else { + URL local = super.findResource(name); + if (local != null) { + try { + return local.openStream(); + } catch(IOException e) { + // TODO: check if this is right or whether we should + // fall back to trying parent. The javadoc doesn't say... + return null; + } + } + return super.getResourceAsStream(name); + } + } +} diff --git a/src/test/java/org/apache/commons/logging/PathableTestSuite.java b/src/test/java/org/apache/commons/logging/PathableTestSuite.java new file mode 100644 index 0000000..1a2ca81 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/PathableTestSuite.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import java.util.Properties; + +import junit.framework.Test; +import junit.framework.TestResult; +import junit.framework.TestSuite; + +/** + * Custom TestSuite class that can be used to control the context classloader + * in operation when a test runs. + *

+ * For tests that need to control exactly what the classloader hierarchy is + * like when the test is run, something like the following is recommended: + *

+ * class SomeTestCase extends TestCase {
+ *  public static Test suite() throws Exception {
+ *   PathableClassLoader parent = new PathableClassLoader(null);
+ *   parent.useSystemLoader("junit.");
+ * 
+ *   PathableClassLoader child = new PathableClassLoader(parent);
+ *   child.addLogicalLib("testclasses");
+ *   child.addLogicalLib("log4j12");
+ *   child.addLogicalLib("commons-logging");
+ * 
+ *   Class testClass = child.loadClass(SomeTestCase.class.getName());
+ *   ClassLoader contextClassLoader = child;
+ * 
+ *   PathableTestSuite suite = new PathableTestSuite(testClass, child);
+ *   return suite;
+ *  }
+ * 
+ *  // test methods go here
+ * }
+ * 
+ * Note that if the suite method throws an exception then this will be handled + * reasonable gracefully by junit; it will report that the suite method for + * a test case failed with exception yyy. + *

+ * The use of PathableClassLoader is not required to use this class, but it + * is expected that using the two classes together is common practice. + *

+ * This class will run each test methods within the specified TestCase using + * the specified context classloader and system classloader. If different + * tests within the same class require different context classloaders, + * then the context classloader passed to the constructor should be the + * "lowest" one available, and tests that need the context set to some parent + * of this "lowest" classloader can call + *

+ *  // NB: pseudo-code only
+ *  setContextClassLoader(getContextClassLoader().getParent());
+ * 
+ * This class ensures that any context classloader changes applied by a test + * is undone after the test is run, so tests don't need to worry about + * restoring the context classloader on exit. This class also ensures that + * the system properties are restored to their original settings after each + * test, so tests that manipulate those don't need to worry about resetting them. + *

+ * This class does not provide facilities for manipulating system properties; + * tests that need specific system properties can simply set them in the + * fixture or at the start of a test method. + *

+ * Important! When the test case is run, "this.getClass()" refers of + * course to the Class object passed to the constructor of this class - which + * is different from the class whose suite() method was executed to determine + * the classpath. This means that the suite method cannot communicate with + * the test cases simply by setting static variables (for example to make the + * custom classloaders available to the test methods or setUp/tearDown fixtures). + * If this is really necessary then it is possible to use reflection to invoke + * static methods on the class object passed to the constructor of this class. + *

+ *

Limitations

+ *

+ * This class cannot control the system classloader (ie what method + * ClassLoader.getSystemClassLoader returns) because Java provides no + * mechanism for setting the system classloader. In this case, the only + * option is to invoke the unit test in a separate JVM with the appropriate + * settings. + *

+ * The effect of using this approach in a system that uses junit's + * "reloading classloader" behaviour is unknown. This junit feature is + * intended for junit GUI apps where a test may be run multiple times + * within the same JVM - and in particular, when the .class file may + * be modified between runs of the test. How junit achieves this is + * actually rather weird (the whole junit code is rather weird in fact) + * and it is not clear whether this approach will work as expected in + * such situations. + */ +public class PathableTestSuite extends TestSuite { + + /** + * The classloader that should be set as the context classloader + * before each test in the suite is run. + */ + private final ClassLoader contextLoader; + + /** + * Constructor. + * + * @param testClass is the TestCase that is to be run, as loaded by + * the appropriate ClassLoader. + * + * @param contextClassLoader is the loader that should be returned by + * calls to Thread.currentThread.getContextClassLoader from test methods + * (or any method called by test methods). + */ + public PathableTestSuite(Class testClass, ClassLoader contextClassLoader) { + super(testClass); + contextLoader = contextClassLoader; + } + + /** + * This method is invoked once for each Test in the current TestSuite. + * Note that a Test may itself be a TestSuite object (ie a collection + * of tests). + *

+ * The context classloader and system properties are saved before each + * test, and restored after the test completes to better isolate tests. + */ + public void runTest(Test test, TestResult result) { + ClassLoader origContext = Thread.currentThread().getContextClassLoader(); + Properties oldSysProps = (Properties) System.getProperties().clone(); + try { + Thread.currentThread().setContextClassLoader(contextLoader); + test.run(result); + } finally { + System.setProperties(oldSysProps); + Thread.currentThread().setContextClassLoader(origContext); + } + } +} diff --git a/src/test/java/org/apache/commons/logging/SimpleLogTestCase.java b/src/test/java/org/apache/commons/logging/SimpleLogTestCase.java new file mode 100644 index 0000000..cc2fceb --- /dev/null +++ b/src/test/java/org/apache/commons/logging/SimpleLogTestCase.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import org.apache.commons.logging.impl.SimpleLog; + +public class SimpleLogTestCase extends AbstractLogTest +{ + public Log getLogObject() + { + return new SimpleLog(this.getClass().getName()); + } +} diff --git a/src/test/java/org/apache/commons/logging/UserClass.java b/src/test/java/org/apache/commons/logging/UserClass.java new file mode 100644 index 0000000..9013133 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/UserClass.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.impl.LogFactoryImpl; + +public class UserClass { + + /** + * Set the ALLOW_FLAWED_CONTEXT feature on the LogFactoryImpl object + * associated with this class' classloader. + *

+ * Don't forget to set the context classloader to whatever it will be + * when an instance of this class is actually created before calling + * this method! + */ + public static void setAllowFlawedContext(String state) { + LogFactory f = LogFactory.getFactory(); + f.setAttribute(LogFactoryImpl.ALLOW_FLAWED_CONTEXT_PROPERTY, state); + } + + public UserClass() { + Log log = LogFactory.getLog(LoadTestCase.class); + } + +} diff --git a/src/test/java/org/apache/commons/logging/avalon/AvalonLoggerTestCase.java b/src/test/java/org/apache/commons/logging/avalon/AvalonLoggerTestCase.java new file mode 100644 index 0000000..182624d --- /dev/null +++ b/src/test/java/org/apache/commons/logging/avalon/AvalonLoggerTestCase.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.avalon; + +import org.apache.avalon.framework.logger.NullLogger; +import org.apache.commons.logging.impl.AvalonLogger; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.AbstractLogTest; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * @author Neeme Praks + * @version $Revision: 1432587 $ $Date: 2013-01-13 12:11:32 +0100 (Sun, 13 Jan 2013) $ + */ +public class AvalonLoggerTestCase extends AbstractLogTest { + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTestSuite(AvalonLoggerTestCase.class); + return suite; + } + + public Log getLogObject() { + // Output does not seem to be used, so don't display it. + Log log = new AvalonLogger(new NullLogger()); + return log; + } +} diff --git a/src/test/java/org/apache/commons/logging/config/FirstPriorityConfigTestCase.java b/src/test/java/org/apache/commons/logging/config/FirstPriorityConfigTestCase.java new file mode 100644 index 0000000..4725dcd --- /dev/null +++ b/src/test/java/org/apache/commons/logging/config/FirstPriorityConfigTestCase.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.config; + + +import java.net.URL; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + + +/** + * Tests that verify that the process of configuring logging on startup + * works correctly by selecting the file with the highest priority. + *

+ * This test sets up a classpath where: + *

    + *
  • first file found has priority=20 + *
  • second file found has priority=10 + *
+ * The result should be that the first file is used. + */ +public class FirstPriorityConfigTestCase extends TestCase { + + // ------------------------------------------- JUnit Infrastructure Methods + + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + Class thisClass = FirstPriorityConfigTestCase.class; + + // Determine the URL to this .class file, so that we can then + // append the priority dirs to it. For tidiness, load this + // class through a dummy loader though this is not absolutely + // necessary... + PathableClassLoader dummy = new PathableClassLoader(null); + dummy.useExplicitLoader("junit.", Test.class.getClassLoader()); + dummy.addLogicalLib("testclasses"); + dummy.addLogicalLib("commons-logging"); + + String thisClassPath = thisClass.getName().replace('.', '/') + ".class"; + URL baseUrl = dummy.findResource(thisClassPath); + + // Now set up the desired classloader hierarchy. We'll put JCL + // in the container path, the testcase in a webapp path, and + // both config files into the webapp path too. + PathableClassLoader containerLoader = new PathableClassLoader(null); + containerLoader.useExplicitLoader("junit.", Test.class.getClassLoader()); + containerLoader.addLogicalLib("commons-logging"); + + PathableClassLoader webappLoader = new PathableClassLoader(containerLoader); + webappLoader.addLogicalLib("testclasses"); + + URL pri20URL = new URL(baseUrl, "priority20/"); + webappLoader.addURL(pri20URL); + + URL pri10URL = new URL(baseUrl, "priority10/"); + webappLoader.addURL(pri10URL); + + // load the test class via webapp loader, and use the webapp loader + // as the tccl loader too. + Class testClass = webappLoader.loadClass(thisClass.getName()); + return new PathableTestSuite(testClass, webappLoader); + } + + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + LogFactory.releaseAll(); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + LogFactory.releaseAll(); + } + + // ----------------------------------------------------------- Test Methods + + /** + * Verify that the config file being used is the one containing + * the desired configId value. + */ + public void testPriority() throws Exception { + LogFactory instance = LogFactory.getFactory(); + + ClassLoader thisClassLoader = this.getClass().getClassLoader(); + ClassLoader lfClassLoader = instance.getClass().getClassLoader(); + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + + // context classloader should be thisClassLoader + assertEquals(thisClassLoader, contextClassLoader); + + // lfClassLoader should be parent of this classloader + assertEquals(lfClassLoader, thisClassLoader.getParent()); + assertEquals(PathableClassLoader.class.getName(), + lfClassLoader.getClass().getName()); + + String id = (String) instance.getAttribute("configId"); + assertEquals("Correct config file loaded", "priority20", id ); + } +} diff --git a/src/test/java/org/apache/commons/logging/config/PriorityConfigTestCase.java b/src/test/java/org/apache/commons/logging/config/PriorityConfigTestCase.java new file mode 100644 index 0000000..831e255 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/config/PriorityConfigTestCase.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.config; + + +import java.net.URL; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + + +/** + * Tests that verify that the process of configuring logging on startup + * works correctly by selecting the file with the highest priority. + *

+ * This test sets up a classpath where: + *

    + *
  • first file (in parent loader) has priority=10 (parentFirst=true) + *
  • second file found has no priority set + *
  • third file found has priority=20 + *
  • fourth file found also has priority=20 + *
+ * The result should be that the third file is used. + *

+ * Note that parentFirst=true is used in this test because method + * PathableClassLoader.getResources always behaves as if + * parentFirst=true; see the PathableClassLoader javadoc for details. + */ + +public class PriorityConfigTestCase extends TestCase { + + // ------------------------------------------- JUnit Infrastructure Methods + + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + Class thisClass = PriorityConfigTestCase.class; + + // Determine the URL to this .class file, so that we can then + // append the priority dirs to it. For tidiness, load this + // class through a dummy loader though this is not absolutely + // necessary... + PathableClassLoader dummy = new PathableClassLoader(null); + dummy.useExplicitLoader("junit.", Test.class.getClassLoader()); + dummy.addLogicalLib("testclasses"); + dummy.addLogicalLib("commons-logging"); + + String thisClassPath = thisClass.getName().replace('.', '/') + ".class"; + URL baseUrl = dummy.findResource(thisClassPath); + + // Now set up the desired classloader hierarchy. We'll put a config + // file of priority=10 in the container path, and ones of both + // "no priority" and priority=20 in the webapp path. + // + // A second properties file with priority=20 is also added, + // so we can check that the first one in the classpath is + // used. + PathableClassLoader containerLoader = new PathableClassLoader(null); + containerLoader.useExplicitLoader("junit.", Test.class.getClassLoader()); + containerLoader.addLogicalLib("commons-logging"); + + URL pri10URL = new URL(baseUrl, "priority10/"); + containerLoader.addURL(pri10URL); + + PathableClassLoader webappLoader = new PathableClassLoader(containerLoader); + webappLoader.setParentFirst(true); + webappLoader.addLogicalLib("testclasses"); + + URL noPriorityURL = new URL(baseUrl, "nopriority/"); + webappLoader.addURL(noPriorityURL); + + URL pri20URL = new URL(baseUrl, "priority20/"); + webappLoader.addURL(pri20URL); + + URL pri20aURL = new URL(baseUrl, "priority20a/"); + webappLoader.addURL(pri20aURL); + + // load the test class via webapp loader, and use the webapp loader + // as the tccl loader too. + Class testClass = webappLoader.loadClass(thisClass.getName()); + return new PathableTestSuite(testClass, webappLoader); + } + + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + LogFactory.releaseAll(); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + LogFactory.releaseAll(); + } + + // ----------------------------------------------------------- Test Methods + + /** + * Verify that the config file being used is the one containing + * the desired configId value. + */ + public void testPriority() throws Exception { + LogFactory instance = LogFactory.getFactory(); + String id = (String) instance.getAttribute("configId"); + assertEquals("Correct config file loaded", "priority20", id ); + } +} diff --git a/src/test/java/org/apache/commons/logging/impl/WeakHashtableTestCase.java b/src/test/java/org/apache/commons/logging/impl/WeakHashtableTestCase.java new file mode 100644 index 0000000..8a37068 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/impl/WeakHashtableTestCase.java @@ -0,0 +1,313 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.impl; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import junit.framework.TestCase; + +public class WeakHashtableTestCase extends TestCase { + + private static final int WAIT_FOR_THREAD_COMPLETION = 5000; // 5 seconds + private static final int RUN_LOOPS = 3000; + private static final int OUTER_LOOP = 400; + private static final int THREAD_COUNT = 10; + + private static WeakHashtable hashtable; + + /** Maximum number of iterations before our test fails */ + private static final int MAX_GC_ITERATIONS = 50; + + private WeakHashtable weakHashtable; + private Long keyOne; + private Long keyTwo; + private Long keyThree; + private Long valueOne; + private Long valueTwo; + private Long valueThree; + + public WeakHashtableTestCase(String testName) { + super(testName); + } + + protected void setUp() throws Exception { + super.setUp(); + weakHashtable = new WeakHashtable(); + + keyOne = new Long(1); + keyTwo = new Long(2); + keyThree = new Long(3); + valueOne = new Long(100); + valueTwo = new Long(200); + valueThree = new Long(300); + + weakHashtable.put(keyOne, valueOne); + weakHashtable.put(keyTwo, valueTwo); + weakHashtable.put(keyThree, valueThree); + } + + /** Tests public boolean contains(Object value) */ + public void testContains() throws Exception { + assertFalse(weakHashtable.contains(new Long(1))); + assertFalse(weakHashtable.contains(new Long(2))); + assertFalse(weakHashtable.contains(new Long(3))); + assertTrue(weakHashtable.contains(new Long(100))); + assertTrue(weakHashtable.contains(new Long(200))); + assertTrue(weakHashtable.contains(new Long(300))); + assertFalse(weakHashtable.contains(new Long(400))); + } + + /** Tests public boolean containsKey(Object key) */ + public void testContainsKey() throws Exception { + assertTrue(weakHashtable.containsKey(new Long(1))); + assertTrue(weakHashtable.containsKey(new Long(2))); + assertTrue(weakHashtable.containsKey(new Long(3))); + assertFalse(weakHashtable.containsKey(new Long(100))); + assertFalse(weakHashtable.containsKey(new Long(200))); + assertFalse(weakHashtable.containsKey(new Long(300))); + assertFalse(weakHashtable.containsKey(new Long(400))); + } + + /** Tests public boolean containsValue(Object value) */ + public void testContainsValue() throws Exception { + assertFalse(weakHashtable.containsValue(new Long(1))); + assertFalse(weakHashtable.containsValue(new Long(2))); + assertFalse(weakHashtable.containsValue(new Long(3))); + assertTrue(weakHashtable.containsValue(new Long(100))); + assertTrue(weakHashtable.containsValue(new Long(200))); + assertTrue(weakHashtable.containsValue(new Long(300))); + assertFalse(weakHashtable.containsValue(new Long(400))); + } + + /** Tests public Enumeration elements() */ + public void testElements() throws Exception { + ArrayList elements = new ArrayList(); + for (Enumeration e = weakHashtable.elements(); e.hasMoreElements();) { + elements.add(e.nextElement()); + } + assertEquals(3, elements.size()); + assertTrue(elements.contains(valueOne)); + assertTrue(elements.contains(valueTwo)); + assertTrue(elements.contains(valueThree)); + } + + /** Tests public Set entrySet() */ + public void testEntrySet() throws Exception { + Set entrySet = weakHashtable.entrySet(); + for (Iterator it = entrySet.iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Object key = entry.getKey(); + if (keyOne.equals(key)) { + assertEquals(valueOne, entry.getValue()); + } else if (keyTwo.equals(key)) { + assertEquals(valueTwo, entry.getValue()); + } else if (keyThree.equals(key)) { + assertEquals(valueThree, entry.getValue()); + } else { + fail("Unexpected key"); + } + } + } + + /** Tests public Object get(Object key) */ + public void testGet() throws Exception { + assertEquals(valueOne, weakHashtable.get(keyOne)); + assertEquals(valueTwo, weakHashtable.get(keyTwo)); + assertEquals(valueThree, weakHashtable.get(keyThree)); + assertNull(weakHashtable.get(new Long(50))); + } + + /** Tests public Enumeration keys() */ + public void testKeys() throws Exception { + ArrayList keys = new ArrayList(); + for (Enumeration e = weakHashtable.keys(); e.hasMoreElements();) { + keys.add(e.nextElement()); + } + assertEquals(3, keys.size()); + assertTrue(keys.contains(keyOne)); + assertTrue(keys.contains(keyTwo)); + assertTrue(keys.contains(keyThree)); + } + + /** Tests public Set keySet() */ + public void testKeySet() throws Exception { + Set keySet = weakHashtable.keySet(); + assertEquals(3, keySet.size()); + assertTrue(keySet.contains(keyOne)); + assertTrue(keySet.contains(keyTwo)); + assertTrue(keySet.contains(keyThree)); + } + + /** Tests public Object put(Object key, Object value) */ + public void testPut() throws Exception { + Long anotherKey = new Long(2004); + weakHashtable.put(anotherKey, new Long(1066)); + + assertEquals(new Long(1066), weakHashtable.get(anotherKey)); + + // Test compliance with the hashtable API re nulls + Exception caught = null; + try { + weakHashtable.put(null, new Object()); + } + catch (Exception e) { + caught = e; + } + assertNotNull("did not throw an exception adding a null key", caught); + caught = null; + try { + weakHashtable.put(new Object(), null); + } + catch (Exception e) { + caught = e; + } + assertNotNull("did not throw an exception adding a null value", caught); + } + + /** Tests public void putAll(Map t) */ + public void testPutAll() throws Exception { + Map newValues = new HashMap(); + Long newKey = new Long(1066); + Long newValue = new Long(1415); + newValues.put(newKey, newValue); + Long anotherNewKey = new Long(1645); + Long anotherNewValue = new Long(1815); + newValues.put(anotherNewKey, anotherNewValue); + weakHashtable.putAll(newValues); + + assertEquals(5, weakHashtable.size()); + assertEquals(newValue, weakHashtable.get(newKey)); + assertEquals(anotherNewValue, weakHashtable.get(anotherNewKey)); + } + + /** Tests public Object remove(Object key) */ + public void testRemove() throws Exception { + weakHashtable.remove(keyOne); + assertEquals(2, weakHashtable.size()); + assertNull(weakHashtable.get(keyOne)); + } + + /** Tests public Collection values() */ + public void testValues() throws Exception { + Collection values = weakHashtable.values(); + assertEquals(3, values.size()); + assertTrue(values.contains(valueOne)); + assertTrue(values.contains(valueTwo)); + assertTrue(values.contains(valueThree)); + } + + /** + * Disabled this test as it makes wrong assumptions wrt the GC. + * This test especially fails with: + * + * Java(TM) SE Runtime Environment (build pxi3260sr12-20121025_01(SR12)) + * IBM J9 VM (build 2.4, JRE 1.6.0 IBM J9 2.4 Linux x86-32 jvmxi3260sr12-20121024_1 + */ + public void xxxIgnoretestRelease() throws Exception { + assertNotNull(weakHashtable.get(new Long(1))); + ReferenceQueue testQueue = new ReferenceQueue(); + WeakReference weakKeyOne = new WeakReference(keyOne, testQueue); + + // lose our references + keyOne = null; + keyTwo = null; + keyThree = null; + valueOne = null; + valueTwo = null; + valueThree = null; + + int iterations = 0; + int bytz = 2; + while(true) { + System.gc(); + if(iterations++ > MAX_GC_ITERATIONS){ + fail("Max iterations reached before resource released."); + } + + if(weakHashtable.get(new Long(1)) == null) { + break; + + } else { + // create garbage: + byte[] b = new byte[bytz]; + bytz = bytz * 2; + } + } + + // some JVMs seem to take a little time to put references on + // the reference queue once the reference has been collected + // need to think about whether this is enough to justify + // stepping through the collection each time... + while(testQueue.poll() == null) {} + + // Test that the released objects are not taking space in the table + assertEquals("underlying table not emptied", 0, weakHashtable.size()); + } + + public static class StupidThread extends Thread { + + public StupidThread(String name) { + super(name); + } + + public void run() { + for (int i = 0; i < RUN_LOOPS; i++) { + hashtable.put("key" + ":" + i%10, Boolean.TRUE); + if(i%50 == 0) { + yield(); + } + } + } + } + + public void testLOGGING_119() throws Exception { + Thread [] t = new Thread[THREAD_COUNT]; + for (int j=1; j <= OUTER_LOOP; j++) { + hashtable = new WeakHashtable(); + for (int i = 0; i < t.length; i++) { + t[i] = new StupidThread("Thread:" + i); + t[i].setDaemon(true); // Otherwise we cannot exit + t[i].start(); + } + for (int i = 0; i < t.length; i++) { + t[i].join(WAIT_FOR_THREAD_COMPLETION); + if (t[i].isAlive()) { + break; // at least one thread is stuck + } + } + int active=0; + for (int i = 0; i < t.length; i++) { + if (t[i].isAlive()) { + active++; + } + } + if (active > 0) { + fail("Attempt: " + j + " Stuck threads: " + active); + } + } + } +} diff --git a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigAPITestCase.java b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigAPITestCase.java new file mode 100644 index 0000000..afa04d6 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigAPITestCase.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.jdk14; + +import junit.framework.Test; + +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + + +/** + * TestCase for Jdk14 logging when the commons-logging-api jar file is in + * the parent classpath and commons-logging.jar is in the child. + */ + +public class CustomConfigAPITestCase extends CustomConfigTestCase { + + public CustomConfigAPITestCase(String name) { + super(name); + } + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + PathableClassLoader parent = new PathableClassLoader(null); + parent.useExplicitLoader("junit.", Test.class.getClassLoader()); + + // the TestHandler class must be accessable from the System classloader + // in order for java.util.logging.LogManager.readConfiguration to + // be able to instantiate it. And this test case must see the same + // class in order to be able to access its data. Yes this is ugly + // but the whole jdk14 API is a ******* mess anyway. + ClassLoader scl = ClassLoader.getSystemClassLoader(); + loadTestHandler(HANDLER_NAME, scl); + parent.useExplicitLoader(HANDLER_NAME, scl); + parent.addLogicalLib("commons-logging-api"); + + PathableClassLoader child = new PathableClassLoader(parent); + child.addLogicalLib("testclasses"); + child.addLogicalLib("commons-logging"); + + Class testClass = child.loadClass(CustomConfigAPITestCase.class.getName()); + return new PathableTestSuite(testClass, child); + } +} diff --git a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigFullTestCase.java b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigFullTestCase.java new file mode 100644 index 0000000..86a6232 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigFullTestCase.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.jdk14; + + +import junit.framework.Test; + +import org.apache.commons.logging.PathableTestSuite; +import org.apache.commons.logging.PathableClassLoader; + + +/** + * TestCase for Jdk14 logging when the commons-logging jar file is in + * the parent classpath. + */ + +public class CustomConfigFullTestCase extends CustomConfigTestCase { + + + public CustomConfigFullTestCase(String name) { + super(name); + } + + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + PathableClassLoader parent = new PathableClassLoader(null); + parent.useExplicitLoader("junit.", Test.class.getClassLoader()); + + // the TestHandler class must be accessable from the System classloader + // in order for java.util.logging.LogManager.readConfiguration to + // be able to instantiate it. And this test case must see the same + // class in order to be able to access its data. Yes this is ugly + // but the whole jdk14 API is a ******* mess anyway. + ClassLoader scl = ClassLoader.getSystemClassLoader(); + loadTestHandler(HANDLER_NAME, scl); + parent.useExplicitLoader(HANDLER_NAME, scl); + parent.addLogicalLib("commons-logging"); + + PathableClassLoader child = new PathableClassLoader(parent); + child.addLogicalLib("testclasses"); + + Class testClass = child.loadClass(CustomConfigFullTestCase.class.getName()); + return new PathableTestSuite(testClass, child); + } +} diff --git a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java new file mode 100644 index 0000000..d970d76 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java @@ -0,0 +1,395 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.jdk14; + + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.Iterator; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import junit.framework.Test; + +import org.apache.commons.logging.DummyException; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + + +/** + *

TestCase for JDK 1.4 logging when running on a JDK 1.4 system with + * custom configuration, so that JDK 1.4 should be selected and an appropriate + * logger configured per the configuration properties.

+ * + * @author Craig R. McClanahan + * @version $Revision: 1448063 $ $Date: 2013-02-20 11:01:41 +0100 (Wed, 20 Feb 2013) $ + */ + +public class CustomConfigTestCase extends DefaultConfigTestCase { + + protected static final String HANDLER_NAME = "org.apache.commons.logging.jdk14.TestHandler"; + + // ----------------------------------------------------------- Constructors + + + /** + *

Construct a new instance of this test case.

+ * + * @param name Name of the test case + */ + public CustomConfigTestCase(String name) { + super(name); + } + + + // ----------------------------------------------------- Instance Variables + + + /** + *

The customized Handler we will be using.

+ */ + protected TestHandler handler = null; + + + /** + *

The underlying Handlers we will be using.

+ */ + protected Handler handlers[] = null; + + + /** + *

The underlying Logger we will be using.

+ */ + protected Logger logger = null; + + + /** + *

The underlying LogManager we will be using.

+ */ + protected LogManager manager = null; + + + /** + *

The message levels that should have been logged.

+ */ + protected Level testLevels[] = + { Level.FINE, Level.INFO, Level.WARNING, Level.SEVERE, Level.SEVERE }; + + + /** + *

The message strings that should have been logged.

+ */ + protected String testMessages[] = + { "debug", "info", "warn", "error", "fatal" }; + + + // ------------------------------------------- JUnit Infrastructure Methods + + + /** + * Given the name of a class that is somewhere in the classpath of the provided + * classloader, return the contents of the corresponding .class file. + */ + protected static byte[] readClass(String name, ClassLoader srcCL) throws Exception { + String resName = name.replace('.', '/') + ".class"; + System.err.println("Trying to load resource [" + resName + "]"); + InputStream is = srcCL.getResourceAsStream(resName); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + System.err.println("Reading resource [" + resName + "]"); + byte[] buf = new byte[1000]; + for(;;) { + int read = is.read(buf); + if (read <= 0) { + break; + } + baos.write(buf, 0, read); + } + is.close(); + return baos.toByteArray(); + } + + /** + * Make a class available in the system classloader even when its classfile is + * not present in the classpath configured for that classloader. This only + * works for classes for which all dependencies are already loaded in + * that classloader. + */ + protected static void loadTestHandler(String className, ClassLoader targetCL) { + try { + targetCL.loadClass(className); + // fail("Class already in target classloader"); + return; + } catch(ClassNotFoundException ex) { + // ok, go ahead and load it + } + + try { + ClassLoader srcCL = CustomConfigAPITestCase.class.getClassLoader(); + byte[] classData = readClass(className, srcCL); + + Class[] params = new Class[] { String.class, classData.getClass(), Integer.TYPE, Integer.TYPE }; + Method m = ClassLoader.class.getDeclaredMethod("defineClass", params); + + Object[] args = new Object[4]; + args[0] = className; + args[1] = classData; + args[2] = new Integer(0); + args[3] = new Integer(classData.length); + m.setAccessible(true); + m.invoke(targetCL, args); + } catch(Exception e) { + e.printStackTrace(); + fail("Unable to load class " + className); + } + } + + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + setUpManager + ("org/apache/commons/logging/jdk14/CustomConfig.properties"); + setUpLogger(this.getClass().getName()); + setUpHandlers(); + setUpFactory(); + setUpLog(this.getClass().getName()); + } + + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + PathableClassLoader cl = new PathableClassLoader(null); + cl.useExplicitLoader("junit.", Test.class.getClassLoader()); + + // the TestHandler class must be accessable from the System classloader + // in order for java.util.logging.LogManager.readConfiguration to + // be able to instantiate it. And this test case must see the same + // class in order to be able to access its data. Yes this is ugly + // but the whole jdk14 API is a ******* mess anyway. + ClassLoader scl = ClassLoader.getSystemClassLoader(); + loadTestHandler(HANDLER_NAME, scl); + cl.useExplicitLoader(HANDLER_NAME, scl); + cl.addLogicalLib("commons-logging"); + cl.addLogicalLib("testclasses"); + + Class testClass = cl.loadClass(CustomConfigTestCase.class.getName()); + return new PathableTestSuite(testClass, cl); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + super.tearDown(); + handlers = null; + logger = null; + manager = null; + } + + + // ----------------------------------------------------------- Test Methods + + + // Test logging message strings with exceptions + public void testExceptionMessages() throws Exception { + + logExceptionMessages(); + checkLogRecords(true); + + } + + + // Test logging plain message strings + public void testPlainMessages() throws Exception { + + logPlainMessages(); + checkLogRecords(false); + + } + + + // Test pristine Handlers instances + public void testPristineHandlers() { + + assertNotNull(handlers); + assertEquals(1, handlers.length); + assertTrue(handlers[0] instanceof TestHandler); + assertNotNull(handler); + + } + + + // Test pristine Logger instance + public void testPristineLogger() { + + assertNotNull("Logger exists", logger); + assertEquals("Logger name", this.getClass().getName(), logger.getName()); + + // Assert which logging levels have been enabled + assertTrue(logger.isLoggable(Level.SEVERE)); + assertTrue(logger.isLoggable(Level.WARNING)); + assertTrue(logger.isLoggable(Level.INFO)); + assertTrue(logger.isLoggable(Level.CONFIG)); + assertTrue(logger.isLoggable(Level.FINE)); + assertTrue(!logger.isLoggable(Level.FINER)); + assertTrue(!logger.isLoggable(Level.FINEST)); + + } + + + // Test Serializability of Log instance + public void testSerializable() throws Exception { + + super.testSerializable(); + testExceptionMessages(); + + } + + + // -------------------------------------------------------- Support Methods + + + // Check the log instance + protected void checkLog() { + + assertNotNull("Log exists", log); + assertEquals("Log class", + "org.apache.commons.logging.impl.Jdk14Logger", + log.getClass().getName()); + + // Assert which logging levels have been enabled + assertTrue(log.isFatalEnabled()); + assertTrue(log.isErrorEnabled()); + assertTrue(log.isWarnEnabled()); + assertTrue(log.isInfoEnabled()); + assertTrue(log.isDebugEnabled()); + assertTrue(!log.isTraceEnabled()); + + } + + + // Check the recorded messages + protected void checkLogRecords(boolean thrown) { + Iterator records = handler.records(); + for (int i = 0; i < testMessages.length; i++) { + assertTrue(records.hasNext()); + LogRecord record = (LogRecord) records.next(); + assertEquals("LogRecord level", + testLevels[i], record.getLevel()); + assertEquals("LogRecord message", + testMessages[i], record.getMessage()); + assertTrue("LogRecord class", + record.getSourceClassName().startsWith( + "org.apache.commons.logging.jdk14.CustomConfig")); + if (thrown) { + assertEquals("LogRecord method", + "logExceptionMessages", + record.getSourceMethodName()); + } else { + assertEquals("LogRecord method", + "logPlainMessages", + record.getSourceMethodName()); + } + if (thrown) { + assertNotNull("LogRecord thrown", record.getThrown()); + assertTrue("LogRecord thrown type", + record.getThrown() instanceof DummyException); + } else { + assertNull("LogRecord thrown", + record.getThrown()); + } + } + assertTrue(!records.hasNext()); + handler.flush(); + } + + + // Log the messages with exceptions + protected void logExceptionMessages() { + Throwable t = new DummyException(); + log.trace("trace", t); // Should not actually get logged + log.debug("debug", t); + log.info("info", t); + log.warn("warn", t); + log.error("error", t); + log.fatal("fatal", t); + } + + + // Log the plain messages + protected void logPlainMessages() { + log.trace("trace"); // Should not actually get logged + log.debug("debug"); + log.info("info"); + log.warn("warn"); + log.error("error"); + log.fatal("fatal"); + } + + + // Set up handlers instance + protected void setUpHandlers() throws Exception { + Logger parent = logger; + while (parent.getParent() != null) { + parent = parent.getParent(); + } + handlers = parent.getHandlers(); + + // The CustomConfig.properties file explicitly defines one handler class + // to be attached to the root logger, so if it isn't there then + // something is badly wrong... + // + // Yes this testing is also done in testPristineHandlers but + // unfortunately: + // * we need to set up the handlers variable here, + // * we don't want that to be set up incorrectly, as that can + // produce weird error messages in other tests, and + // * we can't rely on testPristineHandlers being the first + // test to run. + // so we need to test things here too. + assertNotNull("No Handlers defined for JDK14 logging", handlers); + assertEquals("Unexpected number of handlers for JDK14 logging", 1, handlers.length); + assertNotNull("Handler is null", handlers[0]); + assertTrue("Handler not of expected type", handlers[0] instanceof TestHandler); + handler = (TestHandler) handlers[0]; + } + + + // Set up logger instance + protected void setUpLogger(String name) throws Exception { + logger = Logger.getLogger(name); + } + + + // Set up LogManager instance + protected void setUpManager(String config) throws Exception { + manager = LogManager.getLogManager(); + InputStream is = + this.getClass().getClassLoader().getResourceAsStream(config); + manager.readConfiguration(is); + is.close(); + } + + +} diff --git a/src/test/java/org/apache/commons/logging/jdk14/DefaultConfigTestCase.java b/src/test/java/org/apache/commons/logging/jdk14/DefaultConfigTestCase.java new file mode 100644 index 0000000..29f3992 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/jdk14/DefaultConfigTestCase.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.jdk14; + + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + + +/** + *

TestCase for JDK 1.4 logging when running on a JDK 1.4 system with + * zero configuration, and with Log4J not present (so JDK 1.4 logging + * should be automatically configured.

+ * + * @author Craig R. McClanahan + * @version $Revision: 1432587 $ $Date: 2013-01-13 12:11:32 +0100 (Sun, 13 Jan 2013) $ + */ + +public class DefaultConfigTestCase extends TestCase { + + + // ----------------------------------------------------------- Constructors + + + /** + *

Construct a new instance of this test case.

+ * + * @param name Name of the test case + */ + public DefaultConfigTestCase(String name) { + super(name); + } + + + // ----------------------------------------------------- Instance Variables + + + /** + *

The {@link LogFactory} implementation we have selected.

+ */ + protected LogFactory factory = null; + + + /** + *

The {@link Log} implementation we have selected.

+ */ + protected Log log = null; + + + // ------------------------------------------- JUnit Infrastructure Methods + + + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + setUpFactory(); + setUpLog("TestLogger"); + } + + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + PathableClassLoader loader = new PathableClassLoader(null); + loader.useExplicitLoader("junit.", Test.class.getClassLoader()); + loader.addLogicalLib("testclasses"); + loader.addLogicalLib("commons-logging"); + + Class testClass = loader.loadClass(DefaultConfigTestCase.class.getName()); + return new PathableTestSuite(testClass, loader); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + log = null; + factory = null; + LogFactory.releaseAll(); + } + + + // ----------------------------------------------------------- Test Methods + + + // Test pristine Log instance + public void testPristineLog() { + + checkLog(); + + } + + + // Test pristine LogFactory instance + public void testPristineFactory() { + + assertNotNull("LogFactory exists", factory); + assertEquals("LogFactory class", + "org.apache.commons.logging.impl.LogFactoryImpl", + factory.getClass().getName()); + + String names[] = factory.getAttributeNames(); + assertNotNull("Names exists", names); + assertEquals("Names empty", 0, names.length); + + } + + + // Test Serializability of Log instance + public void testSerializable() throws Exception { + + // Serialize and deserialize the instance + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(log); + oos.close(); + ByteArrayInputStream bais = + new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bais); + log = (Log) ois.readObject(); + ois.close(); + + // Check the characteristics of the resulting object + checkLog(); + + } + + + // -------------------------------------------------------- Support Methods + + + + // Check the log instance + protected void checkLog() { + + assertNotNull("Log exists", log); + assertEquals("Log class", + "org.apache.commons.logging.impl.Jdk14Logger", + log.getClass().getName()); + + // Can we call level checkers with no exceptions? + log.isDebugEnabled(); + log.isErrorEnabled(); + log.isFatalEnabled(); + log.isInfoEnabled(); + log.isTraceEnabled(); + log.isWarnEnabled(); + + } + + + // Set up factory instance + protected void setUpFactory() throws Exception { + factory = LogFactory.getFactory(); + } + + + // Set up log instance + protected void setUpLog(String name) throws Exception { + log = LogFactory.getLog(name); + } + + +} diff --git a/src/test/java/org/apache/commons/logging/jdk14/TestHandler.java b/src/test/java/org/apache/commons/logging/jdk14/TestHandler.java new file mode 100644 index 0000000..d60e53a --- /dev/null +++ b/src/test/java/org/apache/commons/logging/jdk14/TestHandler.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.jdk14; + + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + + +/** + *

Test implementation of java.util.logging.Handler.

+ * + * @author Craig R. McClanahan + * @version $Revision: 1432587 $ $Date: 2013-01-13 12:11:32 +0100 (Sun, 13 Jan 2013) $ + */ + +public class TestHandler extends Handler { + + + + // ----------------------------------------------------- Instance Variables + + + // The set of logged records for this handler + private final List records = new ArrayList(); + + + // --------------------------------------------------------- Public Methods + + + public Iterator records() { + return records.iterator(); + } + + + // -------------------------------------------------------- Handler Methods + + + public void close() { + } + + + public void flush() { + records.clear(); + } + + + public void publish(LogRecord record) { + records.add(record); + } + + +} diff --git a/src/test/java/org/apache/commons/logging/log4j/StandardTests.java b/src/test/java/org/apache/commons/logging/log4j/StandardTests.java new file mode 100644 index 0000000..2f86f74 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/log4j/StandardTests.java @@ -0,0 +1,216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.log4j; + + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +import org.apache.commons.logging.DummyException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Abstract set of tests that can be executed with various classpaths set. + *

+ * The tests verify that when running on a system with Log4J present, + * Log4J is selected and that the logger basically works. + */ + +public abstract class StandardTests extends TestCase { + + /** + * Simple structure to store information about messages that actually get + * logged by the underlying logging library. + */ + public static class LogEvent { + public String msg; + public String level; + public Throwable throwable; + } + + // ------------------------------------------------------------------- + // JUnit Infrastructure Methods + // ------------------------------------------------------------------- + + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + LogFactory.releaseAll(); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + LogFactory.releaseAll(); + } + + // ----------------------------------------------------------- + // abstract methods + // ----------------------------------------------------------- + + /** + * Modify log4j's setup so that all messages actually logged get redirected + * into the specified list. + *

+ * This method also sets the logging level to INFO so that we + * can test whether messages are getting properly filtered. + */ + public abstract void setUpTestAppender(List logEvents) throws Exception; + + // ----------------------------------------------------------- Test Methods + + /** + * Test that a LogFactory gets created as expected. + */ + public void testCreateFactory() { + LogFactory factory = LogFactory.getFactory(); + assertNotNull("LogFactory exists", factory); + assertEquals("LogFactory class", + "org.apache.commons.logging.impl.LogFactoryImpl", + factory.getClass().getName()); + + String names[] = factory.getAttributeNames(); + assertNotNull("Names exists", names); + assertEquals("Names empty", 0, names.length); + } + + /** + * Verify that we can log messages without exceptions. + */ + public void testPlainMessages() throws Exception { + List logEvents = new ArrayList(); + setUpTestAppender(logEvents); + Log log = LogFactory.getLog("test-category"); + logPlainMessages(log); + checkLoggingEvents(logEvents, false); + } + + /** + * Verify that we can log exception messages. + */ + public void testExceptionMessages() throws Exception { + List logEvents = new ArrayList(); + setUpTestAppender(logEvents); + Log log = LogFactory.getLog("test-category"); + logExceptionMessages(log); + checkLoggingEvents(logEvents, true); + } + + /** + * Test Serializability of Log instance + */ + public void testSerializable() throws Exception { + List logEvents = new ArrayList(); + setUpTestAppender(logEvents); + Log log = LogFactory.getLog("test-category"); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(log); + oos.close(); + ByteArrayInputStream bais = + new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bais); + Log newLog = (Log) ois.readObject(); + ois.close(); + + // Check the characteristics of the resulting object + logExceptionMessages(newLog); + checkLoggingEvents(logEvents, true); + } + + // -------------------------------------------------------- Support Methods + + /** + * Verify that the TestAppender has received the expected + * number of messages. This assumes that: + *

    + *
  • setUpTestAppender has been called + *
  • logPlainMessages or logExceptionMessages has been + * called to log a known number of messages at known levels. + *
+ * + * @param logEvents is the list of log events received. + * + * @param thrown False if logPlainMessages was called + * (ie the TestAppender is expected to have received + * logevents with no associated exception info). True if + * logExceptionMessages was called. + */ + private void checkLoggingEvents(List logEvents, boolean thrown) { + LogEvent ev; + + assertEquals("Unexpected number of log events", 4, logEvents.size()); + + ev = (LogEvent) logEvents.get(0); + assertEquals("Info message expected", "info", ev.msg); + assertEquals("Info level expected", "INFO", ev.level); + assertEquals("Exception data incorrect", ev.throwable!=null, thrown); + + ev = (LogEvent) logEvents.get(1); + assertEquals("Warn message expected", "warn", ev.msg); + assertEquals("Warn level expected", "WARN", ev.level); + assertEquals("Exception data incorrect", ev.throwable!=null, thrown); + + ev = (LogEvent) logEvents.get(2); + assertEquals("Error message expected", "error", ev.msg); + assertEquals("Error level expected", "ERROR", ev.level); + assertEquals("Exception data incorrect", ev.throwable!=null, thrown); + + ev = (LogEvent) logEvents.get(3); + assertEquals("Fatal message expected", "fatal", ev.msg); + assertEquals("Fatal level expected", "FATAL", ev.level); + assertEquals("Exception data incorrect", ev.throwable!=null, thrown); + } + + + /** + * Log plain messages. + */ + private void logPlainMessages(Log log) { + log.trace("trace"); // Should not actually get logged + log.debug("debug"); // Should not actually get logged + log.info("info"); + log.warn("warn"); + log.error("error"); + log.fatal("fatal"); + } + + /** + * Log messages with exceptions + */ + private void logExceptionMessages(Log log) { + Throwable t = new DummyException(); + log.trace("trace", t); // Should not actually get logged + log.debug("debug", t); // Should not actually get logged + log.info("info", t); + log.warn("warn", t); + log.error("error", t); + log.fatal("fatal", t); + } +} diff --git a/src/test/java/org/apache/commons/logging/log4j/log4j12/ApiClasspathStandardTestCase.java b/src/test/java/org/apache/commons/logging/log4j/log4j12/ApiClasspathStandardTestCase.java new file mode 100644 index 0000000..1b7bace --- /dev/null +++ b/src/test/java/org/apache/commons/logging/log4j/log4j12/ApiClasspathStandardTestCase.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.log4j.log4j12; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + + +/** + * Tests for Log4J logging that emulate a webapp running within + * a container where the commons-logging-api jar file is in + * the parent classpath and commons-logging.jar is in the child. + */ + +public class ApiClasspathStandardTestCase extends TestCase { + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + PathableClassLoader parent = new PathableClassLoader(null); + parent.useExplicitLoader("junit.", Test.class.getClassLoader()); + parent.addLogicalLib("commons-logging-api"); + + PathableClassLoader child = new PathableClassLoader(parent); + child.addLogicalLib("log4j12"); + child.addLogicalLib("commons-logging"); + child.addLogicalLib("testclasses"); + + Class testClass = child.loadClass( + "org.apache.commons.logging.log4j.log4j12.Log4j12StandardTests"); + return new PathableTestSuite(testClass, child); + } +} diff --git a/src/test/java/org/apache/commons/logging/log4j/log4j12/AppClasspathStandardTestCase.java b/src/test/java/org/apache/commons/logging/log4j/log4j12/AppClasspathStandardTestCase.java new file mode 100644 index 0000000..71da059 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/log4j/log4j12/AppClasspathStandardTestCase.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.log4j.log4j12; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + +/** + * Tests for Log4J logging when there is only one classloader and everything + * is in it, as would be the situation for a standalone application. + */ + +public class AppClasspathStandardTestCase extends TestCase { + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + PathableClassLoader loader = new PathableClassLoader(null); + loader.useExplicitLoader("junit.", Test.class.getClassLoader()); + loader.addLogicalLib("testclasses"); + loader.addLogicalLib("log4j12"); + loader.addLogicalLib("commons-logging"); + + Class testClass = loader.loadClass( + "org.apache.commons.logging.log4j.log4j12.Log4j12StandardTests"); + return new PathableTestSuite(testClass, loader); + } +} diff --git a/src/test/java/org/apache/commons/logging/log4j/log4j12/ChildClasspathStandardTestCase.java b/src/test/java/org/apache/commons/logging/log4j/log4j12/ChildClasspathStandardTestCase.java new file mode 100644 index 0000000..2620dbd --- /dev/null +++ b/src/test/java/org/apache/commons/logging/log4j/log4j12/ChildClasspathStandardTestCase.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.log4j.log4j12; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + + +/** + * Tests for Log4J logging that emulate a webapp running within + * a container where all the necessary libs are in the child. + */ + +public class ChildClasspathStandardTestCase extends TestCase { + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + PathableClassLoader parent = new PathableClassLoader(null); + parent.useExplicitLoader("junit.", Test.class.getClassLoader()); + + PathableClassLoader child = new PathableClassLoader(parent); + child.addLogicalLib("testclasses"); + child.addLogicalLib("log4j12"); + child.addLogicalLib("commons-logging"); + + Class testClass = child.loadClass( + "org.apache.commons.logging.log4j.log4j12.Log4j12StandardTests"); + return new PathableTestSuite(testClass, child); + } +} diff --git a/src/test/java/org/apache/commons/logging/log4j/log4j12/Log4j12StandardTests.java b/src/test/java/org/apache/commons/logging/log4j/log4j12/Log4j12StandardTests.java new file mode 100644 index 0000000..a03e74a --- /dev/null +++ b/src/test/java/org/apache/commons/logging/log4j/log4j12/Log4j12StandardTests.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.log4j.log4j12; + +import java.util.List; + +import org.apache.commons.logging.log4j.StandardTests; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +/** + * A concrete class that runs the standard tests, and is compiled + * specifically against log4j12. The parent class can't call any + * log4j methods at all as that would mean it has to be compiled + * against a particular version of log4j. + */ + +public class Log4j12StandardTests extends StandardTests { + + public void setUpTestAppender(List logEvents) { + TestAppender appender = new TestAppender(logEvents); + Logger rootLogger = Logger.getRootLogger(); + rootLogger.removeAllAppenders(); + rootLogger.addAppender(appender); + rootLogger.setLevel(Level.INFO); + } +} diff --git a/src/test/java/org/apache/commons/logging/log4j/log4j12/ParentClasspathStandardTestCase.java b/src/test/java/org/apache/commons/logging/log4j/log4j12/ParentClasspathStandardTestCase.java new file mode 100644 index 0000000..3654d92 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/log4j/log4j12/ParentClasspathStandardTestCase.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.log4j.log4j12; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + +/** + * Tests for Log4J logging that emulate a webapp running within + * a container where all the necessary libs are in the parent. + */ + +public class ParentClasspathStandardTestCase extends TestCase { + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + PathableClassLoader parent = new PathableClassLoader(null); + parent.useExplicitLoader("junit.", Test.class.getClassLoader()); + parent.addLogicalLib("commons-logging"); + parent.addLogicalLib("log4j12"); + + PathableClassLoader child = new PathableClassLoader(parent); + child.addLogicalLib("testclasses"); + + Class testClass = child.loadClass( + "org.apache.commons.logging.log4j.log4j12.Log4j12StandardTests"); + return new PathableTestSuite(testClass, child); + } +} diff --git a/src/test/java/org/apache/commons/logging/log4j/log4j12/TestAppender.java b/src/test/java/org/apache/commons/logging/log4j/log4j12/TestAppender.java new file mode 100644 index 0000000..5ad71c8 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/log4j/log4j12/TestAppender.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.log4j.log4j12; + + +import java.util.List; + +import org.apache.commons.logging.log4j.StandardTests; +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.spi.LoggingEvent; + +/** + * A custom implementation of org.apache.log4j.Appender which + * converts the log4j-specific log event record into a representation that + * doesn't have a dependency on log4j and stores that new representation into + * an external list. + */ + +public class TestAppender extends AppenderSkeleton { + + /** + * Constructor. + */ + public TestAppender(List logEvents) { + events = logEvents; + } + + // ----------------------------------------------------- Instance Variables + + + // The set of logged events for this appender + private final List events; + + + // ------------------------------------------------------- Appender Methods + + protected void append(LoggingEvent event) { + StandardTests.LogEvent lev = new StandardTests.LogEvent(); + + lev.level = event.getLevel().toString(); + + if (event.getMessage() == null) { + lev.msg = null; + } else { + lev.msg = event.getMessage().toString(); + } + + if (event.getThrowableInformation() == null) { + lev.throwable = null; + } else { + lev.throwable = event.getThrowableInformation().getThrowable(); + } + + events.add(lev); + } + + + public void close() { + } + + + public boolean requiresLayout() { + return false; + } + + +} diff --git a/src/test/java/org/apache/commons/logging/logkit/StandardTestCase.java b/src/test/java/org/apache/commons/logging/logkit/StandardTestCase.java new file mode 100644 index 0000000..4c1fbd8 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/logkit/StandardTestCase.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.logkit; + + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import junit.framework.Test; + +import org.apache.commons.logging.AbstractLogTest; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; +import org.apache.commons.logging.impl.LogKitLogger; + +/** + * Basic tests for Avalon LogKit logger adapter. + */ + +public class StandardTestCase extends AbstractLogTest { + + + // ----------------------------------------------------- Instance Variables + + + /** + *

The {@link LogFactory} implementation we have selected.

+ */ + protected LogFactory factory = null; + + + /** + *

The {@link Log} implementation we have selected.

+ */ + protected Log log = null; + + + // ------------------------------------------- JUnit Infrastructure Methods + + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + Class thisClass = StandardTestCase.class; + + PathableClassLoader loader = new PathableClassLoader(null); + loader.useExplicitLoader("junit.", Test.class.getClassLoader()); + loader.addLogicalLib("testclasses"); + loader.addLogicalLib("commons-logging"); + loader.addLogicalLib("logkit"); + + Class testClass = loader.loadClass(thisClass.getName()); + return new PathableTestSuite(testClass, loader); + } + + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + LogFactory.releaseAll(); + + System.setProperty( + "org.apache.commons.logging.Log", + "org.apache.commons.logging.impl.LogKitLogger"); + + factory = LogFactory.getFactory(); + log = LogFactory.getLog("TestLogger"); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + log = null; + factory = null; + LogFactory.releaseAll(); + } + + // ----------------------------------------------------------- Test Methods + + /** + * Override the abstract method from the parent class so that the + * inherited tests can access the right Log object type. + */ + public Log getLogObject() + { + return new LogKitLogger(this.getClass().getName()); + } + + // Test pristine LogFactory instance + public void testPristineFactory() { + + assertNotNull("LogFactory exists", factory); + assertEquals("LogFactory class", + "org.apache.commons.logging.impl.LogFactoryImpl", + factory.getClass().getName()); + + String names[] = factory.getAttributeNames(); + assertNotNull("Names exists", names); + assertEquals("Names empty", 0, names.length); + } + + // Test pristine Log instance + public void testPristineLog() { + checkStandard(); + } + + // Test Serializability of standard instance + public void testSerializable() throws Exception { + checkStandard(); + + // Serialize and deserialize the instance + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(log); + oos.close(); + ByteArrayInputStream bais = + new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bais); + log = (Log) ois.readObject(); + ois.close(); + + checkStandard(); + } + + + // -------------------------------------------------------- Support Methods + + // Check the standard log instance + protected void checkStandard() { + + assertNotNull("Log exists", log); + assertEquals("Log class", + "org.apache.commons.logging.impl.LogKitLogger", + log.getClass().getName()); + + // Can we call level checkers with no exceptions? + // Note that by default *everything* is enabled for LogKit + assertTrue(log.isTraceEnabled()); + assertTrue(log.isDebugEnabled()); + assertTrue(log.isInfoEnabled()); + assertTrue(log.isWarnEnabled()); + assertTrue(log.isErrorEnabled()); + assertTrue(log.isFatalEnabled()); + } +} diff --git a/src/test/java/org/apache/commons/logging/noop/NoOpLogTestCase.java b/src/test/java/org/apache/commons/logging/noop/NoOpLogTestCase.java new file mode 100644 index 0000000..fa54c82 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/noop/NoOpLogTestCase.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.noop; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.impl.NoOpLog; +import org.apache.commons.logging.AbstractLogTest; + +/** + * Tests for NoOpLog logging adapter. + *

+ * This simply applies the tests defined in AbstractLogTest to this class. + */ +public class NoOpLogTestCase extends AbstractLogTest +{ + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + LogFactory.releaseAll(); + + System.setProperty( + "org.apache.commons.logging.Log", + "org.apache.commons.logging.impl.NoOpLog"); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + LogFactory.releaseAll(); + System.getProperties().remove("org.apache.commons.logging.Log"); + } + + /** + * Override the abstract method from the parent class so that the + * inherited tests can access the right Log object type. + */ + public Log getLogObject() + { + return new NoOpLog(this.getClass().getName()); + } + + // Test Serializability of standard instance + public void testSerializable() throws Exception { + Log log = LogFactory.getLog(this.getClass().getName()); + checkLog(log); + + // Serialize and deserialize the instance + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(log); + oos.close(); + ByteArrayInputStream bais = + new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bais); + log = (Log) ois.readObject(); + ois.close(); + + checkLog(log); + } + + + // -------------------------------------------------------- Support Methods + + private void checkLog(Log log) { + + assertNotNull("Log exists", log); + assertEquals("Log class", + "org.apache.commons.logging.impl.NoOpLog", + log.getClass().getName()); + + // Can we call level checkers with no exceptions? + // Note that *everything* is permanently disabled for NoOpLog + assertFalse(log.isTraceEnabled()); + assertFalse(log.isDebugEnabled()); + assertFalse(log.isInfoEnabled()); + assertFalse(log.isWarnEnabled()); + assertFalse(log.isErrorEnabled()); + assertFalse(log.isFatalEnabled()); + } +} diff --git a/src/test/java/org/apache/commons/logging/pathable/ChildFirstTestCase.java b/src/test/java/org/apache/commons/logging/pathable/ChildFirstTestCase.java new file mode 100644 index 0000000..1aeb12d --- /dev/null +++ b/src/test/java/org/apache/commons/logging/pathable/ChildFirstTestCase.java @@ -0,0 +1,317 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.pathable; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + +/** + * Tests for the PathableTestSuite and PathableClassLoader functionality, + * where lookup order for the PathableClassLoader is child-first. + *

+ * These tests assume: + *

    + *
  • junit is in system classpath + *
  • nothing else is in system classpath + *
+ */ + +public class ChildFirstTestCase extends TestCase { + + /** + * Set up a custom classloader hierarchy for this test case. + * The hierarchy is: + *
    + *
  • contextloader: child-first. + *
  • childloader: child-first, used to load test case. + *
  • parentloader: child-first, parent is the bootclassloader. + *
+ */ + public static Test suite() throws Exception { + Class thisClass = ChildFirstTestCase.class; + ClassLoader thisClassLoader = thisClass.getClassLoader(); + + // Make the parent a direct child of the bootloader to hide all + // other classes in the system classpath + PathableClassLoader parent = new PathableClassLoader(null); + parent.setParentFirst(false); + + // Make the junit classes visible as a special case, as junit + // won't be able to call this class at all without this. The + // junit classes must be visible from the classloader that loaded + // this class, so use that as the source for future access to classes + // from the junit package. + parent.useExplicitLoader("junit.", thisClassLoader); + + // Make the commons-logging.jar classes visible via the parent + parent.addLogicalLib("commons-logging"); + + // Create a child classloader to load the test case through + PathableClassLoader child = new PathableClassLoader(parent); + child.setParentFirst(false); + + // Obviously, the child classloader needs to have the test classes + // in its path! + child.addLogicalLib("testclasses"); + child.addLogicalLib("commons-logging-adapters"); + + // Create a third classloader to be the context classloader. + PathableClassLoader context = new PathableClassLoader(child); + context.setParentFirst(false); + + // reload this class via the child classloader + Class testClass = child.loadClass(thisClass.getName()); + + // and return our custom TestSuite class + return new PathableTestSuite(testClass, context); + } + + /** + * Utility method to return the set of all classloaders in the + * parent chain starting from the one that loaded the class for + * this object instance. + */ + private Set getAncestorCLs() { + Set s = new HashSet(); + ClassLoader cl = this.getClass().getClassLoader(); + while (cl != null) { + s.add(cl); + cl = cl.getParent(); + } + return s; + } + + /** + * Test that the classloader hierarchy is as expected, and that + * calling loadClass() on various classloaders works as expected. + * Note that for this test case, parent-first classloading is + * in effect. + */ + public void testPaths() throws Exception { + // the context classloader is not expected to be null + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + assertNotNull("Context classloader is null", contextLoader); + assertEquals("Context classloader has unexpected type", + PathableClassLoader.class.getName(), + contextLoader.getClass().getName()); + + // the classloader that loaded this class is obviously not null + ClassLoader thisLoader = this.getClass().getClassLoader(); + assertNotNull("thisLoader is null", thisLoader); + assertEquals("thisLoader has unexpected type", + PathableClassLoader.class.getName(), + thisLoader.getClass().getName()); + + // the suite method specified that the context classloader's parent + // is the loader that loaded this test case. + assertSame("Context classloader is not child of thisLoader", + thisLoader, contextLoader.getParent()); + + // thisLoader's parent should be available + ClassLoader parentLoader = thisLoader.getParent(); + assertNotNull("Parent classloader is null", parentLoader); + assertEquals("Parent classloader has unexpected type", + PathableClassLoader.class.getName(), + parentLoader.getClass().getName()); + + // parent should have a parent of null + assertNull("Parent classloader has non-null parent", parentLoader.getParent()); + + // getSystemClassloader is not a PathableClassLoader; it's of a + // built-in type. This also verifies that system classloader is none of + // (context, child, parent). + ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); + assertNotNull("System classloader is null", systemLoader); + assertFalse("System classloader has unexpected type", + PathableClassLoader.class.getName().equals( + systemLoader.getClass().getName())); + + // junit classes should be visible; their classloader is not + // in the hierarchy of parent classloaders for this class, + // though it is accessable due to trickery in the PathableClassLoader. + Class junitTest = contextLoader.loadClass("junit.framework.Test"); + Set ancestorCLs = getAncestorCLs(); + assertFalse("Junit not loaded by ancestor classloader", + ancestorCLs.contains(junitTest.getClassLoader())); + + // jcl api classes should be visible only via the parent + Class logClass = contextLoader.loadClass("org.apache.commons.logging.Log"); + assertSame("Log class not loaded via parent", + logClass.getClassLoader(), parentLoader); + + // jcl adapter classes should be visible via both parent and child. However + // as the classloaders are child-first we should see the child one. + Class log4jClass = contextLoader.loadClass("org.apache.commons.logging.impl.Log4JLogger"); + assertSame("Log4JLogger not loaded via child", + log4jClass.getClassLoader(), thisLoader); + + // test classes should be visible via the child only + Class testClass = contextLoader.loadClass("org.apache.commons.logging.PathableTestSuite"); + assertSame("PathableTestSuite not loaded via child", + testClass.getClassLoader(), thisLoader); + + // test loading of class that is not available + try { + Class noSuchClass = contextLoader.loadClass("no.such.class"); + fail("Class no.such.class is unexpectedly available"); + assertNotNull(noSuchClass); // silence warning about unused var + } catch(ClassNotFoundException ex) { + // ok + } + + // String class classloader is null + Class stringClass = contextLoader.loadClass("java.lang.String"); + assertNull("String class classloader is not null!", + stringClass.getClassLoader()); + } + + /** + * Test that the various flavours of ClassLoader.getResource work as expected. + */ + public void testResource() { + URL resource; + + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader childLoader = contextLoader.getParent(); + + // getResource where it doesn't exist + resource = childLoader.getResource("nosuchfile"); + assertNull("Non-null URL returned for invalid resource name", resource); + + // getResource where it is accessable only to parent classloader + resource = childLoader.getResource("org/apache/commons/logging/Log.class"); + assertNotNull("Unable to locate Log.class resource", resource); + + // getResource where it is accessable only to child classloader + resource = childLoader.getResource("org/apache/commons/logging/PathableTestSuite.class"); + assertNotNull("Unable to locate PathableTestSuite.class resource", resource); + + // getResource where it is accessable to both classloaders. The one visible + // to the child should be returned. The URL returned will be of form + // jar:file:/x/y.jar!path/to/resource. The filename part should include the jarname + // of form commons-logging-adapters-nnnn.jar, not commons-logging-nnnn.jar + resource = childLoader.getResource("org/apache/commons/logging/impl/Log4JLogger.class"); + assertNotNull("Unable to locate Log4JLogger.class resource", resource); + assertTrue("Incorrect source for Log4JLogger class", + resource.toString().indexOf("/commons-logging-adapters-1.") > 0); + } + + /** + * Test that the various flavours of ClassLoader.getResources work as expected. + */ + public void testResources() throws Exception { + Enumeration resources; + URL[] urls; + + // verify the classloader hierarchy + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader childLoader = contextLoader.getParent(); + ClassLoader parentLoader = childLoader.getParent(); + ClassLoader bootLoader = parentLoader.getParent(); + assertNull("Unexpected classloader hierarchy", bootLoader); + + // getResources where no instances exist + resources = childLoader.getResources("nosuchfile"); + urls = toURLArray(resources); + assertEquals("Non-null URL returned for invalid resource name", 0, urls.length); + + // getResources where the resource only exists in the parent + resources = childLoader.getResources("org/apache/commons/logging/Log.class"); + urls = toURLArray(resources); + assertEquals("Unexpected number of Log.class resources found", 1, urls.length); + + // getResources where the resource only exists in the child + resources = childLoader.getResources("org/apache/commons/logging/PathableTestSuite.class"); + urls = toURLArray(resources); + assertEquals("Unexpected number of PathableTestSuite.class resources found", 1, urls.length); + + // getResources where the resource exists in both. + // resources should be returned in order (child-resource, parent-resource). + // + // IMPORTANT: due to the fact that in java 1.4 and earlier method + // ClassLoader.getResources is final it isn't possible for PathableClassLoader + // to override this. So even when child-first is enabled the resource order + // is still (parent-resources, child-resources). This test verifies the expected + // behaviour - even though it's not the desired behaviour. + + resources = childLoader.getResources("org/apache/commons/logging/impl/Log4JLogger.class"); + urls = toURLArray(resources); + assertEquals("Unexpected number of Log4JLogger.class resources found", 2, urls.length); + + // There is no guarantee about the ordering of results returned from getResources + // To make this test portable across JVMs, sort the string to give them a known order + String[] urlsToStrings = new String[2]; + urlsToStrings[0] = urls[0].toString(); + urlsToStrings[1] = urls[1].toString(); + Arrays.sort(urlsToStrings); + assertTrue("Incorrect source for Log4JLogger class", + urlsToStrings[0].indexOf("/commons-logging-1.") > 0); + assertTrue("Incorrect source for Log4JLogger class", + urlsToStrings[1].indexOf("/commons-logging-adapters-1.") > 0); + } + + /** + * Utility method to convert an enumeration-of-URLs into an array of URLs. + */ + private static URL[] toURLArray(Enumeration e) { + ArrayList l = new ArrayList(); + while (e.hasMoreElements()) { + URL u = (URL) e.nextElement(); + l.add(u); + } + URL[] tmp = new URL[l.size()]; + return (URL[]) l.toArray(tmp); + } + + /** + * Test that getResourceAsStream works. + */ + public void testResourceAsStream() throws Exception { + java.io.InputStream is; + + // verify the classloader hierarchy + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader childLoader = contextLoader.getParent(); + ClassLoader parentLoader = childLoader.getParent(); + ClassLoader bootLoader = parentLoader.getParent(); + assertNull("Unexpected classloader hierarchy", bootLoader); + + // getResourceAsStream where no instances exist + is = childLoader.getResourceAsStream("nosuchfile"); + assertNull("Invalid resource returned non-null stream", is); + + // getResourceAsStream where resource does exist + is = childLoader.getResourceAsStream("org/apache/commons/logging/Log.class"); + assertNotNull("Null returned for valid resource", is); + is.close(); + + // It would be nice to test parent-first ordering here, but that would require + // having a resource with the same name in both the parent and child loaders, + // but with different contents. That's a little tricky to set up so we'll + // skip that for now. + } +} diff --git a/src/test/java/org/apache/commons/logging/pathable/GeneralTestCase.java b/src/test/java/org/apache/commons/logging/pathable/GeneralTestCase.java new file mode 100644 index 0000000..86fd475 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/pathable/GeneralTestCase.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.pathable; + +import java.net.URL; +import java.net.URLClassLoader; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + +/** + * Tests for the PathableTestSuite class. + */ + +public class GeneralTestCase extends TestCase { + + /** + * Set up a custom classloader hierarchy for this test case. + */ + public static Test suite() throws Exception { + Class thisClass = GeneralTestCase.class; + ClassLoader thisClassLoader = thisClass.getClassLoader(); + + PathableClassLoader loader = new PathableClassLoader(null); + loader.useExplicitLoader("junit.", thisClassLoader); + loader.addLogicalLib("testclasses"); + + // reload this class via the child classloader + Class testClass = loader.loadClass(thisClass.getName()); + + // and return our custom TestSuite class + return new PathableTestSuite(testClass, loader); + } + + /** + * Verify that a certain system property is not set, then set it. + */ + private static void checkAndSetProperties() { + String prop = System.getProperty("no.such.property"); + assertNull("no.such.property is unexpectedly defined", prop); + System.setProperty("no.such.property", "dummy value"); + prop = System.getProperty("no.such.property"); + assertNotNull("no.such.property is unexpectedly undefined", prop); + } + + /** + * Verify that when a test method modifies the system properties they are + * reset before the next test is run. + *

+ * This method works in conjunction with testResetProps2. There is no + * way of knowing which test method junit will run first, but it doesn't + * matter; whichever one of them runs first will modify the system properties. + * If the PathableTestSuite isn't resetting the system properties then whichever + * of them runs second will fail. Of course if other methods are run in-between + * then those methods might also fail... + */ + public void testResetProps1() { + checkAndSetProperties(); + } + + /** + * See testResetProps1. + */ + public void testResetProps2() { + checkAndSetProperties(); + } + + /** + * Verify that the context classloader is a custom one, then reset it to + * a non-custom one. + */ + private static void checkAndSetContext() { + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + assertEquals("ContextLoader is of unexpected type", + contextLoader.getClass().getName(), + PathableClassLoader.class.getName()); + + URL[] noUrls = new URL[0]; + Thread.currentThread().setContextClassLoader(new URLClassLoader(noUrls)); + } + + /** + * Verify that when a test method modifies the context classloader it is + * reset before the next test is run. + *

+ * This method works in conjunction with testResetContext2. There is no + * way of knowing which test method junit will run first, but it doesn't + * matter; whichever one of them runs first will modify the contextClassloader. + * If the PathableTestSuite isn't resetting the contextClassLoader then whichever + * of them runs second will fail. Of course if other methods are run in-between + * then those methods might also fail... + */ + public void testResetContext1() { + checkAndSetContext(); + } + + /** + * See testResetContext1. + */ + public void testResetContext2() { + checkAndSetContext(); + } +} diff --git a/src/test/java/org/apache/commons/logging/pathable/ParentFirstTestCase.java b/src/test/java/org/apache/commons/logging/pathable/ParentFirstTestCase.java new file mode 100644 index 0000000..f9bf452 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/pathable/ParentFirstTestCase.java @@ -0,0 +1,308 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.pathable; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + +/** + * Tests for the PathableTestSuite and PathableClassLoader functionality, + * where lookup order for the PathableClassLoader is parent-first. + *

+ * These tests assume: + *

    + *
  • junit is in system classpath + *
  • nothing else is in system classpath + *
+ */ + +public class ParentFirstTestCase extends TestCase { + + /** + * Set up a custom classloader hierarchy for this test case. + * The hierarchy is: + *
    + *
  • contextloader: parent-first. + *
  • childloader: parent-first, used to load test case. + *
  • parentloader: parent-first, parent is the bootclassloader. + *
+ */ + public static Test suite() throws Exception { + Class thisClass = ParentFirstTestCase.class; + ClassLoader thisClassLoader = thisClass.getClassLoader(); + + // Make the parent a direct child of the bootloader to hide all + // other classes in the system classpath + PathableClassLoader parent = new PathableClassLoader(null); + + // Make the junit classes visible as a special case, as junit + // won't be able to call this class at all without this. The + // junit classes must be visible from the classloader that loaded + // this class, so use that as the source for future access to classes + // from the junit package. + parent.useExplicitLoader("junit.", thisClassLoader); + + // make the commons-logging.jar classes visible via the parent + parent.addLogicalLib("commons-logging"); + + // create a child classloader to load the test case through + PathableClassLoader child = new PathableClassLoader(parent); + + // obviously, the child classloader needs to have the test classes + // in its path! + child.addLogicalLib("testclasses"); + child.addLogicalLib("commons-logging-adapters"); + + // create a third classloader to be the context classloader. + PathableClassLoader context = new PathableClassLoader(child); + + // reload this class via the child classloader + Class testClass = child.loadClass(thisClass.getName()); + + // and return our custom TestSuite class + return new PathableTestSuite(testClass, context); + } + + /** + * Utility method to return the set of all classloaders in the + * parent chain starting from the one that loaded the class for + * this object instance. + */ + private Set getAncestorCLs() { + Set s = new HashSet(); + ClassLoader cl = this.getClass().getClassLoader(); + while (cl != null) { + s.add(cl); + cl = cl.getParent(); + } + return s; + } + + /** + * Test that the classloader hierarchy is as expected, and that + * calling loadClass() on various classloaders works as expected. + * Note that for this test case, parent-first classloading is + * in effect. + */ + public void testPaths() throws Exception { + // the context classloader is not expected to be null + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + assertNotNull("Context classloader is null", contextLoader); + assertEquals("Context classloader has unexpected type", + PathableClassLoader.class.getName(), + contextLoader.getClass().getName()); + + // the classloader that loaded this class is obviously not null + ClassLoader thisLoader = this.getClass().getClassLoader(); + assertNotNull("thisLoader is null", thisLoader); + assertEquals("thisLoader has unexpected type", + PathableClassLoader.class.getName(), + thisLoader.getClass().getName()); + + // the suite method specified that the context classloader's parent + // is the loader that loaded this test case. + assertSame("Context classloader is not child of thisLoader", + thisLoader, contextLoader.getParent()); + + // thisLoader's parent should be available + ClassLoader parentLoader = thisLoader.getParent(); + assertNotNull("Parent classloader is null", parentLoader); + assertEquals("Parent classloader has unexpected type", + PathableClassLoader.class.getName(), + parentLoader.getClass().getName()); + + // parent should have a parent of null + assertNull("Parent classloader has non-null parent", parentLoader.getParent()); + + // getSystemClassloader is not a PathableClassLoader; it's of a + // built-in type. This also verifies that system classloader is none of + // (context, child, parent). + ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); + assertNotNull("System classloader is null", systemLoader); + assertFalse("System classloader has unexpected type", + PathableClassLoader.class.getName().equals( + systemLoader.getClass().getName())); + + // junit classes should be visible; their classloader is not + // in the hierarchy of parent classloaders for this class, + // though it is accessable due to trickery in the PathableClassLoader. + Class junitTest = contextLoader.loadClass("junit.framework.Test"); + Set ancestorCLs = getAncestorCLs(); + assertFalse("Junit not loaded by ancestor classloader", + ancestorCLs.contains(junitTest.getClassLoader())); + + // jcl api classes should be visible only via the parent + Class logClass = contextLoader.loadClass("org.apache.commons.logging.Log"); + assertSame("Log class not loaded via parent", + logClass.getClassLoader(), parentLoader); + + // jcl adapter classes should be visible via both parent and child. However + // as the classloaders are parent-first we should see the parent one. + Class log4jClass = contextLoader.loadClass("org.apache.commons.logging.impl.Log4JLogger"); + assertSame("Log4JLogger not loaded via parent", + log4jClass.getClassLoader(), parentLoader); + + // test classes should be visible via the child only + Class testClass = contextLoader.loadClass("org.apache.commons.logging.PathableTestSuite"); + assertSame("PathableTestSuite not loaded via child", + testClass.getClassLoader(), thisLoader); + + // test loading of class that is not available + try { + Class noSuchClass = contextLoader.loadClass("no.such.class"); + fail("Class no.such.class is unexpectedly available"); + assertNotNull(noSuchClass); // silence warning about unused var + } catch(ClassNotFoundException ex) { + // ok + } + + // String class classloader is null + Class stringClass = contextLoader.loadClass("java.lang.String"); + assertNull("String class classloader is not null!", + stringClass.getClassLoader()); + } + + /** + * Test that the various flavours of ClassLoader.getResource work as expected. + */ + public void testResource() { + URL resource; + + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader childLoader = contextLoader.getParent(); + + // getResource where it doesn't exist + resource = childLoader.getResource("nosuchfile"); + assertNull("Non-null URL returned for invalid resource name", resource); + + // getResource where it is accessable only to parent classloader + resource = childLoader.getResource("org/apache/commons/logging/Log.class"); + assertNotNull("Unable to locate Log.class resource", resource); + + // getResource where it is accessable only to child classloader + resource = childLoader.getResource("org/apache/commons/logging/PathableTestSuite.class"); + assertNotNull("Unable to locate PathableTestSuite.class resource", resource); + + // getResource where it is accessable to both classloaders. The one visible + // to the parent should be returned. The URL returned will be of form + // jar:file:/x/y.jar!path/to/resource. The filename part should include the jarname + // of form commons-logging-nnnn.jar, not commons-logging-adapters-nnnn.jar + resource = childLoader.getResource("org/apache/commons/logging/impl/Log4JLogger.class"); + assertNotNull("Unable to locate Log4JLogger.class resource", resource); + assertTrue("Incorrect source for Log4JLogger class", + resource.toString().indexOf("/commons-logging-1.") > 0); + } + + /** + * Test that the various flavours of ClassLoader.getResources work as expected. + */ + public void testResources() throws Exception { + Enumeration resources; + URL[] urls; + + // verify the classloader hierarchy + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader childLoader = contextLoader.getParent(); + ClassLoader parentLoader = childLoader.getParent(); + ClassLoader bootLoader = parentLoader.getParent(); + assertNull("Unexpected classloader hierarchy", bootLoader); + + // getResources where no instances exist + resources = childLoader.getResources("nosuchfile"); + urls = toURLArray(resources); + assertEquals("Non-null URL returned for invalid resource name", 0, urls.length); + + // getResources where the resource only exists in the parent + resources = childLoader.getResources("org/apache/commons/logging/Log.class"); + urls = toURLArray(resources); + assertEquals("Unexpected number of Log.class resources found", 1, urls.length); + + // getResources where the resource only exists in the child + resources = childLoader.getResources("org/apache/commons/logging/PathableTestSuite.class"); + urls = toURLArray(resources); + assertEquals("Unexpected number of PathableTestSuite.class resources found", 1, urls.length); + + // getResources where the resource exists in both. + // resources should be returned in order (parent-resource, child-resource) + resources = childLoader.getResources("org/apache/commons/logging/impl/Log4JLogger.class"); + urls = toURLArray(resources); + assertEquals("Unexpected number of Log4JLogger.class resources found", 2, urls.length); + + // There is no gaurantee about the ordering of results returned from getResources + // To make this test portable across JVMs, sort the string to give them a known order + String[] urlsToStrings = new String[2]; + urlsToStrings[0] = urls[0].toString(); + urlsToStrings[1] = urls[1].toString(); + Arrays.sort(urlsToStrings); + assertTrue("Incorrect source for Log4JLogger class", + urlsToStrings[0].indexOf("/commons-logging-1.") > 0); + assertTrue("Incorrect source for Log4JLogger class", + urlsToStrings[1].indexOf("/commons-logging-adapters-1.") > 0); + + } + + /** + * Utility method to convert an enumeration-of-URLs into an array of URLs. + */ + private static URL[] toURLArray(Enumeration e) { + ArrayList l = new ArrayList(); + while (e.hasMoreElements()) { + URL u = (URL) e.nextElement(); + l.add(u); + } + URL[] tmp = new URL[l.size()]; + return (URL[]) l.toArray(tmp); + } + + /** + * Test that getResourceAsStream works. + */ + public void testResourceAsStream() throws Exception { + java.io.InputStream is; + + // verify the classloader hierarchy + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader childLoader = contextLoader.getParent(); + ClassLoader parentLoader = childLoader.getParent(); + ClassLoader bootLoader = parentLoader.getParent(); + assertNull("Unexpected classloader hierarchy", bootLoader); + + // getResourceAsStream where no instances exist + is = childLoader.getResourceAsStream("nosuchfile"); + assertNull("Invalid resource returned non-null stream", is); + + // getResourceAsStream where resource does exist + is = childLoader.getResourceAsStream("org/apache/commons/logging/Log.class"); + assertNotNull("Null returned for valid resource", is); + is.close(); + + // It would be nice to test parent-first ordering here, but that would require + // having a resource with the same name in both the parent and child loaders, + // but with different contents. That's a little tricky to set up so we'll + // skip that for now. + } +} diff --git a/src/test/java/org/apache/commons/logging/security/DummyClass.java b/src/test/java/org/apache/commons/logging/security/DummyClass.java new file mode 100644 index 0000000..ece0e7c --- /dev/null +++ b/src/test/java/org/apache/commons/logging/security/DummyClass.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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.security; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class DummyClass { + + public DummyClass() { + Log log = LogFactory.getLog(DummyClass.class); + log.info("Some log message"); + } +} diff --git a/src/test/java/org/apache/commons/logging/security/MockSecurityManager.java b/src/test/java/org/apache/commons/logging/security/MockSecurityManager.java new file mode 100644 index 0000000..0d9b4de --- /dev/null +++ b/src/test/java/org/apache/commons/logging/security/MockSecurityManager.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.security; + +import java.io.FilePermission; +import java.security.Permission; +import java.security.Permissions; + + +/** + * Custom implementation of a security manager, so we can control the + * security environment for tests in this package. + */ +public class MockSecurityManager extends SecurityManager { + + private final Permissions permissions = new Permissions(); + private static final Permission setSecurityManagerPerm = + new RuntimePermission("setSecurityManager"); + + private int untrustedCodeCount = 0; + + public MockSecurityManager() { + permissions.add(setSecurityManagerPerm); + } + + /** + * Define the set of permissions to be granted to classes in the o.a.c.l package, + * but NOT to unit-test classes in o.a.c.l.security package. + */ + public void addPermission(Permission p) { + permissions.add(p); + } + + /** + * This returns the number of times that a check of a permission failed + * due to stack-walking tracing up into untrusted code. Any non-zero + * value indicates a bug in JCL, ie a situation where code was not + * correctly wrapped in an AccessController block. The result of such a + * bug is that signing JCL is not sufficient to allow JCL to perform + * the operation; the caller would need to be signed too. + */ + public int getUntrustedCodeCount() { + return untrustedCodeCount; + } + + public void checkPermission(Permission p) throws SecurityException { + if (setSecurityManagerPerm.implies(p)) { + // ok, allow this; we don't want to block any calls to setSecurityManager + // otherwise this custom security manager cannot be reset to the original. + // System.out.println("setSecurityManager: granted"); + return; + } + + // Allow read-only access to files, as this is needed to load classes! + // Ideally, we would limit this to just .class and .jar files. + if (p instanceof FilePermission) { + FilePermission fp = (FilePermission) p; + if (fp.getActions().equals("read")) { + // System.out.println("Permit read of files"); + return; + } + } + + System.out.println("\n\ntesting permission:" + p.getClass() + ":"+ p); + + Exception e = new Exception(); + e.fillInStackTrace(); + StackTraceElement[] stack = e.getStackTrace(); + + // scan the call stack from most recent to oldest. + // start at 1 to skip the entry in the stack for this method + for(int i=1; i + * This class has only one unit test, as we are (in part) checking behaviour in + * the static block of the LogFactory class. As that class cannot be unloaded after + * being loaded into a classloader, the only workaround is to use the + * PathableClassLoader approach to ensure each test is run in its own + * classloader, and use a separate testcase class for each test. + */ +public class SecurityAllowedTestCase extends TestCase +{ + private SecurityManager oldSecMgr; + + // Dummy special hashtable, so we can tell JCL to use this instead of + // the standard one. + public static class CustomHashtable extends Hashtable { + + /** + * Generated serial version ID. + */ + private static final long serialVersionUID = 8941017300059246720L; + } + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + PathableClassLoader parent = new PathableClassLoader(null); + parent.useExplicitLoader("junit.", Test.class.getClassLoader()); + parent.addLogicalLib("commons-logging"); + parent.addLogicalLib("testclasses"); + + Class testClass = parent.loadClass( + "org.apache.commons.logging.security.SecurityAllowedTestCase"); + return new PathableTestSuite(testClass, parent); + } + + public void setUp() { + // save security manager so it can be restored in tearDown + oldSecMgr = System.getSecurityManager(); + } + + public void tearDown() { + // Restore, so other tests don't get stuffed up if a test + // sets a custom security manager. + System.setSecurityManager(oldSecMgr); + } + + /** + * Test what happens when JCL is run with all permissions enabled. Custom + * overrides should take effect. + */ + public void testAllAllowed() { + System.setProperty( + LogFactory.HASHTABLE_IMPLEMENTATION_PROPERTY, + CustomHashtable.class.getName()); + MockSecurityManager mySecurityManager = new MockSecurityManager(); + mySecurityManager.addPermission(new AllPermission()); + System.setSecurityManager(mySecurityManager); + + try { + // Use reflection so that we can control exactly when the static + // initialiser for the LogFactory class is executed. + Class c = this.getClass().getClassLoader().loadClass( + "org.apache.commons.logging.LogFactory"); + Method m = c.getMethod("getLog", new Class[] {Class.class}); + Log log = (Log) m.invoke(null, new Object[] {this.getClass()}); + + // Check whether we had any security exceptions so far (which were + // caught by the code). We should not, as every secure operation + // should be wrapped in an AccessController. Any security exceptions + // indicate a path that is missing an appropriate AccessController. + // + // We don't wait until after the log.info call to get this count + // because java.util.logging tries to load a resource bundle, which + // requires permission accessClassInPackage. JCL explicitly does not + // wrap calls to log methods in AccessControllers because writes to + // a log file *should* only be permitted if the original caller is + // trusted to access that file. + int untrustedCodeCount = mySecurityManager.getUntrustedCodeCount(); + log.info("testing"); + + // check that the default map implementation was loaded, as JCL was + // forbidden from reading the HASHTABLE_IMPLEMENTATION_PROPERTY property. + System.setSecurityManager(null); + Field factoryField = c.getDeclaredField("factories"); + factoryField.setAccessible(true); + Object factoryTable = factoryField.get(null); + assertNotNull(factoryTable); + assertEquals(CustomHashtable.class.getName(), factoryTable.getClass().getName()); + + // we better compare that we have no security exception during the call to log + // IBM JVM tries to load bundles during the invoke call, which increase the count + assertEquals("Untrusted code count", untrustedCodeCount, mySecurityManager.getUntrustedCodeCount()); + } catch(Throwable t) { + // Restore original security manager so output can be generated; the + // PrintWriter constructor tries to read the line.separator + // system property. + System.setSecurityManager(oldSecMgr); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + fail("Unexpected exception:" + t.getMessage() + ":" + sw.toString()); + } + } +} diff --git a/src/test/java/org/apache/commons/logging/security/SecurityForbiddenTestCase.java b/src/test/java/org/apache/commons/logging/security/SecurityForbiddenTestCase.java new file mode 100644 index 0000000..08c5af9 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/security/SecurityForbiddenTestCase.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.security; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Hashtable; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + +/** + * Tests for logging with a security policy that forbids JCL access to anything. + *

+ * Performing tests with security permissions disabled is tricky, as building error + * messages on failure requires certain security permissions. If the security manager + * blocks these, then the test can fail without the error messages being output. + *

+ * This class has only one unit test, as we are (in part) checking behaviour in + * the static block of the LogFactory class. As that class cannot be unloaded after + * being loaded into a classloader, the only workaround is to use the + * PathableClassLoader approach to ensure each test is run in its own + * classloader, and use a separate testcase class for each test. + */ +public class SecurityForbiddenTestCase extends TestCase +{ + private SecurityManager oldSecMgr; + private ClassLoader otherClassLoader; + + // Dummy special hashtable, so we can tell JCL to use this instead of + // the standard one. + public static class CustomHashtable extends Hashtable { + + /** + * Generated serial version ID. + */ + private static final long serialVersionUID = 7224652794746236024L; + } + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + PathableClassLoader parent = new PathableClassLoader(null); + parent.useExplicitLoader("junit.", Test.class.getClassLoader()); + parent.addLogicalLib("commons-logging"); + parent.addLogicalLib("testclasses"); + + Class testClass = parent.loadClass( + "org.apache.commons.logging.security.SecurityForbiddenTestCase"); + return new PathableTestSuite(testClass, parent); + } + + public void setUp() { + // save security manager so it can be restored in tearDown + oldSecMgr = System.getSecurityManager(); + + PathableClassLoader classLoader = new PathableClassLoader(null); + classLoader.addLogicalLib("commons-logging"); + classLoader.addLogicalLib("testclasses"); + + otherClassLoader = classLoader; + } + + public void tearDown() { + // Restore, so other tests don't get stuffed up if a test + // sets a custom security manager. + System.setSecurityManager(oldSecMgr); + } + + /** + * Test what happens when JCL is run with absolutely no security + * privileges at all, including reading system properties. Everything + * should fall back to the built-in defaults. + */ + public void testAllForbidden() { + System.setProperty( + LogFactory.HASHTABLE_IMPLEMENTATION_PROPERTY, + CustomHashtable.class.getName()); + MockSecurityManager mySecurityManager = new MockSecurityManager(); + + System.setSecurityManager(mySecurityManager); + + try { + // Use reflection so that we can control exactly when the static + // initialiser for the LogFactory class is executed. + Class c = this.getClass().getClassLoader().loadClass( + "org.apache.commons.logging.LogFactory"); + Method m = c.getMethod("getLog", new Class[] {Class.class}); + Log log = (Log) m.invoke(null, new Object[] {this.getClass()}); + log.info("testing"); + + // check that the default map implementation was loaded, as JCL was + // forbidden from reading the HASHTABLE_IMPLEMENTATION_PROPERTY property. + // + // The default is either the java Hashtable class (java < 1.2) or the + // JCL WeakHashtable (java >= 1.3). + System.setSecurityManager(oldSecMgr); + Field factoryField = c.getDeclaredField("factories"); + factoryField.setAccessible(true); + Object factoryTable = factoryField.get(null); + assertNotNull(factoryTable); + String ftClassName = factoryTable.getClass().getName(); + assertTrue("Custom hashtable unexpectedly used", + !CustomHashtable.class.getName().equals(ftClassName)); + + assertEquals(0, mySecurityManager.getUntrustedCodeCount()); + } catch(Throwable t) { + // Restore original security manager so output can be generated; the + // PrintWriter constructor tries to read the line.separator + // system property. + System.setSecurityManager(oldSecMgr); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + fail("Unexpected exception:" + t.getMessage() + ":" + sw.toString()); + } + } + + /** + * Test what happens when JCL is run with absolutely no security + * privileges at all and a class loaded with a different classloader + * than the context classloader of the current thread tries to log something. + */ + public void testContextClassLoader() { + System.setProperty( + LogFactory.HASHTABLE_IMPLEMENTATION_PROPERTY, + CustomHashtable.class.getName()); + MockSecurityManager mySecurityManager = new MockSecurityManager(); + + System.setSecurityManager(mySecurityManager); + + try { + // load a dummy class with another classloader + // to force a SecurityException when the LogFactory calls + // Thread.getCurrentThread().getContextClassLoader() + loadClass("org.apache.commons.logging.security.DummyClass", otherClassLoader); + + System.setSecurityManager(oldSecMgr); + assertEquals(0, mySecurityManager.getUntrustedCodeCount()); + } catch(Throwable t) { + // Restore original security manager so output can be generated; the + // PrintWriter constructor tries to read the line.separator + // system property. + System.setSecurityManager(oldSecMgr); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + fail("Unexpected exception:" + t.getMessage() + ":" + sw.toString()); + } + } + + /** + * Loads a class with the given classloader. + */ + private Object loadClass(String name, ClassLoader classLoader) { + try { + Class clazz = classLoader.loadClass(name); + Object obj = clazz.newInstance(); + return obj; + } catch ( Exception e ) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + fail("Unexpected exception:" + e.getMessage() + ":" + sw.toString()); + } + return null; + } +} diff --git a/src/test/java/org/apache/commons/logging/servlet/BasicServletTestCase.java b/src/test/java/org/apache/commons/logging/servlet/BasicServletTestCase.java new file mode 100644 index 0000000..e1ecdbb --- /dev/null +++ b/src/test/java/org/apache/commons/logging/servlet/BasicServletTestCase.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.servlet; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; +import org.apache.commons.logging.impl.ServletContextCleaner; + + +/** + * Tests for ServletContextCleaner utility class. + */ + +public class BasicServletTestCase extends TestCase { + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + // LogFactory in parent + // LogFactory in child (loads test) + // LogFactory in tccl + // + // Having the test loaded via a loader above the tccl emulates the situation + // where a web.xml file specifies ServletContextCleaner as a listener, and + // that class is deployed via a shared classloader. + + PathableClassLoader parent = new PathableClassLoader(null); + parent.useExplicitLoader("junit.", Test.class.getClassLoader()); + parent.addLogicalLib("commons-logging"); + parent.addLogicalLib("servlet-api"); + + PathableClassLoader child = new PathableClassLoader(parent); + child.setParentFirst(false); + child.addLogicalLib("commons-logging"); + child.addLogicalLib("testclasses"); + + PathableClassLoader tccl = new PathableClassLoader(child); + tccl.setParentFirst(false); + tccl.addLogicalLib("commons-logging"); + + Class testClass = child.loadClass(BasicServletTestCase.class.getName()); + return new PathableTestSuite(testClass, tccl); + } + + /** + * Test that calling ServletContextCleaner.contextDestroyed doesn't crash. + * Testing anything else is rather difficult... + */ + public void testBasics() { + ServletContextCleaner scc = new ServletContextCleaner(); + scc.contextDestroyed(null); + } +} diff --git a/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java b/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java new file mode 100644 index 0000000..c3a89b6 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java @@ -0,0 +1,277 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.simple; + + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import junit.framework.Test; + +import org.apache.commons.logging.DummyException; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; +import org.apache.commons.logging.impl.SimpleLog; + + +/** + *

TestCase for simple logging when running with custom configuration + * properties.

+ * + * @author Craig R. McClanahan + * @version $Revision: 1432587 $ $Date: 2013-01-13 12:11:32 +0100 (Sun, 13 Jan 2013) $ + */ +public class CustomConfigTestCase extends DefaultConfigTestCase { + + + // ----------------------------------------------------- Instance Variables + + + /** + *

The expected log records.

+ */ + protected List expected; + + + /** + *

The message levels that should have been logged.

+ */ + /* + protected Level testLevels[] = + { Level.FINE, Level.INFO, Level.WARNING, Level.SEVERE, Level.SEVERE }; + */ + + + /** + *

The message strings that should have been logged.

+ */ + protected String testMessages[] = + { "debug", "info", "warn", "error", "fatal" }; + + + // ------------------------------------------- JUnit Infrastructure Methods + + /** + * Set system properties that will control the LogFactory/Log objects + * when they are created. Subclasses can override this method to + * define properties that suit them. + */ + public void setProperties() { + System.setProperty( + "org.apache.commons.logging.Log", + "org.apache.commons.logging.simple.DecoratedSimpleLog"); + System.setProperty( + "org.apache.commons.logging.simplelog.defaultlog", + "debug"); + } + + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + LogFactory.releaseAll(); + setProperties(); + expected = new ArrayList(); + setUpFactory(); + setUpLog("DecoratedLogger"); + } + + + /** + * Return the tests included in this test suite. + *

+ * We need to use a PathableClassLoader here because the SimpleLog class + * is a pile of junk and chock-full of static variables. Any other test + * (like simple.CustomConfigTestCase) that has used the SimpleLog class + * will already have caused it to do once-only initialisation that we + * can't reset, even by calling LogFactory.releaseAll, because of those + * ugly statics. The only clean solution is to load a clean copy of + * commons-logging including SimpleLog via a nice clean classloader. + * Or we could fix SimpleLog to be sane... + */ + public static Test suite() throws Exception { + Class thisClass = CustomConfigTestCase.class; + + PathableClassLoader loader = new PathableClassLoader(null); + loader.useExplicitLoader("junit.", Test.class.getClassLoader()); + loader.addLogicalLib("testclasses"); + loader.addLogicalLib("commons-logging"); + + Class testClass = loader.loadClass(thisClass.getName()); + return new PathableTestSuite(testClass, loader); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + super.tearDown(); + expected = null; + } + + + // ----------------------------------------------------------- Test Methods + + + // Test logging message strings with exceptions + public void testExceptionMessages() throws Exception { + + ((DecoratedSimpleLog) log).clearCache(); + logExceptionMessages(); + checkExpected(); + + } + + + // Test logging plain message strings + public void testPlainMessages() throws Exception { + + ((DecoratedSimpleLog) log).clearCache(); + logPlainMessages(); + checkExpected(); + + } + + + // Test Serializability of standard instance + public void testSerializable() throws Exception { + + ((DecoratedSimpleLog) log).clearCache(); + logPlainMessages(); + super.testSerializable(); + logExceptionMessages(); + checkExpected(); + + } + + + // -------------------------------------------------------- Support Methods + + + // Check the decorated log instance + protected void checkDecorated() { + + assertNotNull("Log exists", log); + assertEquals("Log class", + "org.apache.commons.logging.simple.DecoratedSimpleLog", + log.getClass().getName()); + + // Can we call level checkers with no exceptions? + assertTrue(log.isDebugEnabled()); + assertTrue(log.isErrorEnabled()); + assertTrue(log.isFatalEnabled()); + assertTrue(log.isInfoEnabled()); + assertTrue(!log.isTraceEnabled()); + assertTrue(log.isWarnEnabled()); + + // Can we retrieve the current log level? + assertEquals(SimpleLog.LOG_LEVEL_DEBUG, ((SimpleLog) log).getLevel()); + + // Can we validate the extra exposed properties? + checkDecoratedDateTime(); + assertEquals("DecoratedLogger", + ((DecoratedSimpleLog) log).getLogName()); + checkShowDateTime(); + assertTrue(((DecoratedSimpleLog) log).getShowShortName()); + + } + + /** Hook for subclassses */ + protected void checkShowDateTime() { + assertTrue(!((DecoratedSimpleLog) log).getShowDateTime()); + } + + /** Hook for subclasses */ + protected void checkDecoratedDateTime() { + assertEquals("yyyy/MM/dd HH:mm:ss:SSS zzz", + ((DecoratedSimpleLog) log).getDateTimeFormat()); + } + + + + // Check the actual log records against the expected ones + protected void checkExpected() { + + List acts = ((DecoratedSimpleLog) log).getCache(); + Iterator exps = expected.iterator(); + int n = 0; + while (exps.hasNext()) { + LogRecord exp = (LogRecord) exps.next(); + LogRecord act = (LogRecord) acts.get(n++); + assertEquals("Row " + n + " type", exp.type, act.type); + assertEquals("Row " + n + " message", exp.message, act.message); + assertEquals("Row " + n + " throwable", exp.t, act.t); + } + + } + + + // Check the standard log instance + protected void checkStandard() { + + checkDecorated(); + + } + + + // Log the messages with exceptions + protected void logExceptionMessages() { + + // Generate log records + Throwable t = new DummyException(); + log.trace("trace", t); // Should not actually get logged + log.debug("debug", t); + log.info("info", t); + log.warn("warn", t); + log.error("error", t); + log.fatal("fatal", t); + + // Record the log records we expect + expected.add(new LogRecord(SimpleLog.LOG_LEVEL_DEBUG, "debug", t)); + expected.add(new LogRecord(SimpleLog.LOG_LEVEL_INFO, "info", t)); + expected.add(new LogRecord(SimpleLog.LOG_LEVEL_WARN, "warn", t)); + expected.add(new LogRecord(SimpleLog.LOG_LEVEL_ERROR, "error", t)); + expected.add(new LogRecord(SimpleLog.LOG_LEVEL_FATAL, "fatal", t)); + + } + + + // Log the plain messages + protected void logPlainMessages() { + + // Generate log records + log.trace("trace"); // Should not actually get logged + log.debug("debug"); + log.info("info"); + log.warn("warn"); + log.error("error"); + log.fatal("fatal"); + + // Record the log records we expect + expected.add(new LogRecord(SimpleLog.LOG_LEVEL_DEBUG, "debug", null)); + expected.add(new LogRecord(SimpleLog.LOG_LEVEL_INFO, "info", null)); + expected.add(new LogRecord(SimpleLog.LOG_LEVEL_WARN, "warn", null)); + expected.add(new LogRecord(SimpleLog.LOG_LEVEL_ERROR, "error", null)); + expected.add(new LogRecord(SimpleLog.LOG_LEVEL_FATAL, "fatal", null)); + + } + + +} diff --git a/src/test/java/org/apache/commons/logging/simple/DateTimeCustomConfigTestCase.java b/src/test/java/org/apache/commons/logging/simple/DateTimeCustomConfigTestCase.java new file mode 100644 index 0000000..5e72718 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/simple/DateTimeCustomConfigTestCase.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.simple; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import junit.framework.Test; + +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + + +/** + * Tests custom date time format configuration + */ +public class DateTimeCustomConfigTestCase extends CustomConfigTestCase { + + // ----------------------------------------------------------- Constructors + + /** + * Return the tests included in this test suite. + *

+ * We need to use a PathableClassLoader here because the SimpleLog class + * is a pile of junk and chock-full of static variables. Any other test + * (like simple.CustomConfigTestCase) that has used the SimpleLog class + * will already have caused it to do once-only initialisation that we + * can't reset, even by calling LogFactory.releaseAll, because of those + * ugly statics. The only clean solution is to load a clean copy of + * commons-logging including SimpleLog via a nice clean classloader. + * Or we could fix SimpleLog to be sane... + */ + public static Test suite() throws Exception { + Class thisClass = DateTimeCustomConfigTestCase.class; + + PathableClassLoader loader = new PathableClassLoader(null); + loader.useExplicitLoader("junit.", Test.class.getClassLoader()); + loader.addLogicalLib("testclasses"); + loader.addLogicalLib("commons-logging"); + + Class testClass = loader.loadClass(thisClass.getName()); + return new PathableTestSuite(testClass, loader); + } + + + /** + * Set up system properties required by this unit test. Here, we + * set up the props defined in the parent class setProperties method, + * and add a few to configure the SimpleLog class date/time output. + */ + public void setProperties() { + super.setProperties(); + + System.setProperty( + "org.apache.commons.logging.simplelog.dateTimeFormat", + "dd.mm.yyyy"); + System.setProperty( + "org.apache.commons.logging.simplelog.showdatetime", + "true"); + } + + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + super.setUp(); + } + + + // ----------------------------------------------------------- Methods + + /** Checks that the date time format has been successfully set */ + protected void checkDecoratedDateTime() { + assertEquals("Expected date format to be set", "dd.mm.yyyy", + ((DecoratedSimpleLog) log).getDateTimeFormat()); + // try the formatter + Date now = new Date(); + DateFormat formatter = ((DecoratedSimpleLog) log).getDateTimeFormatter(); + SimpleDateFormat sampleFormatter = new SimpleDateFormat("dd.mm.yyyy"); + assertEquals("Date should be formatters to pattern dd.mm.yyyy", + sampleFormatter.format(now), formatter.format(now)); + } + + /** Hook for subclassses */ + protected void checkShowDateTime() { + assertTrue(((DecoratedSimpleLog) log).getShowDateTime()); + } + +} diff --git a/src/test/java/org/apache/commons/logging/simple/DecoratedSimpleLog.java b/src/test/java/org/apache/commons/logging/simple/DecoratedSimpleLog.java new file mode 100644 index 0000000..0129bcd --- /dev/null +++ b/src/test/java/org/apache/commons/logging/simple/DecoratedSimpleLog.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.simple; + + +import java.util.ArrayList; +import java.util.List; +import java.text.DateFormat; +import org.apache.commons.logging.impl.SimpleLog; + + +/** + *

Decorated instance of SimpleLog to expose internal state and + * support buffered output.

+ */ + +public class DecoratedSimpleLog extends SimpleLog { + + + // ------------------------------------------------------------ Constructor + + + /** + * Generated serial version ID. + */ + private static final long serialVersionUID = 196544280770017153L; + + public DecoratedSimpleLog(String name) { + super(name); + } + + + // ------------------------------------------------------------- Properties + + public DateFormat getDateTimeFormatter() { + return dateFormatter; + } + + + public String getDateTimeFormat() { + return dateTimeFormat; + } + + + public String getLogName() { + return logName; + } + + + public boolean getShowDateTime() { + return showDateTime; + } + + + public boolean getShowShortName() { + return showShortName; + } + + + // ------------------------------------------------------- Protected Methods + + + // Cache logged messages + protected void log(int type, Object message, Throwable t) { + + super.log(type, message, t); + cache.add(new LogRecord(type, message, t)); + + } + + + // ---------------------------------------------------------- Public Methods + + + // Cache of logged records + protected ArrayList cache = new ArrayList(); + + + // Clear cache + public void clearCache() { + cache.clear(); + } + + + // Return cache + public List getCache() { + return this.cache; + } + + +} diff --git a/src/test/java/org/apache/commons/logging/simple/DefaultConfigTestCase.java b/src/test/java/org/apache/commons/logging/simple/DefaultConfigTestCase.java new file mode 100644 index 0000000..37d3847 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/simple/DefaultConfigTestCase.java @@ -0,0 +1,249 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.simple; + + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; +import org.apache.commons.logging.impl.SimpleLog; + + +/** + *

TestCase for simple logging when running with zero configuration + * other than selecting the SimpleLog implementation.

+ * + * @author Craig R. McClanahan + * @version $Revision: 1432587 $ $Date: 2013-01-13 12:11:32 +0100 (Sun, 13 Jan 2013) $ + */ + +public class DefaultConfigTestCase extends TestCase { + + + // ----------------------------------------------------- Instance Variables + + + /** + *

The {@link LogFactory} implementation we have selected.

+ */ + protected LogFactory factory = null; + + + /** + *

The {@link Log} implementation we have selected.

+ */ + protected Log log = null; + + + // ------------------------------------------- JUnit Infrastructure Methods + + + /** + * Return the tests included in this test suite. + *

+ * We need to use a PathableClassLoader here because the SimpleLog class + * is a pile of junk and chock-full of static variables. Any other test + * (like simple.CustomConfigTestCase) that has used the SimpleLog class + * will already have caused it to do once-only initialisation that we + * can't reset, even by calling LogFactory.releaseAll, because of those + * ugly statics. The only clean solution is to load a clean copy of + * commons-logging including SimpleLog via a nice clean classloader. + * Or we could fix SimpleLog to be sane... + */ + public static Test suite() throws Exception { + Class thisClass = DefaultConfigTestCase.class; + + PathableClassLoader loader = new PathableClassLoader(null); + loader.useExplicitLoader("junit.", Test.class.getClassLoader()); + loader.addLogicalLib("testclasses"); + loader.addLogicalLib("commons-logging"); + + Class testClass = loader.loadClass(thisClass.getName()); + return new PathableTestSuite(testClass, loader); + } + + /** + * Set system properties that will control the LogFactory/Log objects + * when they are created. Subclasses can override this method to + * define properties that suit them. + */ + public void setProperties() { + System.setProperty( + "org.apache.commons.logging.Log", + "org.apache.commons.logging.impl.SimpleLog"); + } + + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + LogFactory.releaseAll(); + setProperties(); + setUpFactory(); + setUpLog("TestLogger"); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + log = null; + factory = null; + LogFactory.releaseAll(); + } + + + // ----------------------------------------------------------- Test Methods + + + // Test pristine DecoratedSimpleLog instance + public void testPristineDecorated() { + + setUpDecorated("DecoratedLogger"); + checkDecorated(); + + } + + + // Test pristine Log instance + public void testPristineLog() { + + checkStandard(); + + } + + + // Test pristine LogFactory instance + public void testPristineFactory() { + + assertNotNull("LogFactory exists", factory); + assertEquals("LogFactory class", + "org.apache.commons.logging.impl.LogFactoryImpl", + factory.getClass().getName()); + + String names[] = factory.getAttributeNames(); + assertNotNull("Names exists", names); + assertEquals("Names empty", 0, names.length); + + } + + + // Test Serializability of standard instance + public void testSerializable() throws Exception { + + // Serialize and deserialize the instance + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(log); + oos.close(); + ByteArrayInputStream bais = + new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bais); + log = (Log) ois.readObject(); + ois.close(); + + // Check the characteristics of the resulting object + checkStandard(); + + } + + + // -------------------------------------------------------- Support Methods + + + + // Check the decorated log instance + protected void checkDecorated() { + + assertNotNull("Log exists", log); + assertEquals("Log class", + "org.apache.commons.logging.simple.DecoratedSimpleLog", + log.getClass().getName()); + + // Can we call level checkers with no exceptions? + assertTrue(!log.isDebugEnabled()); + assertTrue(log.isErrorEnabled()); + assertTrue(log.isFatalEnabled()); + assertTrue(log.isInfoEnabled()); + assertTrue(!log.isTraceEnabled()); + assertTrue(log.isWarnEnabled()); + + // Can we retrieve the current log level? + assertEquals(SimpleLog.LOG_LEVEL_INFO, ((SimpleLog) log).getLevel()); + + // Can we validate the extra exposed properties? + assertEquals("yyyy/MM/dd HH:mm:ss:SSS zzz", + ((DecoratedSimpleLog) log).getDateTimeFormat()); + assertEquals("DecoratedLogger", + ((DecoratedSimpleLog) log).getLogName()); + assertTrue(!((DecoratedSimpleLog) log).getShowDateTime()); + assertTrue(((DecoratedSimpleLog) log).getShowShortName()); + + } + + + // Check the standard log instance + protected void checkStandard() { + + assertNotNull("Log exists", log); + assertEquals("Log class", + "org.apache.commons.logging.impl.SimpleLog", + log.getClass().getName()); + + // Can we call level checkers with no exceptions? + assertTrue(!log.isDebugEnabled()); + assertTrue(log.isErrorEnabled()); + assertTrue(log.isFatalEnabled()); + assertTrue(log.isInfoEnabled()); + assertTrue(!log.isTraceEnabled()); + assertTrue(log.isWarnEnabled()); + + // Can we retrieve the current log level? + assertEquals(SimpleLog.LOG_LEVEL_INFO, ((SimpleLog) log).getLevel()); + + } + + + // Set up decorated log instance + protected void setUpDecorated(String name) { + log = new DecoratedSimpleLog(name); + } + + + // Set up factory instance + protected void setUpFactory() throws Exception { + factory = LogFactory.getFactory(); + } + + + // Set up log instance + protected void setUpLog(String name) throws Exception { + log = LogFactory.getLog(name); + } + + +} diff --git a/src/test/java/org/apache/commons/logging/simple/LogRecord.java b/src/test/java/org/apache/commons/logging/simple/LogRecord.java new file mode 100644 index 0000000..0c6d784 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/simple/LogRecord.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.simple; + + +import java.io.Serializable; + + +public class LogRecord implements Serializable { + + + /** + * Generated serial version ID. + */ + private static final long serialVersionUID = -5254831759209770665L; + + public LogRecord(int type, Object message, Throwable t) { + this.type = type; + this.message = message; + this.t = t; + } + + public int type; + public Object message; + public Throwable t; + +} diff --git a/src/test/java/org/apache/commons/logging/tccl/BadTCCLTestCase.java b/src/test/java/org/apache/commons/logging/tccl/BadTCCLTestCase.java new file mode 100644 index 0000000..7211156 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/tccl/BadTCCLTestCase.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.tccl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + +import junit.framework.Test; +import junit.framework.TestCase; + +/** + * Simulates the case when TCCL is badly set and cannot load JCL. + */ +public class BadTCCLTestCase extends TestCase { + + public static Test suite() throws Exception { + PathableClassLoader contextClassLoader = new PathableClassLoader(null); + contextClassLoader.useExplicitLoader("junit.", Test.class.getClassLoader()); + PathableTestSuite suite = new PathableTestSuite(BadTCCLTestCase.class, contextClassLoader); + return suite; + } + + // test methods + + /** + * This test just tests that a log implementation can be found + * by the LogFactory. + */ + public void testGetLog() { + Log log = LogFactory.getLog(BadTCCLTestCase.class); + log.debug("Hello, Mum"); + } +} diff --git a/src/test/java/org/apache/commons/logging/tccl/NullTCCLTestCase.java b/src/test/java/org/apache/commons/logging/tccl/NullTCCLTestCase.java new file mode 100644 index 0000000..ae5ab4a --- /dev/null +++ b/src/test/java/org/apache/commons/logging/tccl/NullTCCLTestCase.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.tccl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableTestSuite; + +import junit.framework.Test; +import junit.framework.TestCase; + +/** + * Simulates the case when TCCL is set to NULL. + */ +public class NullTCCLTestCase extends TestCase { + + public static Test suite() throws Exception { + PathableTestSuite suite = new PathableTestSuite(NullTCCLTestCase.class, null); + return suite; + } + + // test methods + + /** + * This test just tests that a log implementation can be found + * by the LogFactory. + */ + public void testGetLog() { + Log log = LogFactory.getLog(NullTCCLTestCase.class); + log.debug("Hello, Mum"); + } +} diff --git a/src/test/java/org/apache/commons/logging/tccl/custom/MyLog.java b/src/test/java/org/apache/commons/logging/tccl/custom/MyLog.java new file mode 100644 index 0000000..214f7b3 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/tccl/custom/MyLog.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.tccl.custom; + +import org.apache.commons.logging.Log; + +public class MyLog implements Log { + + public MyLog(String category) {} + + public boolean isDebugEnabled() { return false; } + public boolean isErrorEnabled() { return false; } + public boolean isFatalEnabled() { return false; } + public boolean isInfoEnabled() { return false; } + public boolean isTraceEnabled() { return false; } + public boolean isWarnEnabled() { return false; } + + public void trace(Object message) {} + public void trace(Object message, Throwable t) {} + public void debug(Object message) {} + public void debug(Object message, Throwable t) {} + public void info(Object message) {} + public void info(Object message, Throwable t) {} + public void warn(Object message) {} + public void warn(Object message, Throwable t) {} + public void error(Object message) {} + public void error(Object message, Throwable t) {} + public void fatal(Object message) {} + public void fatal(Object message, Throwable t) {} +} diff --git a/src/test/java/org/apache/commons/logging/tccl/custom/MyLogFactoryImpl.java b/src/test/java/org/apache/commons/logging/tccl/custom/MyLogFactoryImpl.java new file mode 100644 index 0000000..63d5ac6 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/tccl/custom/MyLogFactoryImpl.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.tccl.custom; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class MyLogFactoryImpl extends LogFactory { + public Object getAttribute(String name) { return null; } + public String[] getAttributeNames() { return null; } + public Log getInstance(Class clazz) { return null; } + public Log getInstance(String name) { return null; } + public void release() {} + public void removeAttribute(String name) {} + public void setAttribute(String name, Object value) {} +} diff --git a/src/test/java/org/apache/commons/logging/tccl/log/TcclDisabledTestCase.java b/src/test/java/org/apache/commons/logging/tccl/log/TcclDisabledTestCase.java new file mode 100644 index 0000000..152af3c --- /dev/null +++ b/src/test/java/org/apache/commons/logging/tccl/log/TcclDisabledTestCase.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.tccl.log; + + +import java.net.URL; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogConfigurationException; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + + +/** + * Verify that by default LogFactoryImpl is loaded from the tccl classloader. + */ + +public class TcclDisabledTestCase extends TestCase { + + public static final String MY_LOG_PKG = + "org.apache.commons.logging.tccl.custom"; + + public static final String MY_LOG_IMPL = + MY_LOG_PKG + ".MyLog"; + + // ------------------------------------------- JUnit Infrastructure Methods + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + Class thisClass = TcclDisabledTestCase.class; + + // Determine the URL to this .class file, so that we can then + // append the priority dirs to it. For tidiness, load this + // class through a dummy loader though this is not absolutely + // necessary... + PathableClassLoader dummy = new PathableClassLoader(null); + dummy.useExplicitLoader("junit.", Test.class.getClassLoader()); + dummy.addLogicalLib("testclasses"); + dummy.addLogicalLib("commons-logging"); + + String thisClassPath = thisClass.getName().replace('.', '/') + ".class"; + URL baseUrl = dummy.findResource(thisClassPath); + + // Now set up the desired classloader hierarchy. Everything goes into + // the parent classpath, but we exclude the custom Log class. + // + // We then create a tccl classloader that can see the custom + // Log class. Therefore if that class can be found, then the + // TCCL must have been used to load it. + PathableClassLoader emptyLoader = new PathableClassLoader(null); + + PathableClassLoader parentLoader = new PathableClassLoader(null); + parentLoader.useExplicitLoader("junit.", Test.class.getClassLoader()); + parentLoader.addLogicalLib("commons-logging"); + parentLoader.addLogicalLib("testclasses"); + // hack to ensure that the testcase classloader can't see + // the custom MyLog + parentLoader.useExplicitLoader(MY_LOG_PKG + ".", emptyLoader); + + URL propsEnableUrl = new URL(baseUrl, "props_disable_tccl/"); + parentLoader.addURL(propsEnableUrl); + + PathableClassLoader tcclLoader = new PathableClassLoader(parentLoader); + tcclLoader.addLogicalLib("testclasses"); + + Class testClass = parentLoader.loadClass(thisClass.getName()); + return new PathableTestSuite(testClass, tcclLoader); + } + + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + LogFactory.releaseAll(); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + LogFactory.releaseAll(); + } + + // ----------------------------------------------------------- Test Methods + + /** + * Verify that MyLog is only loadable via the tccl. + */ + public void testLoader() throws Exception { + + ClassLoader thisClassLoader = this.getClass().getClassLoader(); + ClassLoader tcclLoader = Thread.currentThread().getContextClassLoader(); + + // the tccl loader should NOT be the same as the loader that loaded this test class. + assertNotSame("tccl not same as test classloader", thisClassLoader, tcclLoader); + + // MyLog should not be loadable via parent loader + try { + Class clazz = thisClassLoader.loadClass(MY_LOG_IMPL); + fail("Unexpectedly able to load MyLog via test class classloader"); + assertNotNull(clazz); // silence warnings about unused var + } catch(ClassNotFoundException ex) { + // ok, expected + } + + // MyLog should be loadable via tccl loader + try { + Class clazz = tcclLoader.loadClass(MY_LOG_IMPL); + assertNotNull(clazz); + } catch(ClassNotFoundException ex) { + fail("Unexpectedly unable to load MyLog via tccl classloader"); + } + } + + /** + * Verify that the custom Log implementation which is only accessable + * via the TCCL has NOT been loaded. Because this is only accessable via the + * TCCL, and we've use a commons-logging.properties that disables TCCL loading, + * we should see the default Log rather than the custom one. + */ + public void testTcclLoading() throws Exception { + LogFactory instance = LogFactory.getFactory(); + assertEquals( + "Correct LogFactory loaded", + "org.apache.commons.logging.impl.LogFactoryImpl", + instance.getClass().getName()); + + try { + Log log = instance.getInstance("test"); + fail("Unexpectedly succeeded in loading a custom Log class" + + " that is only accessable via the tccl."); + assertNotNull(log); // silence compiler warning about unused var + } catch(LogConfigurationException ex) { + // ok, expected + int index = ex.getMessage().indexOf(MY_LOG_IMPL); + assertTrue("MyLog not found", index >= 0); + } + } +} diff --git a/src/test/java/org/apache/commons/logging/tccl/log/TcclEnabledTestCase.java b/src/test/java/org/apache/commons/logging/tccl/log/TcclEnabledTestCase.java new file mode 100644 index 0000000..692b53e --- /dev/null +++ b/src/test/java/org/apache/commons/logging/tccl/log/TcclEnabledTestCase.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.tccl.log; + + +import java.net.URL; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + + +/** + * Verify that by default the standard LogFactoryImpl class loads a + * custom Log implementation via the TCCL. + */ + +public class TcclEnabledTestCase extends TestCase { + + public static final String MY_LOG_PKG = + "org.apache.commons.logging.tccl.custom"; + + public static final String MY_LOG_IMPL = + MY_LOG_PKG + ".MyLog"; + + // ------------------------------------------- JUnit Infrastructure Methods + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + Class thisClass = TcclEnabledTestCase.class; + + // Determine the URL to this .class file, so that we can then + // append the priority dirs to it. For tidiness, load this + // class through a dummy loader though this is not absolutely + // necessary... + PathableClassLoader dummy = new PathableClassLoader(null); + dummy.useExplicitLoader("junit.", Test.class.getClassLoader()); + dummy.addLogicalLib("testclasses"); + dummy.addLogicalLib("commons-logging"); + + String thisClassPath = thisClass.getName().replace('.', '/') + ".class"; + URL baseUrl = dummy.findResource(thisClassPath); + + // Now set up the desired classloader hierarchy. Everything goes into + // the parent classpath, but we exclude the custom Log class. + // + // We then create a tccl classloader that can see the custom + // Log class. Therefore if that class can be found, then the + // TCCL must have been used to load it. + PathableClassLoader emptyLoader = new PathableClassLoader(null); + + PathableClassLoader parentLoader = new PathableClassLoader(null); + parentLoader.useExplicitLoader("junit.", Test.class.getClassLoader()); + parentLoader.addLogicalLib("commons-logging"); + parentLoader.addLogicalLib("testclasses"); + // hack to ensure that the testcase classloader can't see + // the custom MyLogFactoryImpl + parentLoader.useExplicitLoader(MY_LOG_PKG + ".", emptyLoader); + + URL propsEnableUrl = new URL(baseUrl, "props_enable_tccl/"); + parentLoader.addURL(propsEnableUrl); + + PathableClassLoader tcclLoader = new PathableClassLoader(parentLoader); + tcclLoader.addLogicalLib("testclasses"); + + Class testClass = parentLoader.loadClass(thisClass.getName()); + return new PathableTestSuite(testClass, tcclLoader); + } + + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + LogFactory.releaseAll(); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + LogFactory.releaseAll(); + } + + // ----------------------------------------------------------- Test Methods + + /** + * Verify that MyLogFactoryImpl is only loadable via the tccl. + */ + public void testLoader() throws Exception { + + ClassLoader thisClassLoader = this.getClass().getClassLoader(); + ClassLoader tcclLoader = Thread.currentThread().getContextClassLoader(); + + // the tccl loader should NOT be the same as the loader that loaded this test class. + assertNotSame("tccl not same as test classloader", thisClassLoader, tcclLoader); + + // MyLog should not be loadable via parent loader + try { + Class clazz = thisClassLoader.loadClass(MY_LOG_IMPL); + fail("Unexpectedly able to load MyLog via test class classloader"); + assertNotNull(clazz); // silence warnings about unused var + } catch(ClassNotFoundException ex) { + // ok, expected + } + + // MyLog should be loadable via tccl loader + try { + Class clazz = tcclLoader.loadClass(MY_LOG_IMPL); + assertNotNull(clazz); + } catch(ClassNotFoundException ex) { + fail("Unexpectedly unable to load MyLog via tccl classloader"); + } + } + + /** + * Verify that the custom Log implementation which is only accessable + * via the TCCL has successfully been loaded as specified in the config file. + * This proves that the TCCL was used to load that class. + */ + public void testTcclLoading() throws Exception { + LogFactory instance = LogFactory.getFactory(); + + assertEquals( + "Correct LogFactory loaded", + "org.apache.commons.logging.impl.LogFactoryImpl", + instance.getClass().getName()); + + Log log = instance.getInstance("test"); + assertEquals( + "Correct Log loaded", + MY_LOG_IMPL, + log.getClass().getName()); + } +} diff --git a/src/test/java/org/apache/commons/logging/tccl/logfactory/TcclDisabledTestCase.java b/src/test/java/org/apache/commons/logging/tccl/logfactory/TcclDisabledTestCase.java new file mode 100644 index 0000000..fc577e3 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/tccl/logfactory/TcclDisabledTestCase.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.tccl.logfactory; + + +import java.net.URL; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + + +/** + * Verify that a commons-logging.properties file can prevent a custom + * LogFactoryImpl being loaded from the tccl classloader. + */ + +public class TcclDisabledTestCase extends TestCase { + + public static final String MY_LOG_FACTORY_PKG = + "org.apache.commons.logging.tccl.custom"; + + public static final String MY_LOG_FACTORY_IMPL = + MY_LOG_FACTORY_PKG + ".MyLogFactoryImpl"; + + // ------------------------------------------- JUnit Infrastructure Methods + + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + Class thisClass = TcclDisabledTestCase.class; + + // Determine the URL to this .class file, so that we can then + // append the priority dirs to it. For tidiness, load this + // class through a dummy loader though this is not absolutely + // necessary... + PathableClassLoader dummy = new PathableClassLoader(null); + dummy.useExplicitLoader("junit.", Test.class.getClassLoader()); + dummy.addLogicalLib("testclasses"); + dummy.addLogicalLib("commons-logging"); + + String thisClassPath = thisClass.getName().replace('.', '/') + ".class"; + URL baseUrl = dummy.findResource(thisClassPath); + + // Now set up the desired classloader hierarchy. Everything goes into + // the parent classpath, but we exclude the custom LogFactoryImpl + // class. + // + // We then create a tccl classloader that can see the custom + // LogFactory class. Therefore if that class can be found, then the + // TCCL must have been used to load it. + PathableClassLoader emptyLoader = new PathableClassLoader(null); + + PathableClassLoader parentLoader = new PathableClassLoader(null); + parentLoader.useExplicitLoader("junit.", Test.class.getClassLoader()); + parentLoader.addLogicalLib("commons-logging"); + parentLoader.addLogicalLib("testclasses"); + // hack to ensure that the testcase classloader can't see + // the custom MyLogFactoryImpl + parentLoader.useExplicitLoader( + MY_LOG_FACTORY_PKG + ".", emptyLoader); + + URL propsEnableUrl = new URL(baseUrl, "props_disable_tccl/"); + parentLoader.addURL(propsEnableUrl); + + PathableClassLoader tcclLoader = new PathableClassLoader(parentLoader); + tcclLoader.addLogicalLib("testclasses"); + + Class testClass = parentLoader.loadClass(thisClass.getName()); + return new PathableTestSuite(testClass, tcclLoader); + } + + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + LogFactory.releaseAll(); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + LogFactory.releaseAll(); + } + + // ----------------------------------------------------------- Test Methods + + /** + * Verify that MyLogFactoryImpl is only loadable via the tccl. + */ + public void testLoader() throws Exception { + + ClassLoader thisClassLoader = this.getClass().getClassLoader(); + ClassLoader tcclLoader = Thread.currentThread().getContextClassLoader(); + + // the tccl loader should NOT be the same as the loader that loaded this test class. + assertNotSame("tccl not same as test classloader", thisClassLoader, tcclLoader); + + // MyLogFactoryImpl should not be loadable via parent loader + try { + Class clazz = thisClassLoader.loadClass(MY_LOG_FACTORY_IMPL); + fail("Unexpectedly able to load MyLogFactoryImpl via test class classloader"); + assertNotNull(clazz); // silence warning about unused var + } catch(ClassNotFoundException ex) { + // ok, expected + } + + // MyLogFactoryImpl should be loadable via tccl loader + try { + Class clazz = tcclLoader.loadClass(MY_LOG_FACTORY_IMPL); + assertNotNull(clazz); + } catch(ClassNotFoundException ex) { + fail("Unexpectedly unable to load MyLogFactoryImpl via tccl classloader"); + } + } + + /** + * Verify that the custom LogFactory implementation which is only accessable + * via the TCCL has NOT been loaded. Because this is only accessable via the + * TCCL, and we've use a commons-logging.properties that disables TCCL loading, + * we should see the default LogFactoryImpl rather than the custom one. + */ + public void testTcclLoading() throws Exception { + try { + LogFactory instance = LogFactory.getFactory(); + fail("Unexpectedly succeeded in loading custom factory, though TCCL disabled."); + assertNotNull(instance); // silence warning about unused var + } catch(org.apache.commons.logging.LogConfigurationException ex) { + // ok, custom MyLogFactoryImpl as specified in props_disable_tccl + // could not be found. + int index = ex.getMessage().indexOf(MY_LOG_FACTORY_IMPL); + assertTrue("MylogFactoryImpl not found", index >= 0); + } + } +} diff --git a/src/test/java/org/apache/commons/logging/tccl/logfactory/TcclEnabledTestCase.java b/src/test/java/org/apache/commons/logging/tccl/logfactory/TcclEnabledTestCase.java new file mode 100644 index 0000000..58d746b --- /dev/null +++ b/src/test/java/org/apache/commons/logging/tccl/logfactory/TcclEnabledTestCase.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.tccl.logfactory; + + +import java.net.URL; + +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + + +/** + * Verify that by default a custom LogFactoryImpl is loaded from the + * tccl classloader. + */ + +public class TcclEnabledTestCase extends TestCase { + + // ------------------------------------------- JUnit Infrastructure Methods + + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + Class thisClass = TcclEnabledTestCase.class; + + // Determine the URL to this .class file, so that we can then + // append the priority dirs to it. For tidiness, load this + // class through a dummy loader though this is not absolutely + // necessary... + PathableClassLoader dummy = new PathableClassLoader(null); + dummy.useExplicitLoader("junit.", Test.class.getClassLoader()); + dummy.addLogicalLib("testclasses"); + dummy.addLogicalLib("commons-logging"); + + String thisClassPath = thisClass.getName().replace('.', '/') + ".class"; + URL baseUrl = dummy.findResource(thisClassPath); + + // Now set up the desired classloader hierarchy. Everything goes into + // the parent classpath, but we exclude the custom LogFactoryImpl + // class. + // + // We then create a tccl classloader that can see the custom + // LogFactory class. Therefore if that class can be found, then the + // TCCL must have been used to load it. + PathableClassLoader emptyLoader = new PathableClassLoader(null); + + PathableClassLoader parentLoader = new PathableClassLoader(null); + parentLoader.useExplicitLoader("junit.", Test.class.getClassLoader()); + parentLoader.addLogicalLib("commons-logging"); + parentLoader.addLogicalLib("testclasses"); + // hack to ensure that the testcase classloader can't see + // the cust MyLogFactoryImpl + parentLoader.useExplicitLoader( + "org.apache.commons.logging.tccl.custom.", emptyLoader); + + URL propsEnableUrl = new URL(baseUrl, "props_enable_tccl/"); + parentLoader.addURL(propsEnableUrl); + + PathableClassLoader tcclLoader = new PathableClassLoader(parentLoader); + tcclLoader.addLogicalLib("testclasses"); + + Class testClass = parentLoader.loadClass(thisClass.getName()); + return new PathableTestSuite(testClass, tcclLoader); + } + + /** + * Set up instance variables required by this test case. + */ + public void setUp() throws Exception { + LogFactory.releaseAll(); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + LogFactory.releaseAll(); + } + + // ----------------------------------------------------------- Test Methods + + /** + * Verify that MyLogFactoryImpl is only loadable via the tccl. + */ + public void testLoader() throws Exception { + + ClassLoader thisClassLoader = this.getClass().getClassLoader(); + ClassLoader tcclLoader = Thread.currentThread().getContextClassLoader(); + + // the tccl loader should NOT be the same as the loader that loaded this test class. + assertNotSame("tccl not same as test classloader", thisClassLoader, tcclLoader); + + // MyLogFactoryImpl should not be loadable via parent loader + try { + Class clazz = thisClassLoader.loadClass( + "org.apache.commons.logging.tccl.custom.MyLogFactoryImpl"); + fail("Unexpectedly able to load MyLogFactoryImpl via test class classloader"); + assertNotNull(clazz); // silence warning about unused var + } catch(ClassNotFoundException ex) { + // ok, expected + } + + // MyLogFactoryImpl should be loadable via tccl loader + try { + Class clazz = tcclLoader.loadClass( + "org.apache.commons.logging.tccl.custom.MyLogFactoryImpl"); + assertNotNull(clazz); + } catch(ClassNotFoundException ex) { + fail("Unexpectedly unable to load MyLogFactoryImpl via tccl classloader"); + } + } + + /** + * Verify that the custom LogFactory implementation which is only accessable + * via the TCCL has successfully been loaded as specified in the config file. + * This proves that the TCCL was used to load that class. + */ + public void testTcclLoading() throws Exception { + LogFactory instance = LogFactory.getFactory(); + + assertEquals( + "Correct LogFactory loaded", + "org.apache.commons.logging.tccl.custom.MyLogFactoryImpl", + instance.getClass().getName()); + } +} diff --git a/src/test/resources/org/apache/commons/logging/config/nopriority/commons-logging.properties b/src/test/resources/org/apache/commons/logging/config/nopriority/commons-logging.properties new file mode 100644 index 0000000..98d1177 --- /dev/null +++ b/src/test/resources/org/apache/commons/logging/config/nopriority/commons-logging.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +configId=nopriority diff --git a/src/test/resources/org/apache/commons/logging/config/priority10/commons-logging.properties b/src/test/resources/org/apache/commons/logging/config/priority10/commons-logging.properties new file mode 100644 index 0000000..fa10f1a --- /dev/null +++ b/src/test/resources/org/apache/commons/logging/config/priority10/commons-logging.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +priority=10.5 +configId=priority10 diff --git a/src/test/resources/org/apache/commons/logging/config/priority20/commons-logging.properties b/src/test/resources/org/apache/commons/logging/config/priority20/commons-logging.properties new file mode 100644 index 0000000..4ead91f --- /dev/null +++ b/src/test/resources/org/apache/commons/logging/config/priority20/commons-logging.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +priority=20.6 +configId=priority20 diff --git a/src/test/resources/org/apache/commons/logging/config/priority20a/commons-logging.properties b/src/test/resources/org/apache/commons/logging/config/priority20a/commons-logging.properties new file mode 100644 index 0000000..6a91d83 --- /dev/null +++ b/src/test/resources/org/apache/commons/logging/config/priority20a/commons-logging.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +priority=20.6 +configId=priority20a diff --git a/src/test/resources/org/apache/commons/logging/jdk14/CustomConfig.properties b/src/test/resources/org/apache/commons/logging/jdk14/CustomConfig.properties new file mode 100644 index 0000000..2a5e0e7 --- /dev/null +++ b/src/test/resources/org/apache/commons/logging/jdk14/CustomConfig.properties @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + + +# This is the custom configuration properties for the JDK 1.4 logger tests +# in CustomConfigTestCase. + +# Configure the Handler so we can examine the logged messages +handlers = org.apache.commons.logging.jdk14.TestHandler + +# Configre the default logging level to be FINE so we should get +# everything except trace messages +.level = FINE diff --git a/src/test/resources/org/apache/commons/logging/tccl/log/props_disable_tccl/commons-logging.properties b/src/test/resources/org/apache/commons/logging/tccl/log/props_disable_tccl/commons-logging.properties new file mode 100644 index 0000000..8f55b92 --- /dev/null +++ b/src/test/resources/org/apache/commons/logging/tccl/log/props_disable_tccl/commons-logging.properties @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +use_tccl=false +org.apache.commons.logging.Log=org.apache.commons.logging.tccl.custom.MyLog +org.apache.commons.logging.diagnostics.dest=STDERR \ No newline at end of file diff --git a/src/test/resources/org/apache/commons/logging/tccl/log/props_enable_tccl/commons-logging.properties b/src/test/resources/org/apache/commons/logging/tccl/log/props_enable_tccl/commons-logging.properties new file mode 100644 index 0000000..440c764 --- /dev/null +++ b/src/test/resources/org/apache/commons/logging/tccl/log/props_enable_tccl/commons-logging.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +org.apache.commons.logging.Log=org.apache.commons.logging.tccl.custom.MyLog +org.apache.commons.logging.diagnostics.dest=STDERR \ No newline at end of file diff --git a/src/test/resources/org/apache/commons/logging/tccl/logfactory/props_disable_tccl/commons-logging.properties b/src/test/resources/org/apache/commons/logging/tccl/logfactory/props_disable_tccl/commons-logging.properties new file mode 100644 index 0000000..9d21a8c --- /dev/null +++ b/src/test/resources/org/apache/commons/logging/tccl/logfactory/props_disable_tccl/commons-logging.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +use_tccl=false +org.apache.commons.logging.LogFactory=org.apache.commons.logging.tccl.custom.MyLogFactoryImpl diff --git a/src/test/resources/org/apache/commons/logging/tccl/logfactory/props_enable_tccl/commons-logging.properties b/src/test/resources/org/apache/commons/logging/tccl/logfactory/props_enable_tccl/commons-logging.properties new file mode 100644 index 0000000..29bc41a --- /dev/null +++ b/src/test/resources/org/apache/commons/logging/tccl/logfactory/props_enable_tccl/commons-logging.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +org.apache.commons.logging.LogFactory=org.apache.commons.logging.tccl.custom.MyLogFactoryImpl