Skip to content

Commit

Permalink
Better handle soft proxies for custom assert classes in OSGi bundles (#…
Browse files Browse the repository at this point in the history
…1968)

We use a composite class loader for defining soft proxies when the
assert class is from a different class loader than the AssertJ classes.

This can occur in OSGi when the assert class is from a bundle. The
composite class loader provides access to the internal, non-exported
types of AssertJ. ByteBuddy will define the proxy class in the
CompositeClassLoader rather than in the class loader of the assert
class. This means the assert class cannot assume package private access
to super types, interfaces, etc. since the proxy class is defined in a
different class loader (the CompositeClassLoader) than the assert class.

Fixes #1964

Signed-off-by: BJ Hargrave <bj@bjhargrave.com>
  • Loading branch information
bjhargrave authored Aug 18, 2020
1 parent cbf4577 commit eafed85
Showing 1 changed file with 49 additions and 2 deletions.
51 changes: 49 additions & 2 deletions src/main/java/org/assertj/core/api/SoftProxies.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
import static net.bytebuddy.matcher.ElementMatchers.not;
import static org.assertj.core.api.ClassLoadingStrategyFactory.classLoadingStrategy;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;

import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration;
Expand Down Expand Up @@ -196,13 +199,57 @@ static <V> Class<? extends V> generateProxyClass(Class<V> assertClass) {
.andThen(FieldAccessor.ofField(ErrorCollector.FIELD_NAME).setsArgumentAt(1)))
.make()
// Use ClassLoader of soft assertion class to allow ByteBuddy to always find it.
// This is needed in OSGI runtime when custom soft assertion is defined outside of assertj bundle.
.load(assertClass.getClassLoader(), classLoadingStrategy(assertClass))
// This is needed in an OSGi runtime when a custom soft assertion class is defined
// in a different OSGi bundle.
.load(CompositeClassLoader.getClassLoader(assertClass), classLoadingStrategy(assertClass))
.getLoaded();
}

private static Junction<MethodDescription> methodsNamed(String name) {
return named(name);
}

// Composite class loader for when the assert class is from a different
// class loader than AssertJ. This can occur in OSGi when the assert class is
// from a bundle. The composite class loader provides access to the internal,
// non-exported types of AssertJ. ByteBuddy will define the proxy class in the
// CompositeClassLoader rather than in the class loader of the assert class.
// This means the assert class cannot assume package private access to super
// types, interfaces, etc. since the proxy class is defined in a different
// class loader (the CompositeClassLoader) than the assert class.
static class CompositeClassLoader extends ClassLoader {
// Class loader of AssertJ
private static final ClassLoader assertj = SoftProxies.class.getClassLoader();

// Return a new CompositeClassLoader if the assertClass is from a
// different class loader than AssertJ. Otherwise return the class
// loader of AssertJ since there is no need to use a composite class
// loader.
static ClassLoader getClassLoader(Class<?> assertClass) {
final ClassLoader other = assertClass.getClassLoader();
if (other == assertj) {
return other;
}
return new CompositeClassLoader(other);
}

private CompositeClassLoader(ClassLoader other) {
super(other);
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return assertj.loadClass(name);
}

@Override
protected URL findResource(String name) {
return assertj.getResource(name);
}

@Override
protected Enumeration<URL> findResources(String name) throws IOException {
return assertj.getResources(name);
}
}
}

0 comments on commit eafed85

Please sign in to comment.