diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 75bcb82..fae857a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,85 +1,85 @@ -# 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. - -name: "CodeQL" - -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '33 9 * * 4' - -permissions: - contents: read - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'java' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support - - steps: - - name: Checkout repository - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 +# 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. + +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '33 9 * * 4' + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 with: persist-credentials: false - - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL + - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL uses: github/codeql-action/init@00e563ead9f72a8461b24876bee2d0c2e8bd2ee8 # v2.21.5 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild uses: github/codeql-action/autobuild@00e563ead9f72a8461b24876bee2d0c2e8bd2ee8 # v2.21.5 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@00e563ead9f72a8461b24876bee2d0c2e8bd2ee8 # v2.21.5 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index e5cd804..a032eaa 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -1,52 +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. - -name: Java CI - -on: [push, pull_request] - -permissions: - contents: read - -jobs: - build: - - runs-on: ubuntu-latest - continue-on-error: ${{ matrix.experimental }} - strategy: - matrix: - java: [ 8, 11, 17 ] - experimental: [false] -# include: -# - java: 18-ea -# experimental: true - - steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 +# 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. + +name: Java CI + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental }} + strategy: + matrix: + java: [ 8, 11, 17 ] + experimental: [false] +# include: +# - java: 18-ea +# experimental: true + + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 with: persist-credentials: false - - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - name: Set up JDK ${{ matrix.java }} + - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0 - with: - distribution: 'temurin' - java-version: ${{ matrix.java }} - - name: Build with Maven - run: mvn -V --no-transfer-progress -D doclint=none --file pom.xml + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + - name: Build with Maven + run: mvn -V --no-transfer-progress -D doclint=none --file pom.xml diff --git a/README.md b/README.md index d8c2a74..75f8979 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,103 @@ - - -Apache Commons Logging -=================== -[![GitHub Actions Status](https://github.com/apache/commons-logging/workflows/Java%20CI/badge.svg)](https://github.com/apache/commons-logging/actions) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/commons-logging/commons-logging/badge.svg?gav=true)](https://maven-badges.herokuapp.com/maven-central/commons-logging/commons-logging/?gav=true) -[![CodeQL](https://github.com/apache/commons-logging/workflows/CodeQL/badge.svg)](https://github.com/apache/commons-logging/actions/workflows/codeql-analysis.yml?query=workflow%3ACodeQL) - -Apache Commons Logging is a thin adapter allowing configurable bridging to other, well known logging systems. - -Documentation -------------- - -More information can be found on the [Apache Commons Logging homepage](https://commons.apache.org/proper/commons-logging). -The [Javadoc](https://commons.apache.org/proper/commons-logging/javadocs/api-release) can be browsed. -Questions related to the usage of Apache Commons Logging should be posted to the [user mailing list][ml]. - -Where can I get the latest release? ------------------------------------ -You can download source and binaries from our [download page](https://commons.apache.org/proper/commons-logging/download_logging.cgi). - -Alternatively, you can pull it from the central Maven repositories: - -```xml - - commons-logging - commons-logging - 1.2 - -``` - -Contributing ------------- - -We accept Pull Requests via GitHub. The [developer mailing list][ml] is the main channel of communication for contributors. -There are some guidelines which will make applying PRs easier for us: -+ No tabs! Please use spaces for indentation. -+ Respect the code style. -+ Create minimal diffs - disable on save actions like reformat source code or organize imports. If you feel the source code should be reformatted create a separate PR for this change. -+ Provide JUnit tests for your changes and make sure your changes don't break any existing tests by running ```mvn```. - -If you plan to contribute on a regular basis, please consider filing a [contributor license agreement](https://www.apache.org/licenses/#clas). -You can learn more about contributing via GitHub in our [contribution guidelines](CONTRIBUTING.md). - -License -------- -This code is under the [Apache License v2](https://www.apache.org/licenses/LICENSE-2.0). - -See the `NOTICE.txt` file for required notices and attributions. - -Donations ---------- -You like Apache Commons Logging? Then [donate back to the ASF](https://www.apache.org/foundation/contributing.html) to support the development. - -Additional Resources --------------------- - -+ [Apache Commons Homepage](https://commons.apache.org/) -+ [Apache Issue Tracker (JIRA)](https://issues.apache.org/jira/browse/LOGGING) -+ [Apache Commons Twitter Account](https://twitter.com/ApacheCommons) -+ `#apache-commons` IRC channel on `irc.freenode.org` - -[ml]:https://commons.apache.org/mail-lists.html + + +Apache Commons Logging +=================== +[![GitHub Actions Status](https://github.com/apache/commons-logging/workflows/Java%20CI/badge.svg)](https://github.com/apache/commons-logging/actions) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/commons-logging/commons-logging/badge.svg?gav=true)](https://maven-badges.herokuapp.com/maven-central/commons-logging/commons-logging/?gav=true) +[![CodeQL](https://github.com/apache/commons-logging/workflows/CodeQL/badge.svg)](https://github.com/apache/commons-logging/actions/workflows/codeql-analysis.yml?query=workflow%3ACodeQL) + +Apache Commons Logging is a thin adapter allowing configurable bridging to other, well known logging systems. + +Documentation +------------- + +More information can be found on the [Apache Commons Logging homepage](https://commons.apache.org/proper/commons-logging). +The [Javadoc](https://commons.apache.org/proper/commons-logging/javadocs/api-release) can be browsed. +Questions related to the usage of Apache Commons Logging should be posted to the [user mailing list][ml]. + +Where can I get the latest release? +----------------------------------- +You can download source and binaries from our [download page](https://commons.apache.org/proper/commons-logging/download_logging.cgi). + +Alternatively, you can pull it from the central Maven repositories: + +```xml + + commons-logging + commons-logging + 1.2 + +``` + +Contributing +------------ + +We accept Pull Requests via GitHub. The [developer mailing list][ml] is the main channel of communication for contributors. +There are some guidelines which will make applying PRs easier for us: ++ No tabs! Please use spaces for indentation. ++ Respect the code style. ++ Create minimal diffs - disable on save actions like reformat source code or organize imports. If you feel the source code should be reformatted create a separate PR for this change. ++ Provide JUnit tests for your changes and make sure your changes don't break any existing tests by running ```mvn```. + +If you plan to contribute on a regular basis, please consider filing a [contributor license agreement](https://www.apache.org/licenses/#clas). +You can learn more about contributing via GitHub in our [contribution guidelines](CONTRIBUTING.md). + +License +------- +This code is under the [Apache License v2](https://www.apache.org/licenses/LICENSE-2.0). + +See the `NOTICE.txt` file for required notices and attributions. + +Donations +--------- +You like Apache Commons Logging? Then [donate back to the ASF](https://www.apache.org/foundation/contributing.html) to support the development. + +Additional Resources +-------------------- + ++ [Apache Commons Homepage](https://commons.apache.org/) ++ [Apache Issue Tracker (JIRA)](https://issues.apache.org/jira/browse/LOGGING) ++ [Apache Commons Twitter Account](https://twitter.com/ApacheCommons) ++ `#apache-commons` IRC channel on `irc.freenode.org` + +[ml]:https://commons.apache.org/mail-lists.html diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 367a27b..f1326d6 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -1,191 +1,191 @@ - - - - - - - Release Notes - - - - - - Add Automatic-Module-Name Manifest Header for Java 9 compatibility. - - - BufferedReader is not closed properly. - - - Remove redundant initializer #46 - - - Use a weak reference for the cached class loader #71. - - - Add more entries to .gitignore file #25. - - - Minor Improvements #34. - - - [StepSecurity] ci: Harden GitHub Actions #145. - - - - Bump Java from 6 to 8. - - - Bump actions/cache from 2 to 3.0.10 #50, #77, #95, #98, #107, #110. - - - Bump actions/checkout from 2.3.1 to 3.1.0, #24, #63, #75, #109. - - - Bump actions/setup-java from 1.4.0 to 3.5.1 #21. - - - Bump junit from 3.8.1 to 5.9.1 Vintage #23, #38. - - - Bump clirr-maven-plugin from 2.2.2 to 2.8 #14. - - - Bump findbugs-maven-plugin from 2.5.2 to 3.0.5 #8. - - - Bump maven-failsafe-plugin from 2.12 to 3.0.0-M7 #31, #91. - - - Bump spotbugs-surefire-plugin from 2.22.2 to 3.0.0-M5. - - - Bump maven-dependency-plugin from 2.4 to 3.5.0 #29, #79, #117, #131. - - - Bump maven-pmd-plugin from 3.3 to 3.19.0 #28, #90, #100, #103. - - - Bump maven-assembly-plugin from 2.5 to 3.4.2 #30, #94, #96. - - - Bump servlet-api from 2.3 to 2.5 #27. - - - Bump maven-checkstyle-plugin from 2.7 to 3.2.1 #35, #101, #130. - - - Bump spotbugs-maven-plugin from 4.1.4 to 4.7.2.1 #33, #70, #78, #93, #97, #102, #112. - - - Bump spotbugs from 4.5.3 to 4.7.3 #81, #87, #92, #104, #115. - - - Bump commons-parent from 52 to 59 #82, #106, #122, #126, #143. - - - Bump github/codeql-action from 1 to 2 #86. - - - - - 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. - - - - + + + + + + + Release Notes + + + + + + Add Automatic-Module-Name Manifest Header for Java 9 compatibility. + + + BufferedReader is not closed properly. + + + Remove redundant initializer #46 + + + Use a weak reference for the cached class loader #71. + + + Add more entries to .gitignore file #25. + + + Minor Improvements #34. + + + [StepSecurity] ci: Harden GitHub Actions #145. + + + + Bump Java from 6 to 8. + + + Bump actions/cache from 2 to 3.0.10 #50, #77, #95, #98, #107, #110. + + + Bump actions/checkout from 2.3.1 to 3.1.0, #24, #63, #75, #109. + + + Bump actions/setup-java from 1.4.0 to 3.5.1 #21. + + + Bump junit from 3.8.1 to 5.9.1 Vintage #23, #38. + + + Bump clirr-maven-plugin from 2.2.2 to 2.8 #14. + + + Bump findbugs-maven-plugin from 2.5.2 to 3.0.5 #8. + + + Bump maven-failsafe-plugin from 2.12 to 3.0.0-M7 #31, #91. + + + Bump spotbugs-surefire-plugin from 2.22.2 to 3.0.0-M5. + + + Bump maven-dependency-plugin from 2.4 to 3.5.0 #29, #79, #117, #131. + + + Bump maven-pmd-plugin from 3.3 to 3.19.0 #28, #90, #100, #103. + + + Bump maven-assembly-plugin from 2.5 to 3.4.2 #30, #94, #96. + + + Bump servlet-api from 2.3 to 2.5 #27. + + + Bump maven-checkstyle-plugin from 2.7 to 3.2.1 #35, #101, #130. + + + Bump spotbugs-maven-plugin from 4.1.4 to 4.7.2.1 #33, #70, #78, #93, #97, #102, #112. + + + Bump spotbugs from 4.5.3 to 4.7.3 #81, #87, #92, #104, #115. + + + Bump commons-parent from 52 to 59 #82, #106, #122, #126, #143. + + + Bump github/codeql-action from 1 to 2 #86. + + + + + 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/main/java/org/apache/commons/logging/LogFactory.java b/src/main/java/org/apache/commons/logging/LogFactory.java index 7f8fdef..afa510b 100644 --- a/src/main/java/org/apache/commons/logging/LogFactory.java +++ b/src/main/java/org/apache/commons/logging/LogFactory.java @@ -1,1706 +1,1706 @@ -/* - * 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.lang.ref.WeakReference; -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. - */ -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 ({@code 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 ({@code 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 ({@code 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 {@code 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 ({@code 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 ({@code 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 final PrintStream DIAGNOSTICS_STREAM; - - /** - * 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 - * ({@code org.apache.commons.logging.LogFactory.HashtableImpl}) - * value allows the {@code Hashtable} used to store - * classloaders to be substituted by an alternative implementation. - *

