From 2a10ed5e619d5a122a576602ee4e8535d3ce5564 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Mon, 24 Sep 2018 11:26:27 +1000 Subject: [PATCH] Only parse persistence.xml at build time --- .../shamrock/deployment/RuntimePriority.java | 1 + .../deployment/codegen/BytecodeRecorder.java | 13 +++ .../codegen/BytecodeRecorderImpl.java | 94 +++++++++++++++++-- .../jboss/protean/gizmo/MethodDescriptor.java | 5 + .../FastBootEntityManagerFactoryBuilder.java | 1 - .../protean/impl/PersistenceUnitsHolder.java | 23 +++-- .../jpa/HibernateResourceProcessor.java | 16 ++++ .../jpa/runtime/JPADeploymentTemplate.java | 6 ++ 8 files changed, 139 insertions(+), 20 deletions(-) diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/RuntimePriority.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/RuntimePriority.java index b173d99fb6d297..6c291243048a25 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/RuntimePriority.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/RuntimePriority.java @@ -8,6 +8,7 @@ public class RuntimePriority { public static final int UNDERTOW_CREATE_DEPLOYMENT = 100; + public static final int JPA_DEPLOYMENT = 150; public static final int UNDERTOW_REGISTER_SERVLET = 200; public static final int FAULT_TOLERANCE_DEPLOYMENT = 250; public static final int HEALTH_DEPLOYMENT = 260; diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/codegen/BytecodeRecorder.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/codegen/BytecodeRecorder.java index 035ea4635e8a62..7b9671ca856915 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/codegen/BytecodeRecorder.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/codegen/BytecodeRecorder.java @@ -1,6 +1,9 @@ package org.jboss.shamrock.deployment.codegen; import java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.List; +import java.util.function.Function; import org.jboss.shamrock.runtime.InjectionInstance; @@ -38,6 +41,16 @@ public interface BytecodeRecorder extends AutoCloseable { */ void registerSubstitution(Class from, Class to, Class> substitution); + /** + * Registers a way to construct an object via a non-default constructor. Each object may only have at most one + * non-default constructor registered + * + * @param constructor The constructor + * @param parameters A function that maps the object to a list of constructor parameters + * @param The type of the object + */ + void registerNonDefaultConstructor(Constructor constructor, Function> parameters); + /** * Creates an instance factory that can be used to create an injected instance. *

diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/codegen/BytecodeRecorderImpl.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/codegen/BytecodeRecorderImpl.java index 35099d581e6fc8..eaf6d655c58f4a 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/codegen/BytecodeRecorderImpl.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/codegen/BytecodeRecorderImpl.java @@ -4,12 +4,14 @@ import static org.jboss.protean.gizmo.MethodDescriptor.ofMethod; import java.beans.PropertyDescriptor; -import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Array; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -18,6 +20,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import org.apache.commons.beanutils.PropertyUtils; import org.eclipse.microprofile.config.Config; @@ -25,7 +28,9 @@ import org.jboss.invocation.proxy.ProxyConfiguration; import org.jboss.invocation.proxy.ProxyFactory; import org.jboss.protean.gizmo.BranchResult; +import org.jboss.protean.gizmo.CatchBlockCreator; import org.jboss.protean.gizmo.ClassCreator; +import org.jboss.protean.gizmo.ExceptionTable; import org.jboss.protean.gizmo.MethodCreator; import org.jboss.protean.gizmo.MethodDescriptor; import org.jboss.protean.gizmo.ResultHandle; @@ -40,6 +45,8 @@ public class BytecodeRecorderImpl implements BytecodeRecorder { private static final AtomicInteger COUNT = new AtomicInteger(); + private static final MethodDescriptor COLLECTION_ADD = ofMethod(Collection.class, "add", boolean.class, Object.class); + private static final MethodDescriptor MAP_PUT = ofMethod(Map.class, "put", Object.class, Object.class, Object.class); private final ClassLoader classLoader; private final String className; @@ -51,6 +58,7 @@ public class BytecodeRecorderImpl implements BytecodeRecorder { private final Map> returnValueProxy = new HashMap<>(); private final IdentityHashMap, String> classProxies = new IdentityHashMap<>(); private final Map, SubstitutionHolder> substitutions = new HashMap<>(); + private final Map, NonDefaultConstructorHolder> nonDefaulConstructors = new HashMap<>(); public BytecodeRecorderImpl(ClassLoader classLoader, String className, Class serviceType, ClassOutput classOutput) { this.classLoader = classLoader; @@ -86,6 +94,11 @@ public void registerSubstitution(Class from, Class to, Class void registerNonDefaultConstructor(Constructor constructor, Function> parameters) { + nonDefaulConstructors.put(constructor.getDeclaringClass(), new NonDefaultConstructorHolder(constructor, (Function>) parameters)); + } + @Override public InjectionInstance newInstanceFactory(String className) { NewInstance instance = new NewInstance(className); @@ -307,6 +320,14 @@ private ResultHandle loadObjectInstance(MethodCreator method, Object param, Map< } else { out = method.load((String) param); } + } else if (param instanceof URL) { + String url = ((URL) param).toExternalForm(); + ExceptionTable et = method.addTryCatch(); + out = method.newInstance(MethodDescriptor.ofConstructor(URL.class, String.class), method.load(url)); + CatchBlockCreator malformed = et.addCatchClause(MalformedURLException.class); + malformed.throwException(RuntimeException.class, "Malformed URL", malformed.getCaughtException()); + et.complete(); + } else if (param instanceof Enum) { Enum e = (Enum) param; ResultHandle nm = method.load(e.name()); @@ -361,28 +382,73 @@ private ResultHandle loadObjectInstance(MethodCreator method, Object param, Map< method.writeArrayValue(out, method.load(i), component); } } else { - try { - param.getClass().getDeclaredConstructor(); - } catch (NoSuchMethodException e) { - throw new RuntimeException("Unable to serialize objects of type " + param.getClass() + " to bytecode as it has no default constructor"); + if(nonDefaulConstructors.containsKey(param.getClass())) { + NonDefaultConstructorHolder holder = nonDefaulConstructors.get(param.getClass()); + List params = holder.paramGenerator.apply(param); + if(params.size() != holder.constructor.getParameterCount()) { + throw new RuntimeException("Unable to serialize " + param + " as the wrong number of parameters were generated for " + holder.constructor); + } + List handles = new ArrayList<>(); + int count = 0; + for(Object i : params) { + handles.add(loadObjectInstance(method, i, returnValueResults, holder.constructor.getParameterTypes()[count++])); + } + out = method.newInstance(ofConstructor(holder.constructor.getDeclaringClass(), holder.constructor.getParameterTypes()), handles.toArray(new ResultHandle[handles.size()])); + } else { + try { + param.getClass().getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Unable to serialize objects of type " + param.getClass() + " to bytecode as it has no default constructor"); + } + + out = method.newInstance(ofConstructor(param.getClass())); } - out = method.newInstance(ofConstructor(param.getClass())); if (param instanceof Collection) { for (Object i : (Collection) param) { ResultHandle val = loadObjectInstance(method, i, returnValueResults, i.getClass()); - method.invokeInterfaceMethod(ofMethod(Collection.class, "add", boolean.class, Object.class), out, val); + method.invokeInterfaceMethod(COLLECTION_ADD, out, val); } } if (param instanceof Map) { for (Map.Entry i : ((Map) param).entrySet()) { ResultHandle key = loadObjectInstance(method, i.getKey(), returnValueResults, i.getKey().getClass()); ResultHandle val = loadObjectInstance(method, i.getValue(), returnValueResults, i.getValue().getClass()); - method.invokeInterfaceMethod(ofMethod(Map.class, "put", Object.class, Object.class, Object.class), out, key, val); + method.invokeInterfaceMethod(MAP_PUT, out, key, val); } } PropertyDescriptor[] desc = PropertyUtils.getPropertyDescriptors(param); for (PropertyDescriptor i : desc) { - if (i.getReadMethod() != null && i.getWriteMethod() != null) { + if(i.getReadMethod() != null && i.getWriteMethod() == null ) { + try { + //read only prop, we may still be able to do stuff with it if it is a collection + if(Collection.class.isAssignableFrom(i.getPropertyType())) { + //special case, a collection with only a read method + //we assume we can just add to the connection + + Object propertyValue = PropertyUtils.getProperty(param, i.getName()); + ResultHandle prop = method.invokeVirtualMethod(MethodDescriptor.ofMethod(i.getReadMethod()), out); + for (Object c : (Collection)propertyValue) { + ResultHandle toAdd = loadObjectInstance(method, c, returnValueResults, Object.class); + method.invokeInterfaceMethod(COLLECTION_ADD, prop, toAdd); + } + + } else if(Map.class.isAssignableFrom(i.getPropertyType())) { + //special case, a map with only a read method + //we assume we can just add to the map + + Object propertyValue = PropertyUtils.getProperty(param, i.getName()); + ResultHandle prop = method.invokeVirtualMethod(MethodDescriptor.ofMethod(i.getReadMethod()), out); + for (Map.Entry entry : ((Map)propertyValue).entrySet()) { + ResultHandle key = loadObjectInstance(method, entry.getKey(), returnValueResults, Object.class); + ResultHandle val = loadObjectInstance(method, entry.getValue(), returnValueResults, Object.class); + method.invokeInterfaceMethod(MAP_PUT, prop, key, val); + } + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + } else if (i.getReadMethod() != null && i.getWriteMethod() != null) { try { Object propertyValue = PropertyUtils.getProperty(param, i.getName()); if (propertyValue == null) { @@ -465,4 +531,14 @@ static final class SubstitutionHolder { } } + static final class NonDefaultConstructorHolder { + final Constructor constructor; + final Function> paramGenerator; + + NonDefaultConstructorHolder(Constructor constructor, Function> paramGenerator) { + this.constructor = constructor; + this.paramGenerator = paramGenerator; + } + } + } diff --git a/ext/gizmo/src/main/java/org/jboss/protean/gizmo/MethodDescriptor.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/MethodDescriptor.java index 722f47d960db04..e7fdf61a7ad76f 100644 --- a/ext/gizmo/src/main/java/org/jboss/protean/gizmo/MethodDescriptor.java +++ b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/MethodDescriptor.java @@ -1,5 +1,6 @@ package org.jboss.protean.gizmo; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.Objects; @@ -58,6 +59,10 @@ public static MethodDescriptor ofMethod(Class declaringClass, String name, Cl return new MethodDescriptor(DescriptorUtils.objectToInternalClassName(declaringClass), name, DescriptorUtils.classToStringRepresentation(returnType), args); } + public static MethodDescriptor ofMethod(Method method) { + return ofMethod(method.getDeclaringClass(), method.getName(), method.getReturnType(), method.getParameterTypes()); + } + public static MethodDescriptor ofMethod(Object declaringClass, String name, Object returnType, Object... parameterTypes) { return new MethodDescriptor(DescriptorUtils.objectToInternalClassName(declaringClass), name, DescriptorUtils.objectToDescriptor(returnType), DescriptorUtils.objectsToDescriptor(parameterTypes)); } diff --git a/ext/hibernate-protean/hibernate-orm-protean/src/main/java/org/hibernate/protean/impl/FastBootEntityManagerFactoryBuilder.java b/ext/hibernate-protean/hibernate-orm-protean/src/main/java/org/hibernate/protean/impl/FastBootEntityManagerFactoryBuilder.java index c0a682ed1e9d14..2441528ca7b0f0 100644 --- a/ext/hibernate-protean/hibernate-orm-protean/src/main/java/org/hibernate/protean/impl/FastBootEntityManagerFactoryBuilder.java +++ b/ext/hibernate-protean/hibernate-orm-protean/src/main/java/org/hibernate/protean/impl/FastBootEntityManagerFactoryBuilder.java @@ -61,7 +61,6 @@ public EntityManagerFactoryBuilder withDataSource(DataSource dataSource) { public EntityManagerFactory build() { SessionFactoryBuilder sfBuilder = metadata.getSessionFactoryBuilder(); populate( sfBuilder, standardServiceRegistry ); - try { return sfBuilder.build(); } diff --git a/ext/hibernate-protean/hibernate-orm-protean/src/main/java/org/hibernate/protean/impl/PersistenceUnitsHolder.java b/ext/hibernate-protean/hibernate-orm-protean/src/main/java/org/hibernate/protean/impl/PersistenceUnitsHolder.java index 35b7dc91aa30f6..925bdf75728c59 100644 --- a/ext/hibernate-protean/hibernate-orm-protean/src/main/java/org/hibernate/protean/impl/PersistenceUnitsHolder.java +++ b/ext/hibernate-protean/hibernate-orm-protean/src/main/java/org/hibernate/protean/impl/PersistenceUnitsHolder.java @@ -14,15 +14,14 @@ public final class PersistenceUnitsHolder { - private static final PUStatus COMPACT_UNITS = builderPuStatus(); + private static volatile PUStatus COMPACT_UNITS = null; private static final Object NO_NAME_TOKEN = new Object(); - private static PUStatus builderPuStatus() { - final List parsedPersistenceXmlDescriptors = loadOriginalXMLParsedDescriptors(); + public static void initializeJpa(List parsedPersistenceXmlDescriptors) { final List units = convertPersistenceUnits( parsedPersistenceXmlDescriptors ); final Map metadata = constructMetadataAdvance( parsedPersistenceXmlDescriptors ); - return new PUStatus( units, metadata ); + COMPACT_UNITS = new PUStatus( units, metadata ); } private static List convertPersistenceUnits(final List parsedPersistenceXmlDescriptors) { @@ -37,11 +36,13 @@ private static List convertPersistenceUnits(final Lis } } - private static List loadOriginalXMLParsedDescriptors() { + public static List loadOriginalXMLParsedDescriptors() { //Enforce the persistence.xml configuration to be interpreted literally without allowing runtime overrides; //(check for the runtime provided properties to be empty as well) Map configurationOverrides = Collections.emptyMap(); - return PersistenceXmlParser.locatePersistenceUnits( configurationOverrides ); + List ret = PersistenceXmlParser.locatePersistenceUnits(configurationOverrides); + initializeJpa(ret); + return ret; } private static Map constructMetadataAdvance(final List parsedPersistenceXmlDescriptors) { @@ -57,6 +58,9 @@ private static Map constructMetadataAdvance(final List getPersistenceUnitDescriptors() { + if(COMPACT_UNITS == null) { + throw new RuntimeException("JPA not initialized yet"); + } return COMPACT_UNITS.units; } @@ -94,8 +101,4 @@ public PUStatus(final List units, final Map descriptors = PersistenceUnitsHolder.loadOriginalXMLParsedDescriptors(); + try (BytecodeRecorder recorder = processorContext.addStaticInitTask(RuntimePriority.JPA_DEPLOYMENT)) { + recorder.registerNonDefaultConstructor(ParsedPersistenceXmlDescriptor.class.getDeclaredConstructor(URL.class), (i) -> Collections.singletonList( i.getPersistenceUnitRootUrl())); + recorder.getRecordingProxy(JPADeploymentTemplate.class).initMetadata(descriptors); + } + + // Hibernate specific reflective classes; these are independent from the model and configuration details. HibernateReflectiveNeeds.registerStaticReflectiveNeeds(processorContext); diff --git a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPADeploymentTemplate.java b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPADeploymentTemplate.java index f57fbcfe96108b..353cc3cf8ecc9f 100644 --- a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPADeploymentTemplate.java +++ b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPADeploymentTemplate.java @@ -1,6 +1,8 @@ package org.jboss.shamrock.jpa.runtime; +import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; import org.hibernate.protean.Hibernate; +import org.hibernate.protean.impl.PersistenceUnitsHolder; import org.jboss.shamrock.jpa.runtime.cdi.SystemEntityManager; import org.jboss.shamrock.runtime.BeanContainer; import org.jboss.shamrock.runtime.ContextObject; @@ -45,4 +47,8 @@ public Class annotationType() { } } + public void initMetadata(List parsedPersistenceXmlDescriptors) { + PersistenceUnitsHolder.initializeJpa(parsedPersistenceXmlDescriptors); + } + }