Skip to content

Commit

Permalink
Creates a parallel-capable classloader
Browse files Browse the repository at this point in the history
Closes #46.
  • Loading branch information
ppkarwasz committed Aug 18, 2023
1 parent 58ddd35 commit 596159d
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright © 2022 Piotr P. Karwasz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.copernik.log4j.tomcat;

class ClassLoaderUtil {

static boolean isLog4jApiResource(final String name, final boolean isClassName) {
if (isClassName && name.startsWith("org.apache.logging.log4j.")) {
if (name.indexOf('.', 25) == -1
|| name.startsWith("internal.", 25)
|| name.startsWith("message.", 25)
|| name.startsWith("simple.", 25)
|| name.startsWith("spi.", 25)
|| name.startsWith("status.", 25)
|| name.startsWith("util.", 25)) {
return true;
}
} else if (!isClassName && name.startsWith("org/apache/logging/log4j/")) {
if (name.indexOf('/', 25) == -1
|| name.startsWith("internal/", 25)
|| name.startsWith("message/", 25)
|| name.startsWith("simple/", 25)
|| name.startsWith("spi/", 25)
|| name.startsWith("status/", 25)
|| name.startsWith("util/", 25)) {
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright © 2022 Piotr P. Karwasz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.copernik.log4j.tomcat;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.loader.ParallelWebappClassLoader;

/**
* A classloader that delegates lookups for Log4j2 API classes to the parent classloader.
*
* <p>This allows multiple applications bundled with Log4j2 API to use a single copy of Log4j2 in
* the parent classloader, without modifying the servlet delegation model for the application.
*
* <p>In order to replace the default application classloader add:
*
* <pre>
* &lt;Context&gt;
* ...
* &lt;Loader loaderClass="eu.copernik.log4j.tomcat.Log4jParallelWebappClassLoader"/&gt;
* &lt;/Context&gt;
* </pre>
*
* <p>to the application's context definition (cf. <a href=
* "https://tomcat.apache.org/tomcat-10.0-doc/config/context.html#Defining_a_context">defining a
* context</a>).
*/
public class Log4jParallelWebappClassLoader extends ParallelWebappClassLoader {

public Log4jParallelWebappClassLoader() {
super();
}

public Log4jParallelWebappClassLoader(ClassLoader parent) {
super(parent);
}

@Override
protected boolean filter(String name, boolean isClassName) {
if (name == null || name.length() < 25) {
return super.filter(name, isClassName);
}
if (ClassLoaderUtil.isLog4jApiResource(name, isClassName)) {
return true;
}
return super.filter(name, isClassName);
}

@Override
public Log4jParallelWebappClassLoader copyWithoutTransformers() {

Log4jParallelWebappClassLoader result = new Log4jParallelWebappClassLoader(getParent());

super.copyStateWithoutTransformers(result);

try {
result.start();
} catch (LifecycleException e) {
throw new IllegalStateException(e);
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
package eu.copernik.log4j.tomcat;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.catalina.loader.WebappClassLoader;

/**
* A classloader that delegates lookups for Log4j2 API classes to the parent classloader.
Expand All @@ -29,15 +29,15 @@
* <pre>
* &lt;Context&gt;
* ...
* &lt;Loader loaderClass="eu.copernik.log4j.tomcat.Log4jWebappClassLoader"/&gt;
* &lt;Loader loaderClass="eu.copernik.log4j.tomcat.Log4jParallelWebappClassLoader"/&gt;
* &lt;/Context&gt;
* </pre>
*
* <p>to the application's context definition (cf. <a href=
* "https://tomcat.apache.org/tomcat-10.0-doc/config/context.html#Defining_a_context">defining a
* context</a>).
*/
public class Log4jWebappClassLoader extends WebappClassLoaderBase {
public class Log4jWebappClassLoader extends WebappClassLoader {

public Log4jWebappClassLoader() {
super();
Expand All @@ -52,26 +52,8 @@ protected boolean filter(String name, boolean isClassName) {
if (name == null || name.length() < 25) {
return super.filter(name, isClassName);
}
if (isClassName && name.startsWith("org.apache.logging.log4j.")) {
if (name.indexOf('.', 25) == -1
|| name.startsWith("internal.", 25)
|| name.startsWith("message.", 25)
|| name.startsWith("simple.", 25)
|| name.startsWith("spi.", 25)
|| name.startsWith("status.", 25)
|| name.startsWith("util.", 25)) {
return true;
}
} else if (!isClassName && name.startsWith("org/apache/logging/log4j/")) {
if (name.indexOf('/', 25) == -1
|| name.startsWith("internal/", 25)
|| name.startsWith("message/", 25)
|| name.startsWith("simple/", 25)
|| name.startsWith("spi/", 25)
|| name.startsWith("status/", 25)
|| name.startsWith("util/", 25)) {
return true;
}
if (ClassLoaderUtil.isLog4jApiResource(name, isClassName)) {
return true;
}
return super.filter(name, isClassName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,24 @@ public void testLog4jClassloader() throws IOException {
});
}
}

@RepeatedTest(100)
public void testParallelLog4jClassloader() throws IOException {
try (final URLClassLoader cl = createClassLoader(Log4jParallelWebappClassLoader.class); ) {
classes()
.forEach(
arg -> {
final Class<?> clazz = (Class<?>) arg.get()[0];
final boolean isEqual = (boolean) arg.get()[1];
final Class<?> otherClazz =
assertDoesNotThrow(() -> Class.forName(clazz.getName(), true, cl));
final ClassAssert assertion = assertThat(otherClazz);
if (isEqual) {
assertion.isEqualTo(clazz);
} else {
assertion.isNotEqualTo(clazz);
}
});
}
}
}

0 comments on commit 596159d

Please sign in to comment.