Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for bean producers in different package than beans that have … #2241

Merged
2 changes: 1 addition & 1 deletion health/health-checks/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@
exports io.helidon.health.checks;

// required for CDI
opens io.helidon.health.checks to weld.core.impl;
opens io.helidon.health.checks to weld.core.impl, io.helidon.microprofile.cdi;
}
2 changes: 1 addition & 1 deletion microprofile/access-log/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
exports io.helidon.microprofile.accesslog;

// this is needed for CDI extensions that use non-public observer methods
opens io.helidon.microprofile.accesslog to weld.core.impl;
opens io.helidon.microprofile.accesslog to weld.core.impl, io.helidon.microprofile.cdi;

provides Extension with io.helidon.microprofile.accesslog.AccessLogCdiExtension;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import io.helidon.common.NativeImageHelper;
Expand All @@ -32,6 +33,9 @@

class HelidonProxyServices implements ProxyServices {
private static final Logger LOGGER = Logger.getLogger(HelidonProxyServices.class.getName());
private static final String WELD_JAVAX_PREFIX = "org.jboss.weldx.";
private static final String WELD_JAVA_PREFIX = "org.jboss.weld.";

// a cache of all classloaders (this should be empty in most cases, as we use a single class loader in Helidon)
private final Map<ClassLoader, ClassDefiningCl> classLoaders = Collections.synchronizedMap(new IdentityHashMap<>());
private final ClassLoader contextCl;
Expand Down Expand Up @@ -69,14 +73,20 @@ public boolean supportsClassDefining() {
public Class<?> defineClass(Class<?> originalClass, String className, byte[] classBytes, int off, int len)
throws ClassFormatError {

if (samePackage(originalClass, className)) {
// when we need to define a class in the same package (to see package local fields and methods)
// we cannot use a classloader, as the new class would be in the same package, but in a different
// classloader, preventing it from seeing these fields/methods
return defineClassSamePackage(originalClass, className, classBytes, off, len);
} else {
// use a custom classloader to define classes in a new package
return wrapCl(originalClass.getClassLoader()).doDefineClass(originalClass, className, classBytes, off, len);
if (weldInternalProxyClassName(className)) {
// this is special case - these classes are defined in a non-existent package
// and we need to use a classloader (public lookup will not allow this, and private lookup is not
// possible for an empty package)
return wrapCl(originalClass.getClassLoader())
.doDefineClass(originalClass, className, classBytes, off, len);
}
// any other class should be defined using a private lookup
try {
return defineClassPrivateLookup(originalClass, className, classBytes, off, len);
} catch (Exception e) {
LOGGER.log(Level.FINEST, "Failed to create class " + className + " using private lookup", e);

throw e;
}
}

Expand All @@ -88,20 +98,33 @@ public Class<?> defineClass(Class<?> originalClass,
int len,
ProtectionDomain protectionDomain) throws ClassFormatError {

if (samePackage(originalClass, className)) {
return defineClassSamePackage(originalClass, className, classBytes, off, len);
} else {
if (weldInternalProxyClassName(className)) {
// this is special case - these classes are defined in a non-existent package
// and we need to use a classloader (public lookup will not allow this, and private lookup is not
// possible for an empty package)
return wrapCl(originalClass.getClassLoader())
.doDefineClass(originalClass, className, classBytes, off, len, protectionDomain);
}
// any other class should be defined using a private lookup
try {
return defineClassPrivateLookup(originalClass, className, classBytes, off, len);
} catch (Exception e) {
LOGGER.log(Level.FINEST, "Failed to create class " + className + " using private lookup", e);

throw e;
}
}

@Override
public Class<?> loadClass(Class<?> originalClass, String classBinaryName) throws ClassNotFoundException {
return wrapCl(originalClass.getClassLoader()).loadClass(classBinaryName);
}

private Class<?> defineClassSamePackage(Class<?> originalClass, String className, byte[] classBytes, int off, int len) {
private boolean weldInternalProxyClassName(String className) {
return className.startsWith(WELD_JAVAX_PREFIX) || className.startsWith(WELD_JAVA_PREFIX);
}

private Class<?> defineClassPrivateLookup(Class<?> originalClass, String className, byte[] classBytes, int off, int len) {
if (NativeImageHelper.isRuntime()) {
throw new IllegalStateException("Cannot define class in native image. Class name: " + className + ", original "
+ "class: " + originalClass
Expand All @@ -110,39 +133,64 @@ private Class<?> defineClassSamePackage(Class<?> originalClass, String className

LOGGER.finest("Defining class " + className + " original class: " + originalClass.getName());

MethodHandles.Lookup lookup;

try {
Module classModule = originalClass.getModule();
if (!myModule.canRead(classModule)) {
// lookup class name "guessed" from the class name of the proxy
// proxy name must contain the $ - if it does not, we just use the originalClass as that is safe
int index = className.indexOf('$');

Class<?> lookupClass;
if (index < 0) {
LOGGER.finest(() -> "Attempt to define a proxy class without a $ in its name. Class name: " + className + ","
+ " original class name: " + originalClass.getName());
lookupClass = originalClass;
} else {
// I would like to create a private lookup in the same package as the proxied class, so let's do it
// use the "extracted" lookup class name, if that fails, use the original class
lookupClass = tryLoading(originalClass, className.substring(0, index));
}

Module lookupClassModule = lookupClass.getModule();
if (!myModule.canRead(lookupClassModule)) {
// we need to read the module to be able to create a private lookup in it
// it also needs to open the package we are doing the lookup in
myModule.addReads(classModule);
myModule.addReads(lookupClassModule);
}

// next line would fail if the module does not open its package, with a very meaningful error message
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(originalClass, MethodHandles.lookup());
if (classBytes.length == len) {
return lookup.defineClass(classBytes);
} else {
byte[] bytes = new byte[len];
System.arraycopy(classBytes, off, bytes, 0, len);
return lookup.defineClass(bytes);
}
lookup = MethodHandles.privateLookupIn(lookupClass, MethodHandles.lookup());
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to define class " + className, e);
}

return defineClass(lookup, className, classBytes, off, len);
}

private boolean samePackage(Class<?> originalClass, String className) {
String origPackage = originalClass.getPackageName();
String newPackage = packageName(className);
return newPackage.equals(origPackage);
private Class<?> tryLoading(Class<?> originalClass, String className) {
try {
return originalClass.getClassLoader().loadClass(className);
} catch (Exception e) {
LOGGER.log(Level.FINEST, "Attempt to load class " + className + " failed.", e);
return originalClass;
}
}

private String packageName(String className) {
int index = className.lastIndexOf('.');
if (index > 0) {
return className.substring(0, index);
private Class<?> defineClass(MethodHandles.Lookup lookup, String className, byte[] classBytes, int off, int len) {
try {
byte[] definitionBytes;

if (classBytes.length == len) {
definitionBytes = classBytes;
} else {
definitionBytes = new byte[len];
System.arraycopy(classBytes, off, definitionBytes, 0, len);
}

return lookup.defineClass(definitionBytes);
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to define class " + className, e);
}
return "";
}

private ClassDefiningCl wrapCl(ClassLoader origCl) {
Expand Down
2 changes: 1 addition & 1 deletion microprofile/config/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
exports io.helidon.microprofile.config;

// this is needed for CDI extensions that use non-public observer methods
opens io.helidon.microprofile.config to weld.core.impl;
opens io.helidon.microprofile.config to weld.core.impl, io.helidon.microprofile.cdi;

provides javax.enterprise.inject.spi.Extension with io.helidon.microprofile.config.ConfigCdiExtension;
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
exports io.helidon.microprofile.faulttolerance;

// needed when running with modules - to make private methods accessible
opens io.helidon.microprofile.faulttolerance to weld.core.impl;
opens io.helidon.microprofile.faulttolerance to weld.core.impl, io.helidon.microprofile.cdi;

provides javax.enterprise.inject.spi.Extension with io.helidon.microprofile.faulttolerance.FaultToleranceExtension;
}
2 changes: 1 addition & 1 deletion microprofile/health/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
exports io.helidon.microprofile.health;

// this is needed for CDI extensions that use non-public observer methods
opens io.helidon.microprofile.health to weld.core.impl;
opens io.helidon.microprofile.health to weld.core.impl, io.helidon.microprofile.cdi;

uses io.helidon.microprofile.health.HealthCheckProvider;

Expand Down
2 changes: 1 addition & 1 deletion microprofile/jwt-auth/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
exports io.helidon.microprofile.jwt.auth;

// this is needed for CDI extensions that use non-public observer methods
opens io.helidon.microprofile.jwt.auth to weld.core.impl;
opens io.helidon.microprofile.jwt.auth to weld.core.impl, io.helidon.microprofile.cdi;

provides io.helidon.security.providers.common.spi.AnnotationAnalyzer with io.helidon.microprofile.jwt.auth.JwtAuthAnnotationAnalyzer;
provides io.helidon.security.spi.SecurityProviderService with io.helidon.microprofile.jwt.auth.JwtAuthProviderService;
Expand Down
2 changes: 1 addition & 1 deletion microprofile/metrics/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
exports io.helidon.microprofile.metrics;

// this is needed for CDI extensions that use non-public observer methods
opens io.helidon.microprofile.metrics to weld.core.impl;
opens io.helidon.microprofile.metrics to weld.core.impl, io.helidon.microprofile.cdi;

provides javax.enterprise.inject.spi.Extension with io.helidon.microprofile.metrics.MetricsCdiExtension;
}
2 changes: 1 addition & 1 deletion microprofile/openapi/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
exports io.helidon.microprofile.openapi;

// this is needed for CDI extensions that use non-public observer methods
opens io.helidon.microprofile.openapi to weld.core.impl;
opens io.helidon.microprofile.openapi to weld.core.impl, io.helidon.microprofile.cdi;

provides Extension with OpenApiCdiExtension;
}
2 changes: 1 addition & 1 deletion microprofile/security/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
exports io.helidon.microprofile.security;

// this is needed for CDI extensions that use non-public observer methods
opens io.helidon.microprofile.security to weld.core.impl;
opens io.helidon.microprofile.security to weld.core.impl, io.helidon.microprofile.cdi;

provides javax.enterprise.inject.spi.Extension with io.helidon.microprofile.security.SecurityCdiExtension;
}
2 changes: 1 addition & 1 deletion microprofile/server/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,5 @@
io.helidon.microprofile.server.JaxRsCdiExtension;

// needed when running with modules - to make private methods accessible
opens io.helidon.microprofile.server to weld.core.impl;
opens io.helidon.microprofile.server to weld.core.impl, io.helidon.microprofile.cdi;
}
2 changes: 1 addition & 1 deletion microprofile/tracing/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
exports io.helidon.microprofile.tracing;

// this is needed for CDI extensions that use non-public observer methods
opens io.helidon.microprofile.tracing to weld.core.impl,hk2.utils;
opens io.helidon.microprofile.tracing to weld.core.impl,hk2.utils, io.helidon.microprofile.cdi;

provides Extension with io.helidon.microprofile.tracing.TracingCdiExtension;
provides org.glassfish.jersey.internal.spi.AutoDiscoverable with io.helidon.microprofile.tracing.MpTracingAutoDiscoverable;
Expand Down
2 changes: 1 addition & 1 deletion microprofile/websocket/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
exports io.helidon.microprofile.tyrus;

// this is needed for CDI extensions that use non-public observer methods
opens io.helidon.microprofile.tyrus to weld.core.impl;
opens io.helidon.microprofile.tyrus to weld.core.impl, io.helidon.microprofile.cdi;

provides javax.enterprise.inject.spi.Extension with io.helidon.microprofile.tyrus.WebSocketCdiExtension;
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,16 @@

/*
* This class is copied from Weld sources.
* The only modified method is createCompoundProxyName.
* Modified methods:
* - getProxyName
* - createCompoundProxyName
*
* Why?
* In original Weld, the name is generated with bean identifier that is based on identity hashCode - and that is OK as
* long as you run in a single JVM (which is the case with hotspot).
* When running in native image, we go through the process of generating proxies at build time (in GraalVM when building the
* native image) and then running them from the native image.
* As these are two separate instances of JVM, we get different identity has codes, and as a result different class names
* As these are two separate instances of JVM, we get different identity hash codes, and as a result different class names
* at compile time and at runtime. The Helidon change ensures these names are equal and we can reuse the generated proxy
* classes.
*
Expand Down Expand Up @@ -271,8 +274,14 @@ static String getProxyName(String contextId, Class<?> proxiedBeanType, Set<? ext

if (typeInfo.getSuperClass() == Object.class) {
final StringBuilder name = new StringBuilder();
//interface only bean.
className = createCompoundProxyName(contextId, bean, typeInfo, name) + PROXY_SUFFIX;

// for classes that do not have an enclosing class, we want the super interface to be first
if (proxiedBeanType.getEnclosingClass() == null) {
return createProxyName(typeInfo) + PROXY_SUFFIX;
} else {
//interface only bean.
className = createCompoundProxyName(contextId, bean, typeInfo, name) + PROXY_SUFFIX;
}
} else {
boolean typeModified = false;
for (Class<?> iface : typeInfo.getInterfaces()) {
Expand Down Expand Up @@ -308,6 +317,33 @@ public void addInterfacesFromTypeClosure(Set<? extends Type> typeClosure, Class<

/*
* Helidon modification
*
* This is used when there is no enclosing type and we may have multiple interfaces
* This method ensures the superinterface is the base of the name
*/
private static String createProxyName(TypeInfo typeInfo) {
Class<?> superInterface = typeInfo.getSuperInterface();
StringBuilder name = new StringBuilder();
List<String> interfaces = new ArrayList<String>();
for (Class<?> type : typeInfo.getInterfaces()) {
if (!type.equals(superInterface)) {
interfaces.add(uniqueName(type));
}
}

Collections.sort(interfaces);
for (final String iface : interfaces) {
name.append(iface);
name.append('$');
}

return superInterface.getName() + '$' + name;
}

/*
* Helidon modification
*
* Compound name is used when more than one interface needs to be proxied.
*/
private static String createCompoundProxyName(String contextId, Bean<?> bean, TypeInfo typeInfo, StringBuilder name) {
final List<String> interfaces = new ArrayList<String>();
Expand Down
2 changes: 1 addition & 1 deletion security/integration/jersey/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
exports io.helidon.security.integration.jersey;

// needed for jersey injection
opens io.helidon.security.integration.jersey to hk2.locator,hk2.utils,weld.core.impl;
opens io.helidon.security.integration.jersey to hk2.locator,hk2.utils,weld.core.impl, io.helidon.microprofile.cdi;

uses io.helidon.security.providers.common.spi.AnnotationAnalyzer;
}
2 changes: 1 addition & 1 deletion security/security/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
exports io.helidon.security.internal to io.helidon.security.integration.jersey, io.helidon.security.integration.webserver, io.helidon.security.integration.grpc;

// needed for CDI integration
opens io.helidon.security to weld.core.impl;
opens io.helidon.security to weld.core.impl, io.helidon.microprofile.cdi;

uses io.helidon.security.spi.SecurityProviderService;
}
2 changes: 1 addition & 1 deletion tests/apps/bookstore/common/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

requires jakarta.enterprise.cdi.api;

opens io.helidon.tests.apps.bookstore.common to weld.core.impl;
opens io.helidon.tests.apps.bookstore.common to weld.core.impl, io.helidon.microprofile.cdi;

exports io.helidon.tests.apps.bookstore.common;
}
Loading