- * Note: {@code 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 {@code 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 {@code LogFactory} will load an instance of - * {@code 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 WeakReference thisClassLoaderRef; - - // ----------------------------------------------------------- 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 {@code 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 {@code getInstance(String)} with it. - * - * @param clazz Class for which a suitable Log name will be derived - * @throws LogConfigurationException if a suitable {@code Log} - * instance cannot be returned - */ - public abstract Log getInstance(Class clazz) - throws LogConfigurationException; - - /** - * Construct (if necessary) and return a {@code Log} instance, - * using the factory's current set of configuration attributes. - *

- * NOTE - Depending upon the implementation of - * the {@code LogFactory} you are using, the {@code 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 {@code 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 {@code 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 {@code null} value is equivalent to calling - * {@code removeAttribute(name)}. - * - * @param name Name of the attribute to set - * @param value Value of the attribute to set, or {@code null} - * to remove any setting for this attribute - */ - public abstract void setAttribute(String name, Object value); - - // ------------------------------------------------------- Static Variables - - /** - * The previously constructed {@code LogFactory} instances, keyed by - * the {@code ClassLoader} with which it was created. - */ - protected static Hashtable factories; - - /** - * Previously constructed {@code LogFactory} instance as in the - * {@code factories} map, but for the case where - * {@code getClassLoader} returns {@code null}. - * This can happen when: - *

- * Note that {@code factories} is a Hashtable (not a HashMap), - * and hashtables don't allow null as a key. - * @deprecated since 1.1.2 - */ - @Deprecated - protected static volatile LogFactory nullClassLoaderFactory; - - /** - * 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 (final 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 { - final Class implementationClass = Class.forName(storeImplementationClass); - result = (Hashtable) implementationClass.newInstance(); - } catch (final 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(final 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: - *

- * - * @param t the Throwable to check - */ - protected static void handleThrowable(final 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 {@code LogFactory} - * instance, using the following ordered lookup procedure to determine - * the name of the implementation class to be loaded. - *

- *

- *

- * NOTE - If the properties file method of identifying the - * {@code LogFactory} implementation class is utilized, all of the - * properties defined in this file will be set as configuration attributes - * on the corresponding {@code 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 - final ClassLoader contextClassLoader = getContextClassLoaderInternal(); - - - // 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 (contextClassLoader == null && 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. - - final 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) { - final String useTCCLStr = props.getProperty(TCCL_KEY); - // The Boolean.valueOf(useTCCLStr).booleanValue() formulation - // is required for Java 1.2 compatibility. - if ((useTCCLStr != null) && !Boolean.parseBoolean(useTCCLStr)) { - // 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 = thisClassLoaderRef.get(); - } - } - - // 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 { - final 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 (final 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 (final RuntimeException e) { - // This is not consistent with the behavior 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 (final java.io.UnsupportedEncodingException e) { - rd = new BufferedReader(new InputStreamReader(is)); - } - - String factoryClassName; - try { - factoryClassName = rd.readLine(); - } finally { - rd.close(); - } - - if (factoryClassName != null && ! factoryClassName.isEmpty()) { - 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 (final 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..."); - } - final 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, thisClassLoaderRef.get(), contextClassLoader); - } - - if (factory != null) { - /** - * Always cache using context class loader. - */ - cacheFactory(contextClassLoader, factory); - - if (props != null) { - final Enumeration names = props.propertyNames(); - while (names.hasMoreElements()) { - final String name = (String) names.nextElement(); - final 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 {@code Log} - * instance cannot be returned - */ - public static Log getLog(final 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 {@code 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 {@code Log} - * instance cannot be returned - */ - public static Log getLog(final 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 {@code release()} on - * each of them. - * - * @param classLoader ClassLoader for which to release the LogFactory - */ - public static void release(final 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 {@code 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()) { - final 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(final Class clazz) { - try { - return clazz.getClassLoader(); - } catch (final 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() { - @Override - 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 (final 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(final 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; - } - 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(final ClassLoader classLoader, final 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 {@code 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 {@code 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. - final Object result = AccessController.doPrivileged( - new PrivilegedAction() { - @Override - public Object run() { - return createFactory(factoryClass, classLoader); - } - }); - - if (result instanceof LogConfigurationException) { - final 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(final String factoryClass, final 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 (final ClassNotFoundException ex) { - if (classLoader == thisClassLoaderRef.get()) { - // 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 (final NoClassDefFoundError e) { - if (classLoader == thisClassLoaderRef.get()) { - // 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 (final ClassCastException e) { - if (classLoader == thisClassLoaderRef.get()) { - // There's no point in falling through to the code below that - // tries again with thisClassLoaderRef, 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 StringBuilder msg = new StringBuilder(); - 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 thisClassLoaderRef. - // 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 (final 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 {@code 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 {@code LogFactory} class loaded by the class's classloader. - * @param logFactoryClass {@code Class} which may implement {@code LogFactory} - * @return true if the {@code logFactoryClass} does extend - * {@code LogFactory} when that class is loaded via the same - * classloader that loaded the {@code logFactoryClass}. - */ - private static boolean implementsLogFactory(final Class logFactoryClass) { - boolean implementsLogFactory = false; - if (logFactoryClass != null) { - try { - final ClassLoader logFactoryClassLoader = logFactoryClass.getClassLoader(); - if (logFactoryClassLoader == null) { - logDiagnostic("[CUSTOM LOG FACTORY] was loaded by the boot classloader"); - } else { - logHierarchy("[CUSTOM LOG FACTORY] ", logFactoryClassLoader); - final 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 (final 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 (final 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 (final 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() { - @Override - public Object run() { - if (loader != null) { - return loader.getResourceAsStream(name); - } - 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) { - final PrivilegedAction action = - new PrivilegedAction() { - @Override - public Object run() { - try { - if (loader != null) { - return loader.getResources(name); - } - return ClassLoader.getSystemResources(name); - } catch (final IOException e) { - if (isDiagnosticsEnabled()) { - logDiagnostic("Exception while trying to find configuration file " + - name + ":" + e.getMessage()); - } - return null; - } catch (final 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; - } - } - }; - final 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) { - final PrivilegedAction action = - new PrivilegedAction() { - @Override - public Object run() { - InputStream stream = null; - try { - // We must ensure that useCaches is set to false, as the - // default behavior of java is to cache file handles, and - // this "locks" files, preventing hot-redeploy on windows. - final URLConnection connection = url.openConnection(); - connection.setUseCaches(false); - stream = connection.getInputStream(); - if (stream != null) { - final Properties props = new Properties(); - props.load(stream); - stream.close(); - stream = null; - return props; - } - } catch (final IOException e) { - if (isDiagnosticsEnabled()) { - logDiagnostic("Unable to read URL " + url); - } - } finally { - if (stream != null) { - try { - stream.close(); - } catch (final 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(final ClassLoader classLoader, final String fileName) { - Properties props = null; - double priority = 0.0; - URL propsUrl = null; - try { - final Enumeration urls = getResources(classLoader, fileName); - - if (urls == null) { - return null; - } - - while (urls.hasMoreElements()) { - final URL url = (URL) urls.nextElement(); - - final Properties newProps = getProperties(url); - if (newProps != null) { - if (props == null) { - propsUrl = url; - props = newProps; - final 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 { - final 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 (final 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() { - @Override - 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 (final 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; - } - if (dest.equals("STDERR")) { - return System.err; - } - try { - // open the file in append mode - final FileOutputStream fos = new FileOutputStream(dest, true); - return new PrintStream(fos); - } catch (final 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 DIAGNOSTICS_STREAM != 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(final String msg) { - if (DIAGNOSTICS_STREAM != null) { - DIAGNOSTICS_STREAM.print(diagnosticPrefix); - DIAGNOSTICS_STREAM.println(msg); - DIAGNOSTICS_STREAM.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(final String msg) { - if (DIAGNOSTICS_STREAM != null) { - DIAGNOSTICS_STREAM.println(msg); - DIAGNOSTICS_STREAM.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(final 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 (final SecurityException ex) { - logDiagnostic("[ENV] Security setting prevent interrogation of system classpaths."); - } - - final String className = clazz.getName(); - ClassLoader classLoader; - - try { - classLoader = getClassLoader(clazz); - } catch (final 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(final 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 (final SecurityException ex) { - logDiagnostic(prefix + "Security forbids determining the system classloader."); - return; - } - if (classLoader != null) { - final StringBuilder buf = new StringBuilder(prefix + "ClassLoader tree:"); - for(;;) { - buf.append(objectId(classLoader)); - if (classLoader == systemClassLoader) { - buf.append(" (SYSTEM) "); - } - - try { - classLoader = classLoader.getParent(); - } catch (final 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(final Object o) { - if (o == null) { - return "null"; - } - return o.getClass().getName() + "@" + System.identityHashCode(o); - } - - // ---------------------------------------------------------------------- - // Static initializer block to perform initialization 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 initializer - // associated with the variable to run first. If static initializers 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). - ClassLoader thisClassLoader = getClassLoader(LogFactory.class); - thisClassLoaderRef = new WeakReference(thisClassLoader); - // 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 { - if (thisClassLoader == null) { - classLoaderName = "BOOTLOADER"; - } else { - classLoaderName = objectId(thisClassLoader); - } - } catch (final SecurityException e) { - classLoaderName = "UNKNOWN"; - } - diagnosticPrefix = "[LogFactory from " + classLoaderName + "] "; - DIAGNOSTICS_STREAM = initDiagnostics(); - logClassLoaderEnvironment(LogFactory.class); - factories = createFactoryStore(); - if (isDiagnosticsEnabled()) { - logDiagnostic("BOOTSTRAP COMPLETED"); - } - } -} +/* + * 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.lang.ref.WeakReference; +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. + */ +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 ({@code 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 ({@code 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 ({@code 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 {@code 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 ({@code 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 ({@code 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 final PrintStream DIAGNOSTICS_STREAM; + + /** + * 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 + * ({@code org.apache.commons.logging.LogFactory.HashtableImpl}) + * value allows the {@code Hashtable} used to store + * classloaders to be substituted by an alternative implementation. + *

+ * Note: {@code 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 {@code 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 {@code LogFactory} will load an instance of + * {@code 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 WeakReference thisClassLoaderRef; + + // ----------------------------------------------------------- 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 {@code 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 {@code getInstance(String)} with it. + * + * @param clazz Class for which a suitable Log name will be derived + * @throws LogConfigurationException if a suitable {@code Log} + * instance cannot be returned + */ + public abstract Log getInstance(Class clazz) + throws LogConfigurationException; + + /** + * Construct (if necessary) and return a {@code Log} instance, + * using the factory's current set of configuration attributes. + *

+ * NOTE - Depending upon the implementation of + * the {@code LogFactory} you are using, the {@code 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 {@code 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 {@code 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 {@code null} value is equivalent to calling + * {@code removeAttribute(name)}. + * + * @param name Name of the attribute to set + * @param value Value of the attribute to set, or {@code null} + * to remove any setting for this attribute + */ + public abstract void setAttribute(String name, Object value); + + // ------------------------------------------------------- Static Variables + + /** + * The previously constructed {@code LogFactory} instances, keyed by + * the {@code ClassLoader} with which it was created. + */ + protected static Hashtable factories; + + /** + * Previously constructed {@code LogFactory} instance as in the + * {@code factories} map, but for the case where + * {@code getClassLoader} returns {@code null}. + * This can happen when: + *

+ * Note that {@code factories} is a Hashtable (not a HashMap), + * and hashtables don't allow null as a key. + * @deprecated since 1.1.2 + */ + @Deprecated + protected static volatile LogFactory nullClassLoaderFactory; + + /** + * 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 (final 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 { + final Class implementationClass = Class.forName(storeImplementationClass); + result = (Hashtable) implementationClass.newInstance(); + } catch (final 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(final 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: + *

+ * + * @param t the Throwable to check + */ + protected static void handleThrowable(final 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 {@code LogFactory} + * instance, using the following ordered lookup procedure to determine + * the name of the implementation class to be loaded. + *

+ *

+ *

+ * NOTE - If the properties file method of identifying the + * {@code LogFactory} implementation class is utilized, all of the + * properties defined in this file will be set as configuration attributes + * on the corresponding {@code 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 + final ClassLoader contextClassLoader = getContextClassLoaderInternal(); + + + // 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 (contextClassLoader == null && 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. + + final 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) { + final String useTCCLStr = props.getProperty(TCCL_KEY); + // The Boolean.valueOf(useTCCLStr).booleanValue() formulation + // is required for Java 1.2 compatibility. + if ((useTCCLStr != null) && !Boolean.parseBoolean(useTCCLStr)) { + // 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 = thisClassLoaderRef.get(); + } + } + + // 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 { + final 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 (final 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 (final RuntimeException e) { + // This is not consistent with the behavior 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 (final java.io.UnsupportedEncodingException e) { + rd = new BufferedReader(new InputStreamReader(is)); + } + + String factoryClassName; + try { + factoryClassName = rd.readLine(); + } finally { + rd.close(); + } + + if (factoryClassName != null && ! factoryClassName.isEmpty()) { + 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 (final 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..."); + } + final 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, thisClassLoaderRef.get(), contextClassLoader); + } + + if (factory != null) { + /** + * Always cache using context class loader. + */ + cacheFactory(contextClassLoader, factory); + + if (props != null) { + final Enumeration names = props.propertyNames(); + while (names.hasMoreElements()) { + final String name = (String) names.nextElement(); + final 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 {@code Log} + * instance cannot be returned + */ + public static Log getLog(final 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 {@code 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 {@code Log} + * instance cannot be returned + */ + public static Log getLog(final 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 {@code release()} on + * each of them. + * + * @param classLoader ClassLoader for which to release the LogFactory + */ + public static void release(final 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 {@code 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()) { + final 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(final Class clazz) { + try { + return clazz.getClassLoader(); + } catch (final 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() { + @Override + 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 (final 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(final 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; + } + 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(final ClassLoader classLoader, final 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 {@code 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 {@code 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. + final Object result = AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Object run() { + return createFactory(factoryClass, classLoader); + } + }); + + if (result instanceof LogConfigurationException) { + final 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(final String factoryClass, final 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 (final ClassNotFoundException ex) { + if (classLoader == thisClassLoaderRef.get()) { + // 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 (final NoClassDefFoundError e) { + if (classLoader == thisClassLoaderRef.get()) { + // 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 (final ClassCastException e) { + if (classLoader == thisClassLoaderRef.get()) { + // There's no point in falling through to the code below that + // tries again with thisClassLoaderRef, 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 StringBuilder msg = new StringBuilder(); + 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 thisClassLoaderRef. + // 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 (final 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 {@code 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 {@code LogFactory} class loaded by the class's classloader. + * @param logFactoryClass {@code Class} which may implement {@code LogFactory} + * @return true if the {@code logFactoryClass} does extend + * {@code LogFactory} when that class is loaded via the same + * classloader that loaded the {@code logFactoryClass}. + */ + private static boolean implementsLogFactory(final Class logFactoryClass) { + boolean implementsLogFactory = false; + if (logFactoryClass != null) { + try { + final ClassLoader logFactoryClassLoader = logFactoryClass.getClassLoader(); + if (logFactoryClassLoader == null) { + logDiagnostic("[CUSTOM LOG FACTORY] was loaded by the boot classloader"); + } else { + logHierarchy("[CUSTOM LOG FACTORY] ", logFactoryClassLoader); + final 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 (final 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 (final 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 (final 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() { + @Override + public Object run() { + if (loader != null) { + return loader.getResourceAsStream(name); + } + 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) { + final PrivilegedAction action = + new PrivilegedAction() { + @Override + public Object run() { + try { + if (loader != null) { + return loader.getResources(name); + } + return ClassLoader.getSystemResources(name); + } catch (final IOException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Exception while trying to find configuration file " + + name + ":" + e.getMessage()); + } + return null; + } catch (final 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; + } + } + }; + final 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) { + final PrivilegedAction action = + new PrivilegedAction() { + @Override + public Object run() { + InputStream stream = null; + try { + // We must ensure that useCaches is set to false, as the + // default behavior of java is to cache file handles, and + // this "locks" files, preventing hot-redeploy on windows. + final URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + stream = connection.getInputStream(); + if (stream != null) { + final Properties props = new Properties(); + props.load(stream); + stream.close(); + stream = null; + return props; + } + } catch (final IOException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to read URL " + url); + } + } finally { + if (stream != null) { + try { + stream.close(); + } catch (final 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(final ClassLoader classLoader, final String fileName) { + Properties props = null; + double priority = 0.0; + URL propsUrl = null; + try { + final Enumeration urls = getResources(classLoader, fileName); + + if (urls == null) { + return null; + } + + while (urls.hasMoreElements()) { + final URL url = (URL) urls.nextElement(); + + final Properties newProps = getProperties(url); + if (newProps != null) { + if (props == null) { + propsUrl = url; + props = newProps; + final 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 { + final 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 (final 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() { + @Override + 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 (final 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; + } + if (dest.equals("STDERR")) { + return System.err; + } + try { + // open the file in append mode + final FileOutputStream fos = new FileOutputStream(dest, true); + return new PrintStream(fos); + } catch (final 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 DIAGNOSTICS_STREAM != 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(final String msg) { + if (DIAGNOSTICS_STREAM != null) { + DIAGNOSTICS_STREAM.print(diagnosticPrefix); + DIAGNOSTICS_STREAM.println(msg); + DIAGNOSTICS_STREAM.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(final String msg) { + if (DIAGNOSTICS_STREAM != null) { + DIAGNOSTICS_STREAM.println(msg); + DIAGNOSTICS_STREAM.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(final 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 (final SecurityException ex) { + logDiagnostic("[ENV] Security setting prevent interrogation of system classpaths."); + } + + final String className = clazz.getName(); + ClassLoader classLoader; + + try { + classLoader = getClassLoader(clazz); + } catch (final 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(final 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 (final SecurityException ex) { + logDiagnostic(prefix + "Security forbids determining the system classloader."); + return; + } + if (classLoader != null) { + final StringBuilder buf = new StringBuilder(prefix + "ClassLoader tree:"); + for(;;) { + buf.append(objectId(classLoader)); + if (classLoader == systemClassLoader) { + buf.append(" (SYSTEM) "); + } + + try { + classLoader = classLoader.getParent(); + } catch (final 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(final Object o) { + if (o == null) { + return "null"; + } + return o.getClass().getName() + "@" + System.identityHashCode(o); + } + + // ---------------------------------------------------------------------- + // Static initializer block to perform initialization 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 initializer + // associated with the variable to run first. If static initializers 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). + ClassLoader thisClassLoader = getClassLoader(LogFactory.class); + thisClassLoaderRef = new WeakReference(thisClassLoader); + // 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 { + if (thisClassLoader == null) { + classLoaderName = "BOOTLOADER"; + } else { + classLoaderName = objectId(thisClassLoader); + } + } catch (final SecurityException e) { + classLoaderName = "UNKNOWN"; + } + diagnosticPrefix = "[LogFactory from " + classLoaderName + "] "; + DIAGNOSTICS_STREAM = initDiagnostics(); + logClassLoaderEnvironment(LogFactory.class); + factories = createFactoryStore(); + if (isDiagnosticsEnabled()) { + logDiagnostic("BOOTSTRAP COMPLETED"); + } + } +} diff --git a/src/test/java/org/apache/commons/logging/AbstractLogTest.java b/src/test/java/org/apache/commons/logging/AbstractLogTest.java index 10c4f70..352ea60 100644 --- a/src/test/java/org/apache/commons/logging/AbstractLogTest.java +++ b/src/test/java/org/apache/commons/logging/AbstractLogTest.java @@ -1,66 +1,66 @@ -/* - * 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. - */ -public abstract class AbstractLogTest extends TestCase { - - public abstract Log getLogObject(); - - public void testLoggingWithNullParameters() - { - final 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()); - } -} +/* + * 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. + */ +public abstract class AbstractLogTest extends TestCase { + + public abstract Log getLogObject(); + + public void testLoggingWithNullParameters() + { + final 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/LoadTestCase.java b/src/test/java/org/apache/commons/logging/LoadTestCase.java index e0c920a..f577695 100644 --- a/src/test/java/org/apache/commons/logging/LoadTestCase.java +++ b/src/test/java/org/apache/commons/logging/LoadTestCase.java @@ -1,216 +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; - -import junit.framework.TestCase; - -/** - * test to emulate container and application isolated from container - */ -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(final ClassLoader parent) { - super(parent); - } - - private Class def(final String name) throws ClassNotFoundException { - - Class result = (Class) classes.get(name); - if (result != null) { - return result; - } - - try { - - final ClassLoader cl = this.getClass().getClassLoader(); - final String classFileName = name.replace('.', '/') + ".class"; - final java.io.InputStream is = cl.getResourceAsStream(classFileName); - final java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); - - while (is.available() > 0) { - out.write(is.read()); - } - - final byte[] data = out.toByteArray(); - - result = super.defineClass(name, data, 0, data.length); - classes.put(name, result); - - return result; - - } catch (final java.io.IOException ioe) { - - throw new ClassNotFoundException(name + " caused by " + ioe.getMessage()); - } - - } - - // not very trivial to emulate we must implement "findClass", - // but it will delegate to JUnit class loader first - @Override - public Class loadClass(final String name) throws ClassNotFoundException { - - // isolates all logging classes, application in the same classloader too. - // filters exceptions to simplify handling in test - for (final String element : LOG_PCKG) { - if (name.startsWith(element) && 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(final Class c, final String state) throws Exception { - final Class[] params = {String.class}; - final java.lang.reflect.Method m = c.getDeclaredMethod("setAllowFlawedContext", params); - m.invoke(null, 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 (final 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 (final 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; - final AppClassLoader appLoader = new AppClassLoader(this.getClass().getClassLoader()); - try { - - testObjCls = appLoader.loadClass(UserClass.class.getName()); - - } catch (final ClassNotFoundException cnfe) { - throw cnfe; - } catch (final Throwable t) { - t.printStackTrace(); - fail("AppClassLoader failed "); - } - - assertSame("app isolated", testObjCls.getClassLoader(), appLoader); - - return testObjCls; - - } - - - private void execute(final Class cls) throws Exception { - cls.newInstance(); - } - - @Override - public void setUp() { - // save state before test starts so we can restore it when test ends - origContextClassLoader = Thread.currentThread().getContextClassLoader(); - } - - @Override - public void tearDown() { - // restore original state so a test can't stuff up later tests. - Thread.currentThread().setContextClassLoader(origContextClassLoader); - } - - private ClassLoader origContextClassLoader; -} +/* + * 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 to emulate container and application isolated from container + */ +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(final ClassLoader parent) { + super(parent); + } + + private Class def(final String name) throws ClassNotFoundException { + + Class result = (Class) classes.get(name); + if (result != null) { + return result; + } + + try { + + final ClassLoader cl = this.getClass().getClassLoader(); + final String classFileName = name.replace('.', '/') + ".class"; + final java.io.InputStream is = cl.getResourceAsStream(classFileName); + final java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + + while (is.available() > 0) { + out.write(is.read()); + } + + final byte[] data = out.toByteArray(); + + result = super.defineClass(name, data, 0, data.length); + classes.put(name, result); + + return result; + + } catch (final java.io.IOException ioe) { + + throw new ClassNotFoundException(name + " caused by " + ioe.getMessage()); + } + + } + + // not very trivial to emulate we must implement "findClass", + // but it will delegate to JUnit class loader first + @Override + public Class loadClass(final String name) throws ClassNotFoundException { + + // isolates all logging classes, application in the same classloader too. + // filters exceptions to simplify handling in test + for (final String element : LOG_PCKG) { + if (name.startsWith(element) && 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(final Class c, final String state) throws Exception { + final Class[] params = {String.class}; + final java.lang.reflect.Method m = c.getDeclaredMethod("setAllowFlawedContext", params); + m.invoke(null, 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 (final 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 (final 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; + final AppClassLoader appLoader = new AppClassLoader(this.getClass().getClassLoader()); + try { + + testObjCls = appLoader.loadClass(UserClass.class.getName()); + + } catch (final ClassNotFoundException cnfe) { + throw cnfe; + } catch (final Throwable t) { + t.printStackTrace(); + fail("AppClassLoader failed "); + } + + assertSame("app isolated", testObjCls.getClassLoader(), appLoader); + + return testObjCls; + + } + + + private void execute(final Class cls) throws Exception { + cls.newInstance(); + } + + @Override + public void setUp() { + // save state before test starts so we can restore it when test ends + origContextClassLoader = Thread.currentThread().getContextClassLoader(); + } + + @Override + 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/avalon/AvalonLoggerTestCase.java b/src/test/java/org/apache/commons/logging/avalon/AvalonLoggerTestCase.java index bce0a85..f8d6d14 100644 --- a/src/test/java/org/apache/commons/logging/avalon/AvalonLoggerTestCase.java +++ b/src/test/java/org/apache/commons/logging/avalon/AvalonLoggerTestCase.java @@ -1,43 +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.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; - -/** - */ -public class AvalonLoggerTestCase extends AbstractLogTest { - - public static Test suite() { - final TestSuite suite = new TestSuite(); - suite.addTestSuite(AvalonLoggerTestCase.class); - return suite; - } - - @Override - public Log getLogObject() { - // Output does not seem to be used, so don't display it. - final Log log = new AvalonLogger(new NullLogger()); - return log; - } -} +/* + * 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; + +/** + */ +public class AvalonLoggerTestCase extends AbstractLogTest { + + public static Test suite() { + final TestSuite suite = new TestSuite(); + suite.addTestSuite(AvalonLoggerTestCase.class); + return suite; + } + + @Override + public Log getLogObject() { + // Output does not seem to be used, so don't display it. + final Log log = new AvalonLogger(new NullLogger()); + return log; + } +} diff --git a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java index 4995c5e..0c6abbb 100644 --- a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java +++ b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java @@ -1,396 +1,396 @@ -/* - * 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.

- */ - -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(final String name) { - super(name); - } - - - // ----------------------------------------------------- Instance Variables - - - /** - *

The customized {@code Handler} we will be using.

- */ - protected TestHandler handler; - - - /** - *

The underlying {@code Handler}s we will be using.

- */ - protected Handler handlers[]; - - - /** - *

The underlying {@code Logger} we will be using.

- */ - protected Logger logger; - - - /** - *

The underlying {@code LogManager} we will be using.

- */ - protected LogManager manager; - - - /** - *

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(final String name, final ClassLoader srcCL) throws Exception { - final String resName = name.replace('.', '/') + ".class"; - System.err.println("Trying to load resource [" + resName + "]"); - final InputStream is = srcCL.getResourceAsStream(resName); - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - System.err.println("Reading resource [" + resName + "]"); - final byte[] buf = new byte[1000]; - for(;;) { - final 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(final String className, final ClassLoader targetCL) { - try { - targetCL.loadClass(className); - // fail("Class already in target classloader"); - return; - } catch (final ClassNotFoundException ex) { - // ok, go ahead and load it - } - - try { - final ClassLoader srcCL = CustomConfigAPITestCase.class.getClassLoader(); - final byte[] classData = readClass(className, srcCL); - - final Class[] params = new Class[] { String.class, classData.getClass(), Integer.TYPE, Integer.TYPE }; - final Method m = ClassLoader.class.getDeclaredMethod("defineClass", params); - - final 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 (final Exception e) { - e.printStackTrace(); - fail("Unable to load class " + className); - } - } - - /** - * Set up instance variables required by this test case. - */ - @Override - 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 { - final 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. - final ClassLoader scl = ClassLoader.getSystemClassLoader(); - loadTestHandler(HANDLER_NAME, scl); - cl.useExplicitLoader(HANDLER_NAME, scl); - cl.addLogicalLib("commons-logging"); - cl.addLogicalLib("testclasses"); - - final Class testClass = cl.loadClass(CustomConfigTestCase.class.getName()); - return new PathableTestSuite(testClass, cl); - } - - /** - * Tear down instance variables required by this test case. - */ - @Override - 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)); - assertFalse(logger.isLoggable(Level.FINER)); - assertFalse(logger.isLoggable(Level.FINEST)); - - } - - - // Test Serializability of Log instance - @Override - public void testSerializable() throws Exception { - - super.testSerializable(); - testExceptionMessages(); - - } - - - // -------------------------------------------------------- Support Methods - - - // Check the log instance - @Override - 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()); - assertFalse(log.isTraceEnabled()); - - } - - - // Check the recorded messages - protected void checkLogRecords(final boolean thrown) { - final Iterator records = handler.records(); - for (int i = 0; i < testMessages.length; i++) { - assertTrue(records.hasNext()); - final 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()); - } - } - assertFalse(records.hasNext()); - handler.flush(); - } - - - // Log the messages with exceptions - protected void logExceptionMessages() { - final 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(final String name) throws Exception { - logger = Logger.getLogger(name); - } - - - // Set up LogManager instance - protected void setUpManager(final String config) throws Exception { - manager = LogManager.getLogManager(); - final InputStream is = - this.getClass().getClassLoader().getResourceAsStream(config); - manager.readConfiguration(is); - is.close(); - } - - -} +/* + * 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.

+ */ + +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(final String name) { + super(name); + } + + + // ----------------------------------------------------- Instance Variables + + + /** + *

The customized {@code Handler} we will be using.

+ */ + protected TestHandler handler; + + + /** + *

The underlying {@code Handler}s we will be using.

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

The underlying {@code Logger} we will be using.

+ */ + protected Logger logger; + + + /** + *

The underlying {@code LogManager} we will be using.

+ */ + protected LogManager manager; + + + /** + *

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(final String name, final ClassLoader srcCL) throws Exception { + final String resName = name.replace('.', '/') + ".class"; + System.err.println("Trying to load resource [" + resName + "]"); + final InputStream is = srcCL.getResourceAsStream(resName); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + System.err.println("Reading resource [" + resName + "]"); + final byte[] buf = new byte[1000]; + for(;;) { + final 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(final String className, final ClassLoader targetCL) { + try { + targetCL.loadClass(className); + // fail("Class already in target classloader"); + return; + } catch (final ClassNotFoundException ex) { + // ok, go ahead and load it + } + + try { + final ClassLoader srcCL = CustomConfigAPITestCase.class.getClassLoader(); + final byte[] classData = readClass(className, srcCL); + + final Class[] params = new Class[] { String.class, classData.getClass(), Integer.TYPE, Integer.TYPE }; + final Method m = ClassLoader.class.getDeclaredMethod("defineClass", params); + + final 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 (final Exception e) { + e.printStackTrace(); + fail("Unable to load class " + className); + } + } + + /** + * Set up instance variables required by this test case. + */ + @Override + 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 { + final 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. + final ClassLoader scl = ClassLoader.getSystemClassLoader(); + loadTestHandler(HANDLER_NAME, scl); + cl.useExplicitLoader(HANDLER_NAME, scl); + cl.addLogicalLib("commons-logging"); + cl.addLogicalLib("testclasses"); + + final Class testClass = cl.loadClass(CustomConfigTestCase.class.getName()); + return new PathableTestSuite(testClass, cl); + } + + /** + * Tear down instance variables required by this test case. + */ + @Override + 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)); + assertFalse(logger.isLoggable(Level.FINER)); + assertFalse(logger.isLoggable(Level.FINEST)); + + } + + + // Test Serializability of Log instance + @Override + public void testSerializable() throws Exception { + + super.testSerializable(); + testExceptionMessages(); + + } + + + // -------------------------------------------------------- Support Methods + + + // Check the log instance + @Override + 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()); + assertFalse(log.isTraceEnabled()); + + } + + + // Check the recorded messages + protected void checkLogRecords(final boolean thrown) { + final Iterator records = handler.records(); + for (int i = 0; i < testMessages.length; i++) { + assertTrue(records.hasNext()); + final 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()); + } + } + assertFalse(records.hasNext()); + handler.flush(); + } + + + // Log the messages with exceptions + protected void logExceptionMessages() { + final 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(final String name) throws Exception { + logger = Logger.getLogger(name); + } + + + // Set up LogManager instance + protected void setUpManager(final String config) throws Exception { + manager = LogManager.getLogManager(); + final 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 index b8734f8..a8b14c8 100644 --- a/src/test/java/org/apache/commons/logging/jdk14/DefaultConfigTestCase.java +++ b/src/test/java/org/apache/commons/logging/jdk14/DefaultConfigTestCase.java @@ -1,158 +1,158 @@ -/* - * 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.

- */ -public class DefaultConfigTestCase extends TestCase { - - /** - *

Construct a new instance of this test case.

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

The {@link LogFactory} implementation we have selected.

- */ - protected LogFactory factory; - - /** - *

The {@link Log} implementation we have selected.

- */ - protected Log log; - - /** - * Set up instance variables required by this test case. - */ - @Override - public void setUp() throws Exception { - setUpFactory(); - setUpLog("TestLogger"); - } - - /** - * Return the tests included in this test suite. - */ - public static Test suite() throws Exception { - final PathableClassLoader loader = new PathableClassLoader(null); - loader.useExplicitLoader("junit.", Test.class.getClassLoader()); - loader.addLogicalLib("testclasses"); - loader.addLogicalLib("commons-logging"); - - final Class testClass = loader.loadClass(DefaultConfigTestCase.class.getName()); - return new PathableTestSuite(testClass, loader); - } - - /** - * Tear down instance variables required by this test case. - */ - @Override - public void tearDown() { - log = null; - factory = null; - LogFactory.releaseAll(); - } - - // 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()); - - final 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 - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - final ObjectOutputStream oos = new ObjectOutputStream(baos); - oos.writeObject(log); - oos.close(); - final ByteArrayInputStream bais = - new ByteArrayInputStream(baos.toByteArray()); - final ObjectInputStream ois = new ObjectInputStream(bais); - log = (Log) ois.readObject(); - ois.close(); - - // Check the characteristics of the resulting object - checkLog(); - - } - - // 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(final String name) throws Exception { - log = LogFactory.getLog(name); - } - -} +/* + * 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.

+ */ +public class DefaultConfigTestCase extends TestCase { + + /** + *

Construct a new instance of this test case.

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

The {@link LogFactory} implementation we have selected.

+ */ + protected LogFactory factory; + + /** + *

The {@link Log} implementation we have selected.

+ */ + protected Log log; + + /** + * Set up instance variables required by this test case. + */ + @Override + public void setUp() throws Exception { + setUpFactory(); + setUpLog("TestLogger"); + } + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + final PathableClassLoader loader = new PathableClassLoader(null); + loader.useExplicitLoader("junit.", Test.class.getClassLoader()); + loader.addLogicalLib("testclasses"); + loader.addLogicalLib("commons-logging"); + + final Class testClass = loader.loadClass(DefaultConfigTestCase.class.getName()); + return new PathableTestSuite(testClass, loader); + } + + /** + * Tear down instance variables required by this test case. + */ + @Override + public void tearDown() { + log = null; + factory = null; + LogFactory.releaseAll(); + } + + // 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()); + + final 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 + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(log); + oos.close(); + final ByteArrayInputStream bais = + new ByteArrayInputStream(baos.toByteArray()); + final ObjectInputStream ois = new ObjectInputStream(bais); + log = (Log) ois.readObject(); + ois.close(); + + // Check the characteristics of the resulting object + checkLog(); + + } + + // 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(final 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 index 4ec320c..6dae742 100644 --- a/src/test/java/org/apache/commons/logging/jdk14/TestHandler.java +++ b/src/test/java/org/apache/commons/logging/jdk14/TestHandler.java @@ -1,53 +1,53 @@ -/* - * 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 {@code java.util.logging.Handler}.

- */ -public class TestHandler extends Handler { - - // The set of logged records for this handler - private final List records = new ArrayList(); - - public Iterator records() { - return records.iterator(); - } - - @Override - public void close() { - } - - @Override - public void flush() { - records.clear(); - } - - @Override - public void publish(final LogRecord record) { - records.add(record); - } - -} +/* + * 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 {@code java.util.logging.Handler}.

+ */ +public class TestHandler extends Handler { + + // The set of logged records for this handler + private final List records = new ArrayList(); + + public Iterator records() { + return records.iterator(); + } + + @Override + public void close() { + } + + @Override + public void flush() { + records.clear(); + } + + @Override + public void publish(final LogRecord record) { + records.add(record); + } + +} diff --git a/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java b/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java index b01f799..f5f71b2 100644 --- a/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java +++ b/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java @@ -1,244 +1,244 @@ -/* - * 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.

- */ -public class CustomConfigTestCase extends DefaultConfigTestCase { - - /** - *

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" }; - - /** - * 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. - */ - @Override - 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. - */ - @Override - 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 initialization 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 class loader. - * Or we could fix SimpleLog to be sane... - */ - public static Test suite() throws Exception { - final Class thisClass = CustomConfigTestCase.class; - - final PathableClassLoader loader = new PathableClassLoader(null); - loader.useExplicitLoader("junit.", Test.class.getClassLoader()); - loader.addLogicalLib("testclasses"); - loader.addLogicalLib("commons-logging"); - - final Class testClass = loader.loadClass(thisClass.getName()); - return new PathableTestSuite(testClass, loader); - } - - /** - * Tear down instance variables required by this test case. - */ - @Override - public void tearDown() { - super.tearDown(); - expected = null; - } - - // 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 - @Override - public void testSerializable() throws Exception { - ((DecoratedSimpleLog) log).clearCache(); - logPlainMessages(); - super.testSerializable(); - logExceptionMessages(); - checkExpected(); - } - - // Check the decorated log instance - @Override - 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()); - assertFalse(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() { - assertFalse(((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() { - final List acts = ((DecoratedSimpleLog) log).getCache(); - final Iterator exps = expected.iterator(); - int n = 0; - while (exps.hasNext()) { - final LogRecord exp = (LogRecord) exps.next(); - final 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 - @Override - protected void checkStandard() { - checkDecorated(); - } - - - // Log the messages with exceptions - protected void logExceptionMessages() { - // Generate log records - final 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)); - } -} +/* + * 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.

+ */ +public class CustomConfigTestCase extends DefaultConfigTestCase { + + /** + *

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" }; + + /** + * 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. + */ + @Override + 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. + */ + @Override + 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 initialization 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 class loader. + * Or we could fix SimpleLog to be sane... + */ + public static Test suite() throws Exception { + final Class thisClass = CustomConfigTestCase.class; + + final PathableClassLoader loader = new PathableClassLoader(null); + loader.useExplicitLoader("junit.", Test.class.getClassLoader()); + loader.addLogicalLib("testclasses"); + loader.addLogicalLib("commons-logging"); + + final Class testClass = loader.loadClass(thisClass.getName()); + return new PathableTestSuite(testClass, loader); + } + + /** + * Tear down instance variables required by this test case. + */ + @Override + public void tearDown() { + super.tearDown(); + expected = null; + } + + // 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 + @Override + public void testSerializable() throws Exception { + ((DecoratedSimpleLog) log).clearCache(); + logPlainMessages(); + super.testSerializable(); + logExceptionMessages(); + checkExpected(); + } + + // Check the decorated log instance + @Override + 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()); + assertFalse(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() { + assertFalse(((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() { + final List acts = ((DecoratedSimpleLog) log).getCache(); + final Iterator exps = expected.iterator(); + int n = 0; + while (exps.hasNext()) { + final LogRecord exp = (LogRecord) exps.next(); + final 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 + @Override + protected void checkStandard() { + checkDecorated(); + } + + + // Log the messages with exceptions + protected void logExceptionMessages() { + // Generate log records + final 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 index 587d656..2344ad4 100644 --- a/src/test/java/org/apache/commons/logging/simple/DateTimeCustomConfigTestCase.java +++ b/src/test/java/org/apache/commons/logging/simple/DateTimeCustomConfigTestCase.java @@ -1,109 +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. - */ - -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 initialization 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 class loader. - * Or we could fix SimpleLog to be sane... - */ - public static Test suite() throws Exception { - final Class thisClass = DateTimeCustomConfigTestCase.class; - - final PathableClassLoader loader = new PathableClassLoader(null); - loader.useExplicitLoader("junit.", Test.class.getClassLoader()); - loader.addLogicalLib("testclasses"); - loader.addLogicalLib("commons-logging"); - - final 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. - */ - @Override - 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. - */ - @Override - public void setUp() throws Exception { - super.setUp(); - } - - - // ----------------------------------------------------------- Methods - - /** Checks that the date time format has been successfully set */ - @Override - protected void checkDecoratedDateTime() { - assertEquals("Expected date format to be set", "dd.mm.yyyy", - ((DecoratedSimpleLog) log).getDateTimeFormat()); - // try the formatter - final Date now = new Date(); - final DateFormat formatter = ((DecoratedSimpleLog) log).getDateTimeFormatter(); - final 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 */ - @Override - protected void checkShowDateTime() { - assertTrue(((DecoratedSimpleLog) log).getShowDateTime()); - } - -} +/* + * 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 initialization 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 class loader. + * Or we could fix SimpleLog to be sane... + */ + public static Test suite() throws Exception { + final Class thisClass = DateTimeCustomConfigTestCase.class; + + final PathableClassLoader loader = new PathableClassLoader(null); + loader.useExplicitLoader("junit.", Test.class.getClassLoader()); + loader.addLogicalLib("testclasses"); + loader.addLogicalLib("commons-logging"); + + final 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. + */ + @Override + 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. + */ + @Override + public void setUp() throws Exception { + super.setUp(); + } + + + // ----------------------------------------------------------- Methods + + /** Checks that the date time format has been successfully set */ + @Override + protected void checkDecoratedDateTime() { + assertEquals("Expected date format to be set", "dd.mm.yyyy", + ((DecoratedSimpleLog) log).getDateTimeFormat()); + // try the formatter + final Date now = new Date(); + final DateFormat formatter = ((DecoratedSimpleLog) log).getDateTimeFormatter(); + final 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 */ + @Override + protected void checkShowDateTime() { + assertTrue(((DecoratedSimpleLog) log).getShowDateTime()); + } + +} diff --git a/src/test/java/org/apache/commons/logging/simple/DefaultConfigTestCase.java b/src/test/java/org/apache/commons/logging/simple/DefaultConfigTestCase.java index c96abb5..1e3fade 100644 --- a/src/test/java/org/apache/commons/logging/simple/DefaultConfigTestCase.java +++ b/src/test/java/org/apache/commons/logging/simple/DefaultConfigTestCase.java @@ -1,216 +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.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.

- */ - -public class DefaultConfigTestCase extends TestCase { - - /** - *

The {@link LogFactory} implementation we have selected.

- */ - protected LogFactory factory; - - /** - *

The {@link Log} implementation we have selected.

- */ - protected Log log; - - /** - * 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 initialization 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 class loader. - * Or we could fix SimpleLog to be sane... - */ - public static Test suite() throws Exception { - final Class thisClass = DefaultConfigTestCase.class; - - final PathableClassLoader loader = new PathableClassLoader(null); - loader.useExplicitLoader("junit.", Test.class.getClassLoader()); - loader.addLogicalLib("testclasses"); - loader.addLogicalLib("commons-logging"); - - final 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. - */ - @Override - public void setUp() throws Exception { - LogFactory.releaseAll(); - setProperties(); - setUpFactory(); - setUpLog("TestLogger"); - } - - /** - * Tear down instance variables required by this test case. - */ - @Override - public void tearDown() { - log = null; - factory = null; - LogFactory.releaseAll(); - } - - // 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()); - - final 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 - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - final ObjectOutputStream oos = new ObjectOutputStream(baos); - oos.writeObject(log); - oos.close(); - final ByteArrayInputStream bais = - new ByteArrayInputStream(baos.toByteArray()); - final ObjectInputStream ois = new ObjectInputStream(bais); - log = (Log) ois.readObject(); - ois.close(); - - // Check the characteristics of the resulting object - checkStandard(); - - } - - // 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? - assertFalse(log.isDebugEnabled()); - assertTrue(log.isErrorEnabled()); - assertTrue(log.isFatalEnabled()); - assertTrue(log.isInfoEnabled()); - assertFalse(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()); - assertFalse(((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? - assertFalse(log.isDebugEnabled()); - assertTrue(log.isErrorEnabled()); - assertTrue(log.isFatalEnabled()); - assertTrue(log.isInfoEnabled()); - assertFalse(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(final 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(final String name) throws Exception { - log = LogFactory.getLog(name); - } - -} +/* + * 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.

+ */ + +public class DefaultConfigTestCase extends TestCase { + + /** + *

The {@link LogFactory} implementation we have selected.

+ */ + protected LogFactory factory; + + /** + *

The {@link Log} implementation we have selected.

+ */ + protected Log log; + + /** + * 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 initialization 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 class loader. + * Or we could fix SimpleLog to be sane... + */ + public static Test suite() throws Exception { + final Class thisClass = DefaultConfigTestCase.class; + + final PathableClassLoader loader = new PathableClassLoader(null); + loader.useExplicitLoader("junit.", Test.class.getClassLoader()); + loader.addLogicalLib("testclasses"); + loader.addLogicalLib("commons-logging"); + + final 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. + */ + @Override + public void setUp() throws Exception { + LogFactory.releaseAll(); + setProperties(); + setUpFactory(); + setUpLog("TestLogger"); + } + + /** + * Tear down instance variables required by this test case. + */ + @Override + public void tearDown() { + log = null; + factory = null; + LogFactory.releaseAll(); + } + + // 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()); + + final 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 + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(log); + oos.close(); + final ByteArrayInputStream bais = + new ByteArrayInputStream(baos.toByteArray()); + final ObjectInputStream ois = new ObjectInputStream(bais); + log = (Log) ois.readObject(); + ois.close(); + + // Check the characteristics of the resulting object + checkStandard(); + + } + + // 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? + assertFalse(log.isDebugEnabled()); + assertTrue(log.isErrorEnabled()); + assertTrue(log.isFatalEnabled()); + assertTrue(log.isInfoEnabled()); + assertFalse(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()); + assertFalse(((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? + assertFalse(log.isDebugEnabled()); + assertTrue(log.isErrorEnabled()); + assertTrue(log.isFatalEnabled()); + assertTrue(log.isInfoEnabled()); + assertFalse(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(final 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(final String name) throws Exception { + log = LogFactory.getLog(name); + } + +}