Skip to content

Commit

Permalink
Only parse persistence.xml at build time
Browse files Browse the repository at this point in the history
  • Loading branch information
stuartwdouglas committed Sep 24, 2018
1 parent 83b352e commit f4f4903
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -38,6 +41,16 @@ public interface BytecodeRecorder extends AutoCloseable {
*/
<F, T> void registerSubstitution(Class<F> from, Class<T> to, Class<? extends ObjectSubstitution<F, T>> 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 <T> The type of the object
*/
<T> void registerNonDefaultConstructor(Constructor<T> constructor, Function<T, List<Object>> parameters);

/**
* Creates an instance factory that can be used to create an injected instance.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -18,14 +20,17 @@
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;
import org.eclipse.microprofile.config.ConfigProvider;
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;
Expand All @@ -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;
Expand All @@ -51,6 +58,7 @@ public class BytecodeRecorderImpl implements BytecodeRecorder {
private final Map<Class, ProxyFactory<?>> returnValueProxy = new HashMap<>();
private final IdentityHashMap<Class<?>, String> classProxies = new IdentityHashMap<>();
private final Map<Class<?>, SubstitutionHolder> substitutions = new HashMap<>();
private final Map<Class<?>, NonDefaultConstructorHolder> nonDefaulConstructors = new HashMap<>();

public BytecodeRecorderImpl(ClassLoader classLoader, String className, Class<?> serviceType, ClassOutput classOutput) {
this.classLoader = classLoader;
Expand Down Expand Up @@ -86,6 +94,11 @@ public <F, T> void registerSubstitution(Class<F> from, Class<T> to, Class<? exte
substitutions.put(from, new SubstitutionHolder(from, to, substitution));
}

@Override
public <T> void registerNonDefaultConstructor(Constructor<T> constructor, Function<T, List<Object>> parameters) {
nonDefaulConstructors.put(constructor.getDeclaringClass(), new NonDefaultConstructorHolder(constructor, (Function<Object, List<Object>>) parameters));
}

@Override
public InjectionInstance<?> newInstanceFactory(String className) {
NewInstance instance = new NewInstance(className);
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -361,28 +382,77 @@ 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<Object> 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<ResultHandle> 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

Collection propertyValue = (Collection) PropertyUtils.getProperty(param, i.getName());
if(!propertyValue.isEmpty()) {
ResultHandle prop = method.invokeVirtualMethod(MethodDescriptor.ofMethod(i.getReadMethod()), out);
for (Object c : 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

Map<Object, Object> propertyValue = (Map<Object, Object>)PropertyUtils.getProperty(param, i.getName());
if(!propertyValue.isEmpty()) {
ResultHandle prop = method.invokeVirtualMethod(MethodDescriptor.ofMethod(i.getReadMethod()), out);
for (Map.Entry<Object, Object> entry : 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) {
Expand Down Expand Up @@ -465,4 +535,14 @@ static final class SubstitutionHolder {
}
}

static final class NonDefaultConstructorHolder {
final Constructor<?> constructor;
final Function<Object, List<Object>> paramGenerator;

NonDefaultConstructorHolder(Constructor<?> constructor, Function<Object, List<Object>> paramGenerator) {
this.constructor = constructor;
this.paramGenerator = paramGenerator;
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.jboss.protean.gizmo;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;

Expand Down Expand Up @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ public EntityManagerFactoryBuilder withDataSource(DataSource dataSource) {
public EntityManagerFactory build() {
SessionFactoryBuilder sfBuilder = metadata.getSessionFactoryBuilder();
populate( sfBuilder, standardServiceRegistry );

try {
return sfBuilder.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ParsedPersistenceXmlDescriptor> parsedPersistenceXmlDescriptors = loadOriginalXMLParsedDescriptors();
public static void initializeJpa(List<ParsedPersistenceXmlDescriptor> parsedPersistenceXmlDescriptors) {
final List<PersistenceUnitDescriptor> units = convertPersistenceUnits( parsedPersistenceXmlDescriptors );
final Map<String,RecordedState> metadata = constructMetadataAdvance( parsedPersistenceXmlDescriptors );
return new PUStatus( units, metadata );
COMPACT_UNITS = new PUStatus( units, metadata );
}

private static List<PersistenceUnitDescriptor> convertPersistenceUnits(final List<ParsedPersistenceXmlDescriptor> parsedPersistenceXmlDescriptors) {
Expand All @@ -37,11 +36,13 @@ private static List<PersistenceUnitDescriptor> convertPersistenceUnits(final Lis
}
}

private static List<ParsedPersistenceXmlDescriptor> loadOriginalXMLParsedDescriptors() {
public static List<ParsedPersistenceXmlDescriptor> 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<Object, Object> configurationOverrides = Collections.emptyMap();
return PersistenceXmlParser.locatePersistenceUnits( configurationOverrides );
List<ParsedPersistenceXmlDescriptor> ret = PersistenceXmlParser.locatePersistenceUnits(configurationOverrides);
initializeJpa(ret);
return ret;
}

private static Map<String,RecordedState> constructMetadataAdvance(final List<ParsedPersistenceXmlDescriptor> parsedPersistenceXmlDescriptors) {
Expand All @@ -57,6 +58,9 @@ private static Map<String,RecordedState> constructMetadataAdvance(final List<Par
}

static RecordedState getMetadata(String persistenceUnitName) {
if(COMPACT_UNITS == null) {
throw new RuntimeException("JPA not initialized yet");
}
Object key = persistenceUnitName;
if ( persistenceUnitName == null ) {
key = NO_NAME_TOKEN;
Expand All @@ -79,6 +83,9 @@ private static RecordedState createMetadata(PersistenceUnitDescriptor unit) {

// Not a public contract but used by Shamrock
public static List<PersistenceUnitDescriptor> getPersistenceUnitDescriptors() {
if(COMPACT_UNITS == null) {
throw new RuntimeException("JPA not initialized yet");
}
return COMPACT_UNITS.units;
}

Expand All @@ -94,8 +101,4 @@ public PUStatus(final List<PersistenceUnitDescriptor> units, final Map<String, R

}

public static void featureInit(){
System.out.println("Hibernate's PersistenceUnitsHolder was initialized");
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
package org.jboss.shamrock.jpa;

import java.net.URL;
import java.util.Collections;
import java.util.List;

import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
import org.hibernate.protean.impl.PersistenceUnitsHolder;
import org.jboss.shamrock.deployment.ArchiveContext;
import org.jboss.shamrock.deployment.ProcessorContext;
import org.jboss.shamrock.deployment.ResourceProcessor;
import org.jboss.shamrock.deployment.RuntimePriority;
import org.jboss.shamrock.deployment.codegen.BytecodeRecorder;
import org.jboss.shamrock.jpa.runtime.JPADeploymentTemplate;

/**
* Simulacrum of JPA bootstrap.
Expand All @@ -18,6 +27,13 @@ public final class HibernateResourceProcessor implements ResourceProcessor {
@Override
public void process(final ArchiveContext archiveContext, final ProcessorContext processorContext) throws Exception {

List<ParsedPersistenceXmlDescriptor> 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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -45,4 +47,8 @@ public Class<? extends Annotation> annotationType() {
}
}

public void initMetadata(List<ParsedPersistenceXmlDescriptor> parsedPersistenceXmlDescriptors) {
PersistenceUnitsHolder.initializeJpa(parsedPersistenceXmlDescriptors);
}

}

0 comments on commit f4f4903

Please sign in to comment.