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

Feature: Add a MethodHandles.Lookup SPI #3535

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions ebean-api/src/main/java/io/ebean/config/ClassLoadConfig.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.ebean.config;

import io.ebean.lookup.Lookups;

/**
* Helper to find classes taking into account the context class loader.
*/
Expand Down Expand Up @@ -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);
}
}
Expand Down
10 changes: 10 additions & 0 deletions ebean-api/src/main/java/io/ebean/config/LookupProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.ebean.config;

import java.lang.invoke.MethodHandles.Lookup;

/** Provides a Lookup instance for accessing entity/dto fields. */
public interface LookupProvider {

Lookup provideLookup();

}
5 changes: 3 additions & 2 deletions ebean-api/src/main/java/io/ebean/event/ClassUtil.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.ebean.event;

import io.ebean.lookup.Lookups;

/**
* Helper to find classes taking into account the context class loader.
Expand All @@ -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);
}
Expand Down
30 changes: 30 additions & 0 deletions ebean-api/src/main/java/io/ebean/lookup/Lookups.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.ebean.lookup;

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;

public final class Lookups {

private static final Map<String, Lookup> LOOKUP_MAP =
ServiceLoader.load(LookupProvider.class).stream()
.collect(toMap(p -> p.type().getModule().getName(), p -> p.get().provideLookup()));

private static final Lookup LOOKUP = MethodHandles.publicLookup();

private static final MethodType VOID = MethodType.methodType(void.class);

public static Lookup getLookup(Class<?> type) {
return LOOKUP_MAP.getOrDefault(type.getModule().getName(), LOOKUP);
}

public static <T> T newDefaultInstance(Class<?> type) throws Throwable {
return (T) getLookup(type).findConstructor(type, VOID).invoke();
}
}
8 changes: 5 additions & 3 deletions ebean-api/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -22,15 +23,16 @@
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.lookup to io.ebean.core;
exports io.ebean.meta;
exports io.ebean.metric;
exports io.ebean.search;
exports io.ebean.service;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.lookup.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;
Expand Down Expand Up @@ -94,9 +95,9 @@ public BootupClasses(Set<Class<?>> 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);
}
Expand Down Expand Up @@ -206,12 +207,12 @@ public void addChangeLogInstances(DatabaseBuilder.Settings config) {
*/
private <T> T create(Class<T> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.lookup.Lookups;
import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.MetricVisitor;
import io.ebean.meta.QueryPlanInit;
Expand Down Expand Up @@ -414,8 +415,8 @@ EntityBean createPrototypeEntityBean(Class<T> 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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.ebean.PersistenceIOException;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebean.lookup.Lookups;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
Expand All @@ -22,8 +23,8 @@ class BeanDescriptorElementEmbedded<T> extends BeanDescriptorElement<T> {
BeanDescriptorElementEmbedded(BeanDescriptorMap owner, DeployBeanDescriptor<T> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.event.*;
import io.ebean.event.changelog.ChangeLogFilter;
import io.ebean.lookup.Lookups;
import io.ebean.text.PathProperties;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.ConcurrencyMode;
Expand All @@ -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.*;

/**
Expand All @@ -48,6 +49,8 @@ public int compare(DeployBeanProperty o1, DeployBeanProperty o2) {

private final DatabaseBuilder.Settings config;
private final BeanDescriptorManager manager;
private final Lookup lookup;

/**
* Map of BeanProperty Linked so as to preserve order.
*/
Expand Down Expand Up @@ -136,6 +139,7 @@ public DeployBeanDescriptor(BeanDescriptorManager manager, Class<T> beanType, Da
this.manager = manager;
this.config = config;
this.beanType = beanType;
this.lookup = Lookups.getLookup(beanType);
}

public BindMaxLength bindMaxLength() {
Expand All @@ -144,8 +148,7 @@ public BindMaxLength bindMaxLength() {

private String[] readPropertyNames() {
try {
Field field = beanType.getField("_ebean_props");
return (String[]) field.get(null);
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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.lookup.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 {
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.lookup.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;
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.core.type.*;
import io.ebean.lookup.Lookups;
import io.ebean.types.Cidr;
import io.ebean.types.Inet;
import io.ebean.util.AnnotationUtil;
Expand All @@ -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;
Expand Down Expand Up @@ -597,19 +599,28 @@ private ScalarTypeEnum<?> createEnumScalarType(Class enumType, Map<String, Strin
private void initialiseCustomScalarTypes(BootupClasses bootupClasses) {
for (Class<? extends ScalarType<?>> cls : bootupClasses.getScalarTypes()) {
try {
var lookup = Lookups.getLookup(cls);
ScalarType<?> scalarType;
if (objectMapper == null) {
scalarType = cls.getDeclaredConstructor().newInstance();
scalarType =
(ScalarType<?>)
lookup.findConstructor(cls, MethodType.methodType(void.class)).invoke();
} 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 =
(ScalarType<?>)
lookup.findConstructor(cls, MethodType.methodType(void.class)).invoke();
}
}
add(scalarType);
} catch (Exception e) {
} catch (Throwable e) {
log.log(ERROR, "Error loading ScalarType " + cls.getName(), e);
}
}
Expand Down Expand Up @@ -639,11 +650,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);
}
}
Expand All @@ -663,11 +674,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);
}
}
Expand Down
Loading