diff --git a/ebean-api/src/main/java/io/ebean/config/ClassLoadConfig.java b/ebean-api/src/main/java/io/ebean/config/ClassLoadConfig.java index c31be3aa5b..7fa749ca87 100644 --- a/ebean-api/src/main/java/io/ebean/config/ClassLoadConfig.java +++ b/ebean-api/src/main/java/io/ebean/config/ClassLoadConfig.java @@ -1,5 +1,7 @@ package io.ebean.config; +import io.ebean.plugin.Lookups; + /** * Helper to find classes taking into account the context class loader. */ @@ -73,9 +75,8 @@ public boolean isJacksonObjectMapperPresent() { */ public Object newInstance(String className) { try { - Class cls = forName(className); - return cls.getDeclaredConstructor().newInstance(); - } catch (Exception e) { + return Lookups.newDefaultInstance(forName(className)); + } catch (Throwable e) { throw new IllegalArgumentException("Error constructing " + className, e); } } diff --git a/ebean-api/src/main/java/io/ebean/config/LookupProvider.java b/ebean-api/src/main/java/io/ebean/config/LookupProvider.java new file mode 100644 index 0000000000..f0e6a8243b --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/config/LookupProvider.java @@ -0,0 +1,15 @@ +package io.ebean.config; + +import java.lang.invoke.MethodHandles.Lookup; + +/** + * Provides a Lookup instance for accessing entity/dto fields. + */ +public interface LookupProvider { + + /** + * Return the Lookup. + */ + Lookup provideLookup(); + +} diff --git a/ebean-api/src/main/java/io/ebean/event/ClassUtil.java b/ebean-api/src/main/java/io/ebean/event/ClassUtil.java index 72cea3955c..c2469b79c8 100644 --- a/ebean-api/src/main/java/io/ebean/event/ClassUtil.java +++ b/ebean-api/src/main/java/io/ebean/event/ClassUtil.java @@ -1,5 +1,6 @@ package io.ebean.event; +import io.ebean.plugin.Lookups; /** * Helper to find classes taking into account the context class loader. @@ -12,8 +13,8 @@ class ClassUtil { static Object newInstance(String className) { try { Class cls = forName(className); - return cls.getDeclaredConstructor().newInstance(); - } catch (Exception e) { + return Lookups.newDefaultInstance(cls); + } catch (Throwable e) { String msg = "Error constructing " + className; throw new IllegalArgumentException(msg, e); } diff --git a/ebean-api/src/main/java/io/ebean/plugin/Lookups.java b/ebean-api/src/main/java/io/ebean/plugin/Lookups.java new file mode 100644 index 0000000000..5b3468b1bd --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/plugin/Lookups.java @@ -0,0 +1,41 @@ +package io.ebean.plugin; + +import static java.util.stream.Collectors.toMap; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.util.Map; +import java.util.ServiceLoader; + +import io.ebean.config.LookupProvider; + +/** + * Provides Lookup instances using potentially module specific Lookups. + */ +public final class Lookups { + + private static final Map LOOKUP_MAP = + ServiceLoader.load(LookupProvider.class).stream() + .collect(toMap(p -> p.type().getModule().getName(), p -> p.get().provideLookup())); + + private static final Lookup DEFAULT_LOOKUP = MethodHandles.publicLookup(); + + private static final MethodType VOID = MethodType.methodType(void.class); + + /** + * Return a Lookup ideally for the module associated with the given type. + */ + public static Lookup getLookup(Class type) { + return LOOKUP_MAP.getOrDefault(type.getModule().getName(), DEFAULT_LOOKUP); + } + + /** + * Find the default constructor and return a new instance for the given type + * potentially using a module specific Lookup instance. + */ + @SuppressWarnings("unchecked") + public static T newDefaultInstance(Class type) throws Throwable { + return (T) getLookup(type).findConstructor(type, VOID).invoke(); + } +} diff --git a/ebean-api/src/main/java/module-info.java b/ebean-api/src/main/java/module-info.java index 27548d1ee6..e105eb0df3 100644 --- a/ebean-api/src/main/java/module-info.java +++ b/ebean-api/src/main/java/module-info.java @@ -1,6 +1,7 @@ module io.ebean.api { uses io.ebean.config.AutoConfigure; + uses io.ebean.config.LookupProvider; uses io.ebean.datasource.DataSourceAlertFactory; uses io.ebean.service.BootstrapService; uses io.ebean.service.SpiJsonService; @@ -22,15 +23,15 @@ exports io.ebean; exports io.ebean.bean; exports io.ebean.cache; - exports io.ebean.meta; + exports io.ebean.common; exports io.ebean.config; exports io.ebean.config.dbplatform; + exports io.ebean.docstore; exports io.ebean.event; exports io.ebean.event.readaudit; exports io.ebean.event.changelog; - exports io.ebean.common; - exports io.ebean.docstore; exports io.ebean.plugin; + exports io.ebean.meta; exports io.ebean.metric; exports io.ebean.search; exports io.ebean.service; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java index 726411dd2f..f518309a8b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java @@ -11,14 +11,15 @@ import io.ebean.event.changelog.ChangeLogRegister; import io.ebean.event.readaudit.ReadAuditLogger; import io.ebean.event.readaudit.ReadAuditPrepare; +import io.ebean.plugin.Lookups; import io.ebean.util.AnnotationUtil; import io.ebeaninternal.api.CoreLog; - import jakarta.persistence.AttributeConverter; import jakarta.persistence.Embeddable; import jakarta.persistence.Entity; import jakarta.persistence.Table; import java.lang.annotation.Annotation; +import java.lang.invoke.MethodType; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; @@ -94,9 +95,9 @@ public BootupClasses(Set> classes) { public void runServerConfigStartup(DatabaseBuilder config) { for (Class cls : serverConfigStartupCandidates) { try { - ServerConfigStartup newInstance = (ServerConfigStartup) cls.getDeclaredConstructor().newInstance(); + ServerConfigStartup newInstance = Lookups.newDefaultInstance(cls); newInstance.onStart(config); - } catch (Exception e) { + } catch (Throwable e) { // assume that the desired behavior is to fail - add your own try catch if needed throw new IllegalStateException("Error running ServerConfigStartup " + cls, e); } @@ -206,12 +207,12 @@ public void addChangeLogInstances(DatabaseBuilder.Settings config) { */ private T create(Class cls, boolean logOnException) { try { - return cls.getConstructor().newInstance(); + return Lookups.newDefaultInstance(cls); } catch (NoSuchMethodException e) { log.log(DEBUG, "Ignore/expected - no default constructor: {0}", e.getMessage()); return null; - } catch (Exception e) { + } catch (Throwable e) { if (logOnException) { // not expected but we log and carry on log.log(ERROR, "Error creating " + cls, e); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index b0988c2e35..34da592a25 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -23,6 +23,7 @@ import io.ebean.plugin.BeanDocType; import io.ebean.plugin.BeanType; import io.ebean.plugin.ExpressionPath; +import io.ebean.plugin.Lookups; import io.ebean.plugin.Property; import io.ebean.util.SplitName; import io.ebeaninternal.api.*; @@ -414,8 +415,8 @@ EntityBean createPrototypeEntityBean(Class beanType) { return null; } try { - return (EntityBean) beanType.getDeclaredConstructor().newInstance(); - } catch (Exception e) { + return Lookups.newDefaultInstance(beanType); + } catch (Throwable e) { throw new IllegalStateException("Error trying to create the prototypeEntityBean for " + beanType, e); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElementEmbedded.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElementEmbedded.java index 71d79b9dce..fcb4e4c390 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElementEmbedded.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElementEmbedded.java @@ -3,6 +3,7 @@ import io.ebean.PersistenceIOException; import io.ebean.SqlUpdate; import io.ebean.bean.EntityBean; +import io.ebean.plugin.Lookups; import io.ebeaninternal.api.json.SpiJsonReader; import io.ebeaninternal.api.json.SpiJsonWriter; import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; @@ -22,8 +23,8 @@ class BeanDescriptorElementEmbedded extends BeanDescriptorElement { BeanDescriptorElementEmbedded(BeanDescriptorMap owner, DeployBeanDescriptor deploy, ElementHelp elementHelp) { super(owner, deploy, elementHelp); try { - this.prototype = (EntityBean) beanType.getDeclaredConstructor().newInstance(); - } catch (Exception e) { + this.prototype = Lookups.newDefaultInstance(beanType); + } catch (Throwable e) { throw new IllegalStateException("Unable to create entity bean prototype for "+beanType); } BeanPropertyAssocOne[] embedded = propertiesEmbedded(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index f96cabb465..e105c46dd6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -10,6 +10,7 @@ import io.ebean.config.dbplatform.PlatformIdGenerator; import io.ebean.event.*; import io.ebean.event.changelog.ChangeLogFilter; +import io.ebean.plugin.Lookups; import io.ebean.text.PathProperties; import io.ebean.util.SplitName; import io.ebeaninternal.api.ConcurrencyMode; @@ -24,7 +25,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.MappedSuperclass; -import java.lang.reflect.Field; +import java.lang.invoke.MethodHandles.Lookup; import java.util.*; /** @@ -48,6 +49,7 @@ public int compare(DeployBeanProperty o1, DeployBeanProperty o2) { private final DatabaseBuilder.Settings config; private final BeanDescriptorManager manager; + /** * Map of BeanProperty Linked so as to preserve order. */ @@ -144,8 +146,8 @@ public BindMaxLength bindMaxLength() { private String[] readPropertyNames() { try { - Field field = beanType.getField("_ebean_props"); - return (String[]) field.get(null); + final Lookup lookup = Lookups.getLookup(beanType); + return (String[]) lookup.findStaticVarHandle(beanType, "_ebean_props", String[].class).get(); } catch (Exception e) { throw new IllegalStateException("Error getting _ebean_props field on type " + beanType, e); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaConstructor.java b/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaConstructor.java index 7b8672fc68..dd72a47a8f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaConstructor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaConstructor.java @@ -1,20 +1,19 @@ package io.ebeaninternal.server.dto; -import io.ebean.core.type.DataReader; -import io.ebean.core.type.ScalarType; -import io.ebeaninternal.server.type.TypeManager; - import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; import java.sql.SQLException; +import io.ebean.core.type.DataReader; +import io.ebean.core.type.ScalarType; +import io.ebean.plugin.Lookups; +import io.ebeaninternal.server.type.TypeManager; + final class DtoMetaConstructor { private final Class[] types; private final MethodHandle handle; - private static final MethodHandles.Lookup LOOKUP = MethodHandles.publicLookup(); private final ScalarType[] scalarTypes; DtoMetaConstructor(TypeManager typeManager, Constructor constructor, Class someClass) throws NoSuchMethodException, IllegalAccessException { @@ -23,7 +22,7 @@ final class DtoMetaConstructor { for (int i = 0; i < types.length; i++) { scalarTypes[i] = typeManager.type(types[i]); } - this.handle = LOOKUP.findConstructor(someClass, typeFor(types)); + this.handle = Lookups.getLookup(someClass).findConstructor(someClass, typeFor(types)); } private MethodType typeFor(Class[] types) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaProperty.java index f0efe9b02d..dd25b225af 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaProperty.java @@ -1,19 +1,17 @@ package io.ebeaninternal.server.dto; -import io.ebean.core.type.DataReader; -import io.ebean.core.type.ScalarType; -import io.ebeaninternal.server.type.TypeManager; - import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.sql.SQLException; -final class DtoMetaProperty implements DtoReadSet { +import io.ebean.core.type.DataReader; +import io.ebean.core.type.ScalarType; +import io.ebean.plugin.Lookups; +import io.ebeaninternal.server.type.TypeManager; - private static final MethodHandles.Lookup LOOKUP = MethodHandles.publicLookup(); +final class DtoMetaProperty implements DtoReadSet { private final Class dtoType; private final String name; @@ -33,7 +31,7 @@ final class DtoMetaProperty implements DtoReadSet { } private static MethodHandle lookupMethodHandle(Class dtoType, Method method) throws NoSuchMethodException, IllegalAccessException { - return LOOKUP.findVirtual(dtoType, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes())); + return Lookups.getLookup(dtoType).findVirtual(dtoType, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes())); } static Type propertyType(Method method) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index d781b2abc2..25eb11ca2e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -7,6 +7,7 @@ import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.core.type.*; +import io.ebean.plugin.Lookups; import io.ebean.types.Cidr; import io.ebean.types.Inet; import io.ebean.util.AnnotationUtil; @@ -20,6 +21,7 @@ import jakarta.persistence.AttributeConverter; import jakarta.persistence.EnumType; import java.io.File; +import java.lang.invoke.MethodType; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; @@ -597,19 +599,20 @@ private ScalarTypeEnum createEnumScalarType(Class enumType, Map> cls : bootupClasses.getScalarTypes()) { try { + var lookup = Lookups.getLookup(cls); ScalarType scalarType; if (objectMapper == null) { - scalarType = cls.getDeclaredConstructor().newInstance(); + scalarType = Lookups.newDefaultInstance(cls); } else { try { // first try objectMapper constructor - scalarType = cls.getDeclaredConstructor(ObjectMapper.class).newInstance(objectMapper); + scalarType = (ScalarType) lookup.findConstructor(cls, MethodType.methodType(ObjectMapper.class)).invoke(objectMapper); } catch (NoSuchMethodException e) { - scalarType = cls.getDeclaredConstructor().newInstance(); + scalarType = Lookups.newDefaultInstance(cls); } } add(scalarType); - } catch (Exception e) { + } catch (Throwable e) { log.log(ERROR, "Error loading ScalarType " + cls.getName(), e); } } @@ -639,11 +642,11 @@ private void initialiseScalarConverters(BootupClasses bootupClasses) { if (wrappedType == null) { throw new IllegalStateException("Could not find ScalarType for: " + paramTypes[1]); } - ScalarTypeConverter converter = foundType.getDeclaredConstructor().newInstance(); + ScalarTypeConverter converter = Lookups.newDefaultInstance(foundType); ScalarTypeWrapper stw = new ScalarTypeWrapper(logicalType, wrappedType, converter); log.log(DEBUG, "Register ScalarTypeWrapper from {0} -> {1} using:{2}", logicalType, persistType, foundType); add(stw); - } catch (Exception e) { + } catch (Throwable e) { log.log(ERROR, "Error registering ScalarTypeConverter " + foundType.getName(), e); } } @@ -663,11 +666,11 @@ private void initialiseAttributeConverters(BootupClasses bootupClasses) { if (wrappedType == null) { throw new IllegalStateException("Could not find ScalarType for: " + paramTypes[1]); } - AttributeConverter converter = foundType.getDeclaredConstructor().newInstance(); + AttributeConverter converter = Lookups.newDefaultInstance(foundType); ScalarTypeWrapper stw = new ScalarTypeWrapper(logicalType, wrappedType, new AttributeConverterAdapter(converter)); log.log(DEBUG, "Register ScalarTypeWrapper from {0} -> {1} using:{2}", logicalType, persistType, foundType); add(stw); - } catch (Exception e) { + } catch (Throwable e) { log.log(ERROR, "Error registering AttributeConverter " + foundType.getName(), e); } }