From 25528fd67b65417c188d917d0741f782e9dc88c2 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 5 Oct 2018 14:40:08 +0200 Subject: [PATCH 1/3] Arc - resource provider SPI --- ext/arc/pom.xml | 8 + .../protean/arc/processor/BeanDeployment.java | 14 + .../protean/arc/processor/BeanProcessor.java | 18 +- .../protean/arc/processor/BuiltinBean.java | 49 ++- .../jboss/protean/arc/processor/DotNames.java | 5 +- .../protean/arc/processor/Injection.java | 17 + .../arc/processor/InjectionPointInfo.java | 18 +- .../jboss/protean/arc/ArcContainerImpl.java | 22 +- .../protean/arc/CreationalContextImpl.java | 6 +- .../org/jboss/protean/arc/InstanceHandle.java | 25 +- .../jboss/protean/arc/InstanceHandleImpl.java | 5 - .../jboss/protean/arc/ResourceProvider.java | 40 ++ .../arc/ResourceReferenceProvider.java | 51 +++ ext/arc/tests/pom.xml | 5 + .../protean/arc/test/ArcTestContainer.java | 87 +++- .../resource/ResourceInjectionTest.java | 370 ++++++++++++++++++ 16 files changed, 706 insertions(+), 34 deletions(-) create mode 100644 ext/arc/runtime/src/main/java/org/jboss/protean/arc/ResourceProvider.java create mode 100644 ext/arc/runtime/src/main/java/org/jboss/protean/arc/ResourceReferenceProvider.java create mode 100644 ext/arc/tests/src/test/java/org/jboss/protean/arc/test/injection/resource/ResourceInjectionTest.java diff --git a/ext/arc/pom.xml b/ext/arc/pom.xml index f99de4f9d51ac..2bd535047a1dc 100644 --- a/ext/arc/pom.xml +++ b/ext/arc/pom.xml @@ -33,6 +33,7 @@ 3.3.2.Final 1.3 1.0.0.Alpha1-SNAPSHOT + 2.2 @@ -118,6 +119,13 @@ + + javax.persistence + javax.persistence-api + ${version.jpa} + test + + diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java index f7a9fddfbb112..5e50cb7f46e13 100644 --- a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java @@ -3,6 +3,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -55,9 +56,18 @@ public class BeanDeployment { private final List, Collection>> annotationTransformers; + private final Set resourceAnnotations; + BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, List, Collection>> annotationTransformers) { + this(index, additionalBeanDefiningAnnotations, annotationTransformers, Collections.emptyList()); + } + + BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, + List, Collection>> annotationTransformers, + Collection resourceAnnotations) { long start = System.currentTimeMillis(); + this.resourceAnnotations = new HashSet<>(resourceAnnotations); this.index = index; this.qualifiers = findQualifiers(index); // TODO interceptor bindings are transitive!!! @@ -109,6 +119,10 @@ StereotypeInfo getStereotype(DotName name) { return stereotypes.get(name); } + Set getResourceAnnotations() { + return resourceAnnotations; + } + Collection getAnnotations(AnnotationTarget target) { Collection annotations = null; switch (target.kind()) { diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java index 1dd1320696d98..1fd52908c0f59 100644 --- a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java @@ -58,9 +58,12 @@ public static Builder builder() { private final List, Collection>> annotationTransformers; + private final Collection resourceAnnotations; + private BeanProcessor(String name, IndexView index, Collection additionalBeanDefiningAnnotations, ResourceOutput output, boolean sharedAnnotationLiterals, ReflectionRegistration reflectionRegistration, - List, Collection>> annotationTransformers) { + List, Collection>> annotationTransformers, + Collection resourceAnnotations) { this.reflectionRegistration = reflectionRegistration; Objects.requireNonNull(output); this.name = name; @@ -69,11 +72,13 @@ private BeanProcessor(String name, IndexView index, Collection addition this.output = output; this.sharedAnnotationLiterals = sharedAnnotationLiterals; this.annotationTransformers = annotationTransformers; + this.resourceAnnotations = resourceAnnotations; } public BeanDeployment process() throws IOException { - BeanDeployment beanDeployment = new BeanDeployment(new IndexWrapper(index), additionalBeanDefiningAnnotations, annotationTransformers); + BeanDeployment beanDeployment = new BeanDeployment(new IndexWrapper(index), additionalBeanDefiningAnnotations, annotationTransformers, + resourceAnnotations); beanDeployment.init(); AnnotationLiteralProcessor annotationLiterals = new AnnotationLiteralProcessor(name, sharedAnnotationLiterals); @@ -182,6 +187,8 @@ public static class Builder { private final List, Collection>> annotationTransformers = new ArrayList<>(); + private final List resourceAnnotations = new ArrayList<>(); + public Builder setName(String name) { this.name = name; return this; @@ -217,9 +224,14 @@ public Builder addAnnotationTransformer(BiFunction resourceAnnotations) { + this.resourceAnnotations.addAll(resourceAnnotations); + return this; + } + public BeanProcessor build() { return new BeanProcessor(name, addBuiltinClasses(index), additionalBeanDefiningAnnotations, output, sharedAnnotationLiterals, - reflectionRegistration, annotationTransformers); + reflectionRegistration, annotationTransformers, resourceAnnotations); } } diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BuiltinBean.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BuiltinBean.java index 14547251c1c9e..8231965677383 100644 --- a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BuiltinBean.java +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BuiltinBean.java @@ -2,6 +2,7 @@ import java.util.HashSet; import java.util.Set; +import java.util.function.Predicate; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; @@ -12,6 +13,8 @@ import org.jboss.protean.arc.InjectableReferenceProvider; import org.jboss.protean.arc.InjectionPointProvider; import org.jboss.protean.arc.InstanceProvider; +import org.jboss.protean.arc.ResourceProvider; +import org.jboss.protean.arc.processor.InjectionPointInfo.Kind; import org.jboss.protean.gizmo.ClassCreator; import org.jboss.protean.gizmo.ClassOutput; import org.jboss.protean.gizmo.FieldDescriptor; @@ -101,27 +104,61 @@ void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionP } } ResultHandle parameterizedType = Types.getTypeHandle(constructor, injectionPoint.requiredType); - ResultHandle eventProvider = constructor.newInstance( - MethodDescriptor.ofConstructor(EventProvider.class, java.lang.reflect.Type.class, Set.class), parameterizedType, qualifiers); + ResultHandle eventProvider = constructor.newInstance(MethodDescriptor.ofConstructor(EventProvider.class, java.lang.reflect.Type.class, Set.class), + parameterizedType, qualifiers); constructor.writeInstanceField(FieldDescriptor.of(clazzCreator.getClassName(), providerName, InjectableReferenceProvider.class.getName()), constructor.getThis(), eventProvider); } - }); + }), RESOURCE(DotNames.OBJECT, new Generator() { + @Override + void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, ClassCreator clazzCreator, + MethodCreator constructor, String providerName, AnnotationLiteralProcessor annotationLiterals) { + + ResultHandle annotations = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + // For a resource field the requiredQualifiers field contains all annotations declared on the field + if (!injectionPoint.requiredQualifiers.isEmpty()) { + for (AnnotationInstance annotation : injectionPoint.requiredQualifiers) { + // Create annotation literal first + ClassInfo annotationClass = beanDeployment.getIndex().getClassByName(annotation.name()); + String annotationLiteralName = annotationLiterals.process(classOutput, annotationClass, annotation, + Types.getPackageName(clazzCreator.getClassName())); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, annotations, + constructor.newInstance(MethodDescriptor.ofConstructor(annotationLiteralName))); + } + } + ResultHandle parameterizedType = Types.getTypeHandle(constructor, injectionPoint.requiredType); + ResultHandle resourceProvider = constructor.newInstance( + MethodDescriptor.ofConstructor(ResourceProvider.class, java.lang.reflect.Type.class, Set.class), parameterizedType, annotations); + constructor.writeInstanceField(FieldDescriptor.of(clazzCreator.getClassName(), providerName, InjectableReferenceProvider.class.getName()), + constructor.getThis(), resourceProvider); + } + }, ip -> ip.kind == Kind.RESOURCE); private final DotName rawTypeDotName; private final Generator generator; + private final Predicate matcher; + BuiltinBean(DotName rawTypeDotName, Generator generator) { + this(rawTypeDotName, generator, ip -> ip.kind == Kind.CDI && rawTypeDotName.equals(ip.requiredType.name())); + } + + BuiltinBean(DotName rawTypeDotName, Generator generator, Predicate matcher) { this.rawTypeDotName = rawTypeDotName; this.generator = generator; + this.matcher = matcher; + } + + boolean matches(InjectionPointInfo injectionPoint) { + return matcher.test(injectionPoint); } - public DotName getRawTypeDotName() { + DotName getRawTypeDotName() { return rawTypeDotName; } - public Generator getGenerator() { + Generator getGenerator() { return generator; } @@ -131,7 +168,7 @@ static boolean resolvesTo(InjectionPointInfo injectionPoint) { static BuiltinBean resolve(InjectionPointInfo injectionPoint) { for (BuiltinBean bean : values()) { - if (bean.getRawTypeDotName().equals(injectionPoint.requiredType.name())) { + if (bean.matches(injectionPoint)) { return bean; } } diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/DotNames.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/DotNames.java index f81f9bf47ba5a..c764d64951897 100644 --- a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/DotNames.java +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/DotNames.java @@ -58,6 +58,9 @@ final class DotNames { static final DotName TYPED = DotName.createSimple(Typed.class.getName()); static final DotName CLASS = DotName.createSimple(Class.class.getName()); + static final DotName PERSISTENCE_CONTEXT = DotName.createSimple("javax.persistence.PersistenceContext"); + static final DotName PERSISTENCE_UNIT = DotName.createSimple("javax.persistence.PersistenceUnit"); + private DotNames() { } @@ -69,7 +72,7 @@ static String simpleName(DotName dotName) { static String packageName(DotName dotName) { String name = dotName.toString(); int index = name.lastIndexOf('.'); - if(index == -1) { + if (index == -1) { return ""; } return name.substring(0, index); diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Injection.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Injection.java index 8ae72a072fa63..440e6be314d68 100644 --- a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Injection.java +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Injection.java @@ -8,6 +8,7 @@ import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import org.jboss.logging.Logger; @@ -68,6 +69,22 @@ private static void forClassBean(ClassInfo beanTarget, BeanDeployment beanDeploy } } } + + for (DotName resourceAnnotation : beanDeployment.getResourceAnnotations()) { + List resourceAnnotations = beanTarget.annotations().get(resourceAnnotation); + if (resourceAnnotations != null) { + for (AnnotationInstance resourceAnnotationInstance : resourceAnnotations) { + if (Kind.FIELD == resourceAnnotationInstance.target().kind() && resourceAnnotationInstance.target().asField().annotations().stream() + .noneMatch(a -> DotNames.INJECT.equals(a.name()))) { + // Add special injection for a resource field + injections.add(new Injection(resourceAnnotationInstance.target(), + Collections.singletonList(InjectionPointInfo.fromResourceField(resourceAnnotationInstance.target().asField(), beanDeployment)))); + } + // TODO setter injection + } + } + } + if (!beanTarget.superName().equals(DotNames.OBJECT)) { ClassInfo info = beanDeployment.getIndex().getClassByName(beanTarget.superName()); if (info != null) { diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InjectionPointInfo.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InjectionPointInfo.java index a3bbdd3c37287..da9623636c8b3 100644 --- a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InjectionPointInfo.java +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InjectionPointInfo.java @@ -11,7 +11,6 @@ import java.util.stream.Collectors; import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; @@ -28,6 +27,10 @@ static InjectionPointInfo fromField(FieldInfo field, BeanDeployment beanDeployme field.annotations().stream().filter(a -> beanDeployment.getQualifier(a.name()) != null).collect(Collectors.toSet())); } + static InjectionPointInfo fromResourceField(FieldInfo field, BeanDeployment beanDeployment) { + return new InjectionPointInfo(field.type(), new HashSet<>(field.annotations()), Kind.RESOURCE); + } + static List fromMethod(MethodInfo method, BeanDeployment beanDeployment) { return fromMethod(method, beanDeployment, null); } @@ -38,7 +41,7 @@ static List fromMethod(MethodInfo method, BeanDeployment bea Type paramType = iterator.next(); Set paramAnnotations = new HashSet<>(); for (AnnotationInstance annotation : method.annotations()) { - if (Kind.METHOD_PARAMETER.equals(annotation.target().kind()) + if (org.jboss.jandex.AnnotationTarget.Kind.METHOD_PARAMETER.equals(annotation.target().kind()) && annotation.target().asMethodParameter().position() == iterator.previousIndex()) { paramAnnotations.add(annotation); } @@ -64,12 +67,19 @@ static List fromMethod(MethodInfo method, BeanDeployment bea final AtomicReference resolvedBean; + final Kind kind; + public InjectionPointInfo(Type requiredType, Set requiredQualifiers) { + this(requiredType, requiredQualifiers, Kind.CDI); + } + + public InjectionPointInfo(Type requiredType, Set requiredQualifiers, Kind kind) { this.requiredType = requiredType; this.requiredQualifiers = requiredQualifiers.isEmpty() ? Collections.singleton(AnnotationInstance.create(DotNames.DEFAULT, null, Collections.emptyList())) : requiredQualifiers; this.resolvedBean = new AtomicReference(null); + this.kind = kind; } void resolve(BeanInfo bean) { @@ -85,4 +95,8 @@ public String toString() { return "InjectionPointInfo [requiredType=" + requiredType + ", requiredQualifiers=" + requiredQualifiers + "]"; } + enum Kind { + CDI, RESOURCE + } + } \ No newline at end of file diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainerImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainerImpl.java index 96e28d0fcf23a..ad92ac9c7939a 100644 --- a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainerImpl.java +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainerImpl.java @@ -45,6 +45,8 @@ class ArcContainerImpl implements ArcContainer { private final ComputingCache>> resolved; + private final List resourceProviders; + public ArcContainerImpl() { id = UUID.randomUUID().toString(); running = new AtomicBoolean(true); @@ -60,6 +62,10 @@ public ArcContainerImpl() { contexts.put(Singleton.class, new SingletonContext()); contexts.put(RequestScoped.class, new RequestContext()); resolved = new ComputingCache<>(this::resolve); + resourceProviders = new ArrayList<>(); + for (ResourceReferenceProvider resourceProvider : ServiceLoader.load(ResourceReferenceProvider.class)) { + resourceProviders.add(resourceProvider); + } } void init() { @@ -151,6 +157,16 @@ void shutdown() { } } + InstanceHandle getResource(Type type, Set annotations) { + for (ResourceReferenceProvider resourceProvider : resourceProviders) { + InstanceHandle ret = resourceProvider.get(type, annotations); + if (ret != null) { + return ret; + } + } + return null; + } + private InstanceHandle instanceHandle(Type type, Annotation... qualifiers) { return instance(getBean(type, qualifiers)); } @@ -249,7 +265,7 @@ private boolean matches(InjectableBean bean, Type requiredType, Annotation... return Qualifiers.hasQualifiers(bean, qualifiers); } - static ArcContainerImpl unwrap(ArcContainer container) { + static ArcContainerImpl unwrap(ArcContainer container) { if (container instanceof ArcContainerImpl) { return (ArcContainerImpl) container; } else { @@ -257,6 +273,10 @@ static ArcContainerImpl unwrap(ArcContainer container) { } } + static ArcContainerImpl instance() { + return unwrap(Arc.container()); + } + private void requireRunning() { if (!running.get()) { throw new IllegalStateException("Container not running: " + toString()); diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/CreationalContextImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/CreationalContextImpl.java index c12f0142a596f..0ff584bc381bd 100644 --- a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/CreationalContextImpl.java +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/CreationalContextImpl.java @@ -27,7 +27,11 @@ public CreationalContextImpl(CreationalContextImpl parent) { } public void addDependentInstance(InjectableBean bean, I instance, CreationalContext ctx) { - dependentInstances.add(new InstanceHandleImpl(bean, instance, ctx)); + addDependentInstance(new InstanceHandleImpl(bean, instance, ctx)); + } + + public void addDependentInstance(InstanceHandle instanceHandle) { + dependentInstances.add(instanceHandle); } void destroyDependentInstance(Object dependentInstance) { diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandle.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandle.java index 77b7bdd213348..0ddec8bef6f28 100644 --- a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandle.java +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandle.java @@ -1,7 +1,7 @@ package org.jboss.protean.arc; /** - * Represents a contextual instance handle. + * Represents an instance handle. * * @author Martin Kouba * @@ -11,27 +11,32 @@ public interface InstanceHandle extends AutoCloseable { /** * - * @return {@code true} if there is exactly one bean that matches the required type and qualifiers, {@code false} otherwise + * @return an instance of {@code T} or {@code null} */ - boolean isAvailable(); + T get(); /** * - * @return an injected instance of {@code T} or {@code null} + * @return {@code true} if an instance is available, {@code false} otherwise */ - T get(); + default boolean isAvailable() { + return get() != null; + } /** - * Destroys the instance and removes the instance from the underlying context. - * + * Destroy/release the instance. If this is a CDI contextual instance it's also removed from the underlying context. */ - void destroy(); + default void destroy() { + // No-op + } /** * - * @return the injectable bean + * @return the injectable bean for a CDI contextual instance or {@code null} */ - InjectableBean getBean(); + default InjectableBean getBean() { + return null; + } /** * Delegates to {@link #destroy()}. diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandleImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandleImpl.java index 37ac0a2f25787..e1229fd427bd1 100644 --- a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandleImpl.java +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandleImpl.java @@ -40,11 +40,6 @@ public static final InstanceHandle unavailable() { this.parentCreationalContext = parentCreationalContext; } - @Override - public boolean isAvailable() { - return instance != null; - } - @Override public T get() { return instance; diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ResourceProvider.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ResourceProvider.java new file mode 100644 index 0000000000000..bc57d41025ad1 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ResourceProvider.java @@ -0,0 +1,40 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Set; + +import javax.enterprise.context.spi.CreationalContext; + +/** + * Represents a placeholder for all suppored non-CDI injection points. + * + * @author Martin Kouba + * @see ResourceReferenceProvider + */ +public class ResourceProvider implements InjectableReferenceProvider { + + private final Type type; + + private final Set annotations; + + public ResourceProvider(Type type, Set annotations) { + this.type = type; + this.annotations = annotations; + } + + @Override + public Object get(CreationalContext creationalContext) { + InstanceHandle instance = ArcContainerImpl.instance().getResource(type, annotations); + if (instance != null) { + CreationalContextImpl ctx = CreationalContextImpl.unwrap(creationalContext); + if (ctx.getParent() != null) { + ctx.getParent().addDependentInstance(instance); + } + return instance.get(); + } + // TODO log a warning that a resource cannot be injected + return null; + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ResourceReferenceProvider.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ResourceReferenceProvider.java new file mode 100644 index 0000000000000..a089209cd8bb5 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ResourceReferenceProvider.java @@ -0,0 +1,51 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Set; + +/** + * Makes it possible to resolve non-CDI injection points, such as Java EE resources. + */ +public interface ResourceReferenceProvider { + + /** + * A resource reference handle is a dependent object of the object it is injected into. {@link InstanceHandle#destroy()} is called when the target object is + * destroyed. + * + *
+     * class ResourceBean {
+     *
+     *     @Resource(lookup = "bar")
+     *     String bar;
+     *
+     *     @Produces
+     *     @PersistenceContext
+     *     EntityManager entityManager;
+     * }
+     * 
+ * + * @param type + * @param annotations + * @return the resource reference handle or {@code null} if not resolvable + */ + InstanceHandle get(Type type, Set annotations); + + /** + * Convenient util method. + * + * @param annotations + * @param annotationType + * @return + */ + @SuppressWarnings("unchecked") + default T getAnnotation(Set annotations, Class annotationType) { + for (Annotation annotation : annotations) { + if (annotation.annotationType().equals(annotationType)) { + return (T) annotation; + } + } + return null; + } + +} diff --git a/ext/arc/tests/pom.xml b/ext/arc/tests/pom.xml index eae9baed3f2d2..61d918d2040dc 100644 --- a/ext/arc/tests/pom.xml +++ b/ext/arc/tests/pom.xml @@ -29,6 +29,11 @@ junit + + javax.persistence + javax.persistence-api + + diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/ArcTestContainer.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/ArcTestContainer.java index 8f6d7bc75f001..ffafa0aed5d61 100644 --- a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/ArcTestContainer.java +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/ArcTestContainer.java @@ -4,17 +4,23 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.annotation.Annotation; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Files; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.List; +import java.util.stream.Collectors; +import org.jboss.jandex.DotName; import org.jboss.jandex.Index; import org.jboss.jandex.Indexer; import org.jboss.protean.arc.Arc; import org.jboss.protean.arc.ComponentsProvider; +import org.jboss.protean.arc.ResourceReferenceProvider; import org.jboss.protean.arc.processor.BeanProcessor; import org.jboss.protean.arc.processor.ResourceOutput; import org.junit.rules.TestRule; @@ -23,10 +29,60 @@ public class ArcTestContainer implements TestRule { + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private final List> resourceReferenceProviders; + + private final List> beanClasses; + + private final List> resourceAnnotations; + + public Builder() { + this.resourceReferenceProviders = new ArrayList<>(); + this.beanClasses = new ArrayList<>(); + this.resourceAnnotations = new ArrayList<>(); + } + + public Builder resourceReferenceProviders(Class... resourceReferenceProviders) { + Collections.addAll(this.resourceReferenceProviders, resourceReferenceProviders); + return this; + } + + public Builder beanClasses(Class... beanClasses) { + Collections.addAll(this.beanClasses, beanClasses); + return this; + } + + @SafeVarargs + public final Builder resourceAnnotations(Class... resourceAnnotations) { + Collections.addAll(this.resourceAnnotations, resourceAnnotations); + return this; + } + + public ArcTestContainer build() { + return new ArcTestContainer(resourceReferenceProviders, beanClasses, resourceAnnotations); + } + + } + + private final List> resourceReferenceProviders; + private final List> beanClasses; + private final List> resourceAnnotations; + public ArcTestContainer(Class... beanClasses) { - this.beanClasses = Arrays.asList(beanClasses); + this(Collections.emptyList(), Arrays.asList(beanClasses), Collections.emptyList()); + } + + public ArcTestContainer(List> resourceReferenceProviders, List> beanClasses, List> resourceAnnotations) { + this.resourceReferenceProviders = resourceReferenceProviders; + this.beanClasses = beanClasses; + this.resourceAnnotations = resourceAnnotations; } @Override @@ -67,7 +123,23 @@ private ClassLoader init(Class testClass) { File componentsProviderFile = new File(generatedSourcesDirectory + "/" + nameToPath(testClass.getPackage().getName()), ComponentsProvider.class.getSimpleName()); - BeanProcessor beanProcessor = BeanProcessor.builder().setName(testClass.getSimpleName()).setIndex(index).setOutput(new ResourceOutput() { + File resourceReferenceProviderFile = new File(generatedSourcesDirectory + "/" + nameToPath(testClass.getPackage().getName()), + ResourceReferenceProvider.class.getSimpleName()); + + if (!resourceReferenceProviders.isEmpty()) { + try { + resourceReferenceProviderFile.getParentFile().mkdirs(); + Files.write(resourceReferenceProviderFile.toPath(), resourceReferenceProviders.stream().map(c -> c.getName()).collect(Collectors.toList())); + } catch (IOException e) { + throw new IllegalStateException("Error generating resource reference providers", e); + } + } + + BeanProcessor.Builder beanProcessorBuilder = BeanProcessor.builder().setName(testClass.getSimpleName()).setIndex(index); + if (!resourceAnnotations.isEmpty()) { + beanProcessorBuilder.addResourceAnnotations(resourceAnnotations.stream().map(c -> DotName.createSimple(c.getName())).collect(Collectors.toList())); + } + beanProcessorBuilder.setOutput(new ResourceOutput() { @Override public void writeResource(Resource resource) throws IOException { @@ -87,7 +159,10 @@ public void writeResource(Resource resource) throws IOException { throw new IllegalArgumentException(); } } - }).build(); + }); + + BeanProcessor beanProcessor = beanProcessorBuilder.build(); + try { beanProcessor.process(); } catch (IOException e) { @@ -101,15 +176,17 @@ public Enumeration getResources(String name) throws IOException { if (("META-INF/services/" + ComponentsProvider.class.getName()).equals(name)) { // return URL that points to the correct test bean provider return Collections.enumeration(Collections.singleton(componentsProviderFile.toURI().toURL())); + } else if (("META-INF/services/" + ResourceReferenceProvider.class.getName()).equals(name) && !resourceReferenceProviders.isEmpty()) { + return Collections.enumeration(Collections.singleton(resourceReferenceProviderFile.toURI().toURL())); } return super.getResources(name); } }; Thread.currentThread().setContextClassLoader(testClassLoader); - + // Now we are ready to initialize Arc Arc.initialize(); - + return old; } diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/injection/resource/ResourceInjectionTest.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/injection/resource/ResourceInjectionTest.java new file mode 100644 index 0000000000000..ff4ef21254336 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/injection/resource/ResourceInjectionTest.java @@ -0,0 +1,370 @@ +package org.jboss.protean.arc.test.injection.resource; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.persistence.EntityGraph; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; +import javax.persistence.FlushModeType; +import javax.persistence.LockModeType; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; +import javax.persistence.StoredProcedureQuery; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaDelete; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.CriteriaUpdate; +import javax.persistence.metamodel.Metamodel; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.InstanceHandle; +import org.jboss.protean.arc.ResourceReferenceProvider; +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class ResourceInjectionTest { + + @Rule + public ArcTestContainer container = ArcTestContainer.builder().beanClasses(EEResourceField.class, JpaClient.class) + .resourceReferenceProviders(EntityManagerProvider.class, DummyProvider.class).resourceAnnotations(PersistenceContext.class, Dummy.class).build(); + + @Test + public void testInjection() { + DummyProvider.DUMMY_DESTROYED.set(false); + + InstanceHandle handle = Arc.container().instance(JpaClient.class); + JpaClient client = handle.get(); + assertNotNull(client.entityManager); + assertFalse(client.entityManager.isOpen()); + assertEquals("05", client.dummyString); + + assertFalse(DummyProvider.DUMMY_DESTROYED.get()); + handle.destroy(); + assertTrue(DummyProvider.DUMMY_DESTROYED.get()); + } + + @Dependent + static class JpaClient { + + @Dummy + String dummyString; + + @Inject + EntityManager entityManager; + + } + + @Singleton + static class EEResourceField { + + @Produces + @PersistenceContext + EntityManager entityManager; + + } + + @Target({ ElementType.FIELD }) + @Retention(RetentionPolicy.RUNTIME) + public @interface Dummy { + } + + public static class DummyProvider implements ResourceReferenceProvider { + + static final AtomicBoolean DUMMY_DESTROYED = new AtomicBoolean(); + + @Override + public InstanceHandle get(Type type, Set annotations) { + if (String.class.equals(type) && getAnnotation(annotations, Dummy.class) != null) { + return new InstanceHandle() { + @Override + public String get() { + return "05"; + } + + @Override + public void destroy() { + DUMMY_DESTROYED.set(true); + } + }; + } + return null; + } + + } + + @SuppressWarnings("rawtypes") + public static class EntityManagerProvider implements ResourceReferenceProvider { + + @Override + public InstanceHandle get(Type type, Set annotations) { + if (EntityManager.class.equals(type)) { + EntityManager entityManager = new EntityManager() { + + @Override + public T unwrap(Class cls) { + return null; + } + + @Override + public void setProperty(String propertyName, Object value) { + } + + @Override + public void setFlushMode(FlushModeType flushMode) { + } + + @Override + public void remove(Object entity) { + } + + @Override + public void refresh(Object entity, LockModeType lockMode, Map properties) { + } + + @Override + public void refresh(Object entity, LockModeType lockMode) { + } + + @Override + public void refresh(Object entity, Map properties) { + } + + @Override + public void refresh(Object entity) { + } + + @Override + public void persist(Object entity) { + } + + @Override + public T merge(T entity) { + return null; + } + + @Override + public void lock(Object entity, LockModeType lockMode, Map properties) { + } + + @Override + public void lock(Object entity, LockModeType lockMode) { + } + + @Override + public void joinTransaction() { + } + + @Override + public boolean isOpen() { + return false; + } + + @Override + public boolean isJoinedToTransaction() { + return false; + } + + @Override + public EntityTransaction getTransaction() { + return null; + } + + @Override + public T getReference(Class entityClass, Object primaryKey) { + return null; + } + + @Override + public Map getProperties() { + return null; + } + + @Override + public Metamodel getMetamodel() { + return null; + } + + @Override + public LockModeType getLockMode(Object entity) { + return null; + } + + @Override + public FlushModeType getFlushMode() { + return null; + } + + @Override + public EntityManagerFactory getEntityManagerFactory() { + return null; + } + + @Override + public List> getEntityGraphs(Class entityClass) { + return null; + } + + @Override + public EntityGraph getEntityGraph(String graphName) { + return null; + } + + @Override + public Object getDelegate() { + return null; + } + + @Override + public CriteriaBuilder getCriteriaBuilder() { + return null; + } + + @Override + public void flush() { + } + + @Override + public T find(Class entityClass, Object primaryKey, LockModeType lockMode, Map properties) { + return null; + } + + @Override + public T find(Class entityClass, Object primaryKey, LockModeType lockMode) { + return null; + } + + @Override + public T find(Class entityClass, Object primaryKey, Map properties) { + return null; + } + + @Override + public T find(Class entityClass, Object primaryKey) { + return null; + } + + @Override + public void detach(Object entity) { + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName, String... resultSetMappings) { + return null; + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses) { + return null; + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName) { + return null; + } + + @Override + public TypedQuery createQuery(String qlString, Class resultClass) { + return null; + } + + @Override + public Query createQuery(CriteriaDelete deleteQuery) { + return null; + } + + @Override + public Query createQuery(CriteriaUpdate updateQuery) { + return null; + } + + @Override + public TypedQuery createQuery(CriteriaQuery criteriaQuery) { + return null; + } + + @Override + public Query createQuery(String qlString) { + return null; + } + + @Override + public Query createNativeQuery(String sqlString, String resultSetMapping) { + return null; + } + + @Override + public Query createNativeQuery(String sqlString, Class resultClass) { + return null; + } + + @Override + public Query createNativeQuery(String sqlString) { + return null; + } + + @Override + public StoredProcedureQuery createNamedStoredProcedureQuery(String name) { + return null; + } + + @Override + public TypedQuery createNamedQuery(String name, Class resultClass) { + return null; + } + + @Override + public Query createNamedQuery(String name) { + return null; + } + + @Override + public EntityGraph createEntityGraph(String graphName) { + return null; + } + + @Override + public EntityGraph createEntityGraph(Class rootType) { + return null; + } + + @Override + public boolean contains(Object entity) { + return false; + } + + @Override + public void close() { + } + + @Override + public void clear() { + } + }; + return () -> entityManager; + } + return null; + } + + } +} From 37677d1af5649592b8c97d0abb0028ccfbb292cc Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 8 Oct 2018 16:13:58 +0200 Subject: [PATCH 2/3] JPA - make use of Arc resource provider SPI --- .../deployment/ArcAnnotationProcessor.java | 1 + .../shamrock/deployment/BeanDeployment.java | 26 +- .../shamrock/deployment/Capabilities.java | 1 + .../shamrock/deployment/ProcessorContext.java | 5 +- .../jpa/JPAFunctionalityTestEndpoint.java | 19 +- .../shamrock/example/jpa/JpaProducer.java | 2 + .../cdi/HibernateCdiResourceProcessor.java | 235 +++------------ jpa/runtime/pom.xml | 6 + .../DefaultEntityManagerFactoryProducer.java | 15 + .../runtime/DefaultEntityManagerProducer.java | 15 + .../jpa/runtime/ForwardingEntityManager.java | 282 ++++++++++++++++++ .../jboss/shamrock/jpa/runtime/JPAConfig.java | 64 ++++ .../jpa/runtime/JPADeploymentTemplate.java | 34 +-- .../runtime/JPAResourceReferenceProvider.java | 62 ++++ .../runtime/TransactionEntityManagers.java | 43 +++ .../TransactionScopedEntityManager.java | 2 +- .../jpa/runtime/cdi/SystemEntityManager.java | 11 - .../transactions/TransactionsSetup.java | 3 +- 18 files changed, 563 insertions(+), 263 deletions(-) create mode 100644 jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/DefaultEntityManagerFactoryProducer.java create mode 100644 jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/DefaultEntityManagerProducer.java create mode 100644 jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/ForwardingEntityManager.java create mode 100644 jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPAConfig.java create mode 100644 jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPAResourceReferenceProvider.java create mode 100644 jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/TransactionEntityManagers.java rename jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/{cdi => }/TransactionScopedEntityManager.java (99%) delete mode 100644 jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/cdi/SystemEntityManager.java diff --git a/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java b/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java index 899364a38b0b4..4a2d433c9a5ff 100644 --- a/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java +++ b/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java @@ -105,6 +105,7 @@ public void process(ArchiveContext archiveContext, ProcessorContext processorCon builder.setIndex(index); builder.setAdditionalBeanDefiningAnnotations(additionalBeanDefiningAnnotations); builder.setSharedAnnotationLiterals(false); + builder.addResourceAnnotations(beanDeployment.getResourceAnnotations().stream().map(ran -> DotName.createSimple(ran)).collect(Collectors.toList())); builder.setReflectionRegistration(new ReflectionRegistration() { @Override public void registerMethod(MethodInfo methodInfo) { diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/BeanDeployment.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/BeanDeployment.java index 4bdd1ff31147f..601bb3c166144 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/BeanDeployment.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/BeanDeployment.java @@ -15,15 +15,17 @@ public class BeanDeployment { private final List additionalBeans = new ArrayList<>(); - + private final Map generatedBeans = new HashMap<>(); - + // Lite profile private final List, Collection>> annotationTransformers = new ArrayList<>(); + private final List resourceAnnotations = new ArrayList<>(); + // Full profile private final List extensions = new ArrayList<>(); - + public void addAdditionalBean(Class... beanClass) { additionalBeans.addAll(Arrays.stream(beanClass).map(Class::getName).collect(Collectors.toList())); } @@ -31,7 +33,7 @@ public void addAdditionalBean(Class... beanClass) { public void addAdditionalBean(String... beanClass) { additionalBeans.addAll(Arrays.stream(beanClass).collect(Collectors.toList())); } - + public void addGeneratedBean(String name, byte[] bean) { generatedBeans.put(name, bean); } @@ -39,11 +41,15 @@ public void addGeneratedBean(String name, byte[] bean) { public void addAnnotationTransformer(BiFunction, Collection> transformer) { annotationTransformers.add(transformer); } - + public void addExtension(String extensionClass) { extensions.add(extensionClass); } - + + public void addResourceAnnotation(String resourceAnnotation) { + resourceAnnotations.add(resourceAnnotation); + } + public List getAdditionalBeans() { return additionalBeans; } @@ -51,7 +57,7 @@ public List getAdditionalBeans() { public Map getGeneratedBeans() { return generatedBeans; } - + public List, Collection>> getAnnotationTransformers() { return annotationTransformers; } @@ -59,5 +65,9 @@ public List, Collect public List getExtensions() { return extensions; } - + + public List getResourceAnnotations() { + return resourceAnnotations; + } + } diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/Capabilities.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/Capabilities.java index dcaea01d8e0d0..713cc9603322f 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/Capabilities.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/Capabilities.java @@ -10,6 +10,7 @@ public final class Capabilities { public static final String CDI_WELD = "org.jboss.shamrock.cdi.weld"; public static final String CDI_ARC = "org.jboss.shamrock.cdi.arc"; + public static final String TRANSACTIONS = "org.jboss.shamrock.transactions"; private Capabilities() { } diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java index e9c86328a33e4..2732345cdfb64 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java @@ -3,12 +3,10 @@ import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.function.Consumer; import java.util.function.Function; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.MethodInfo; -import org.jboss.protean.gizmo.MethodCreator; import org.jboss.shamrock.deployment.codegen.BytecodeRecorder; import org.objectweb.asm.ClassVisitor; @@ -120,7 +118,8 @@ public interface ProcessorContext { /** * * @param capability - * @return + * @return if the given capability is present + * @see Capabilities */ boolean isCapabilityPresent(String capability); diff --git a/examples/jpa-strict/src/main/java/org/jboss/shamrock/example/jpa/JPAFunctionalityTestEndpoint.java b/examples/jpa-strict/src/main/java/org/jboss/shamrock/example/jpa/JPAFunctionalityTestEndpoint.java index 2bb3a45726233..9247ed391ea55 100644 --- a/examples/jpa-strict/src/main/java/org/jboss/shamrock/example/jpa/JPAFunctionalityTestEndpoint.java +++ b/examples/jpa-strict/src/main/java/org/jboss/shamrock/example/jpa/JPAFunctionalityTestEndpoint.java @@ -8,7 +8,7 @@ import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; -import javax.persistence.Persistence; +import javax.persistence.PersistenceUnit; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -24,28 +24,19 @@ @WebServlet(name = "JPATestBootstrapEndpoint", urlPatterns = "/jpa/testfunctionality") public class JPAFunctionalityTestEndpoint extends HttpServlet { + @PersistenceUnit(unitName = "templatePU") + EntityManagerFactory entityManagerFactory; + @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { try { - bootAndDoStuff(); + doStuffWithHibernate(entityManagerFactory); } catch (Exception e) { reportException("Oops, shit happened, No boot for you!", e, resp); } resp.getWriter().write("OK"); } - public void bootAndDoStuff() throws Exception { - EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("templatePU"); - try { - System.out.println("Hibernate EntityManagerFactory: booted"); - doStuffWithHibernate(entityManagerFactory); - } - finally { - entityManagerFactory.close(); - System.out.println("Hibernate EntityManagerFactory: shut down"); - } - } - /** * Lists the various operations we want to test for: */ diff --git a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/JpaProducer.java b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/JpaProducer.java index 29487c6ed2a5c..67a08442f3444 100644 --- a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/JpaProducer.java +++ b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/JpaProducer.java @@ -1,11 +1,13 @@ package org.jboss.shamrock.example.jpa; +import javax.enterprise.context.Dependent; import javax.enterprise.inject.Produces; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceUnit; +@Dependent public class JpaProducer { @Produces diff --git a/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/cdi/HibernateCdiResourceProcessor.java b/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/cdi/HibernateCdiResourceProcessor.java index 031a87521ad2f..8e47c70c1a009 100644 --- a/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/cdi/HibernateCdiResourceProcessor.java +++ b/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/cdi/HibernateCdiResourceProcessor.java @@ -1,49 +1,30 @@ package org.jboss.shamrock.jpa.cdi; -import java.io.IOException; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.context.Dependent; -import javax.enterprise.context.RequestScoped; -import javax.enterprise.inject.Disposes; import javax.enterprise.inject.Produces; import javax.inject.Inject; -import javax.persistence.EntityManager; -import javax.persistence.EntityManagerFactory; -import javax.persistence.Persistence; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceUnit; -import javax.transaction.TransactionManager; -import javax.transaction.TransactionSynchronizationRegistry; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; -import org.hibernate.protean.impl.PersistenceUnitsHolder; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; -import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.DotName; -import org.jboss.protean.gizmo.ClassCreator; -import org.jboss.protean.gizmo.ClassOutput; -import org.jboss.protean.gizmo.FieldCreator; -import org.jboss.protean.gizmo.FieldDescriptor; -import org.jboss.protean.gizmo.MethodCreator; -import org.jboss.protean.gizmo.MethodDescriptor; -import org.jboss.protean.gizmo.ResultHandle; +import org.jboss.jandex.IndexView; import org.jboss.shamrock.deployment.ArchiveContext; -import org.jboss.shamrock.deployment.BeanArchiveIndex; import org.jboss.shamrock.deployment.BeanDeployment; +import org.jboss.shamrock.deployment.Capabilities; 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.HibernateResourceProcessor; +import org.jboss.shamrock.jpa.runtime.DefaultEntityManagerFactoryProducer; +import org.jboss.shamrock.jpa.runtime.DefaultEntityManagerProducer; +import org.jboss.shamrock.jpa.runtime.JPAConfig; import org.jboss.shamrock.jpa.runtime.JPADeploymentTemplate; -import org.jboss.shamrock.jpa.runtime.cdi.SystemEntityManager; -import org.jboss.shamrock.jpa.runtime.cdi.TransactionScopedEntityManager; +import org.jboss.shamrock.jpa.runtime.TransactionEntityManagers; public class HibernateCdiResourceProcessor implements ResourceProcessor { @@ -54,177 +35,58 @@ public class HibernateCdiResourceProcessor implements ResourceProcessor { @Inject BeanDeployment beanDeployment; - @Inject - BeanArchiveIndex beanArchiveIndex; - @Override public void process(ArchiveContext archiveContext, ProcessorContext processorContext) throws Exception { - Set knownUnitNames = new HashSet<>(); - Set knownContextNames = new HashSet<>(); - scanForAnnotations(archiveContext, knownUnitNames, PERSISTENCE_UNIT); - scanForAnnotations(archiveContext, knownContextNames, PERSISTENCE_CONTEXT); - //now create producer beans for all of the above unit names - //this is not great, we really need a better way to do this than generating bytecode - - String defaultName = null; - List pus = processorContext.getProperty(HibernateResourceProcessor.PARSED_DESCRIPTORS); - //look through the parsed descriptors to see if we can figure out the default PU name - if(pus.size() ==1) { - defaultName = pus.get(0).getName(); - - if(knownUnitNames.contains("")) { - knownUnitNames.remove(""); - knownUnitNames.add(defaultName); - } - if(knownContextNames.contains("")) { - knownContextNames.remove(""); - knownContextNames.add(defaultName); - } - } - - Set allKnownNames = new HashSet<>(knownUnitNames); - allKnownNames.addAll(knownContextNames); - if(allKnownNames.contains("") && defaultName == null) { - throw new RuntimeException("No default persistence unit could be determined, you must specify the name at the injection point"); - } - - try(BytecodeRecorder recorder = processorContext.addDeploymentTask(RuntimePriority.BOOTSTRAP_EMF)) { + try (BytecodeRecorder recorder = processorContext.addDeploymentTask(RuntimePriority.BOOTSTRAP_EMF)) { JPADeploymentTemplate template = recorder.getRecordingProxy(JPADeploymentTemplate.class); - //every persistence unit needs a producer, even if the factory is not injectable - for (String name : allKnownNames) { - String className = getClass().getName() + "$$EMFProducer$$APP$$-" + name; - AtomicReference bytes = new AtomicReference<>(); - - boolean system = false; - try (ClassCreator creator = new ClassCreator(new InMemoryClassOutput(bytes, processorContext), className, null, Object.class.getName())) { - - creator.addAnnotation(Dependent.class); - MethodCreator producer = creator.getMethodCreator("producerMethod", EntityManagerFactory.class); - producer.addAnnotation(Produces.class); - producer.addAnnotation(ApplicationScoped.class); - if (!knownUnitNames.contains(name)) { - //there was no @PersistenceUnit producer with this name - //this means that we still need it, but the user would not be expecting a bean to be registered - //we register an artificial qualifier that we will use for the managed persistence contexts - producer.addAnnotation(SystemEntityManager.class); - system = true; - } - ResultHandle ret = producer.invokeStaticMethod(MethodDescriptor.ofMethod(Persistence.class, "createEntityManagerFactory", EntityManagerFactory.class, String.class), producer.load(name)); - producer.returnValue(ret); - } - beanDeployment.addGeneratedBean(className, bytes.get()); - template.boostrapPu(null, system, null); //force PU bootstrap at startup - } - - - - for (String name : knownContextNames) { - String className = getClass().getName() + "$$EMProducer-" + name; - AtomicReference bytes = new AtomicReference<>(); - - //we need to know if transactions are present or not - //TODO: this should be based on if a PU is JTA enabled or not - if (processorContext.isCapabilityPresent("transactions")) { - try (ClassCreator creator = new ClassCreator(new InMemoryClassOutput(bytes, processorContext), className, null, Object.class.getName())) { - - creator.addAnnotation(Dependent.class); - - FieldCreator emfField = creator.getFieldCreator("emf", EntityManagerFactory.class); - emfField.addAnnotation(Inject.class); - if (!knownUnitNames.contains(name)) { - emfField.addAnnotation(SystemEntityManager.class); - } - FieldDescriptor emf = emfField.getFieldDescriptor(); - - - FieldCreator tsrField = creator.getFieldCreator("tsr", TransactionSynchronizationRegistry.class); - tsrField.addAnnotation(Inject.class); - FieldDescriptor tsr = tsrField.getFieldDescriptor(); - - - FieldCreator tmField = creator.getFieldCreator("tm", TransactionManager.class); - tmField.addAnnotation(Inject.class); - FieldDescriptor tm = tmField.getFieldDescriptor(); - - MethodCreator producer = creator.getMethodCreator("producerMethod", EntityManager.class); - producer.addAnnotation(Produces.class); - producer.addAnnotation(RequestScoped.class); - - ResultHandle emfRh = producer.readInstanceField(emf, producer.getThis()); - ResultHandle tsrRh = producer.readInstanceField(tsr, producer.getThis()); - ResultHandle tmRh = producer.readInstanceField(tm, producer.getThis()); - - producer.returnValue(producer.newInstance(MethodDescriptor.ofConstructor(TransactionScopedEntityManager.class, TransactionManager.class, TransactionSynchronizationRegistry.class, EntityManagerFactory.class), tmRh, tsrRh, emfRh)); + beanDeployment.addAdditionalBean(JPAConfig.class, TransactionEntityManagers.class); + beanDeployment.addResourceAnnotation(PERSISTENCE_CONTEXT.toString()); + beanDeployment.addResourceAnnotation(PERSISTENCE_UNIT.toString()); + template.initializeJpa(null, processorContext.isCapabilityPresent(Capabilities.TRANSACTIONS)); - MethodCreator disposer = creator.getMethodCreator("disposerMethod", void.class, EntityManager.class); - disposer.getParameterAnnotations(0).addAnnotation(Disposes.class); - disposer.invokeVirtualMethod(MethodDescriptor.ofMethod(TransactionScopedEntityManager.class, "requestDone", void.class), disposer.getMethodParam(0)); - disposer.returnValue(null); - - } - beanDeployment.addGeneratedBean(className, bytes.get()); - } else { - //if there is no TX support then we just use a super simple approach, and produce a normal EM - try (ClassCreator creator = new ClassCreator(new InMemoryClassOutput(bytes, processorContext), className, null, Object.class.getName())) { - - creator.addAnnotation(Dependent.class); - - FieldCreator emfField = creator.getFieldCreator("emf", EntityManagerFactory.class); - emfField.addAnnotation(Inject.class); - if (!knownUnitNames.contains(name)) { - emfField.addAnnotation(SystemEntityManager.class); - } - FieldDescriptor emf = emfField.getFieldDescriptor(); - - - MethodCreator producer = creator.getMethodCreator("producerMethod", EntityManager.class); - producer.addAnnotation(Produces.class); - producer.addAnnotation(Dependent.class); - - ResultHandle factory = producer.readInstanceField(emf, producer.getThis()); - producer.returnValue(producer.invokeInterfaceMethod(MethodDescriptor.ofMethod(EntityManagerFactory.class, "createEntityManager", EntityManager.class), factory)); - - - MethodCreator disposer = creator.getMethodCreator("disposerMethod", void.class, EntityManager.class); - disposer.getParameterAnnotations(0).addAnnotation(Disposes.class); - disposer.invokeInterfaceMethod(MethodDescriptor.ofMethod(EntityManager.class, "close", void.class), disposer.getMethodParam(0)); - disposer.returnValue(null); + // Bootstrap all persistence units + List pus = processorContext + .getProperty(HibernateResourceProcessor.PARSED_DESCRIPTORS); + for (PersistenceUnitDescriptor persistenceUnitDescriptor : pus) { + template.bootstrapPersistenceUnit(null, persistenceUnitDescriptor.getName()); + } - } - beanDeployment.addGeneratedBean(className, bytes.get()); + if (pus.size() == 1) { + // There is only one persistence unit - register CDI beans for EM and EMF if no + // producers are defined + if (isUserDefinedProducerMissing(archiveContext.getCombinedIndex(), PERSISTENCE_UNIT)) { + beanDeployment.addAdditionalBean(DefaultEntityManagerFactoryProducer.class); + } + if (isUserDefinedProducerMissing(archiveContext.getCombinedIndex(), PERSISTENCE_CONTEXT)) { + beanDeployment.addAdditionalBean(DefaultEntityManagerProducer.class); } } - } + if (processorContext.isCapabilityPresent(Capabilities.CDI_ARC)) { + processorContext.createResource("META-INF/services/org.jboss.protean.arc.ResourceReferenceProvider", + "org.jboss.shamrock.jpa.runtime.JPAResourceReferenceProvider".getBytes()); + } + } } - private void scanForAnnotations(ArchiveContext archiveContext, Set knownUnitNames, DotName nm) { - for (AnnotationInstance anno : archiveContext.getCombinedIndex().getAnnotations(nm)) { - AnnotationValue unitNameValue = anno.value("unitName"); - String unitName = unitNameValue == null ? "" : unitNameValue.asString(); - if (anno.target().kind() == AnnotationTarget.Kind.METHOD) { - if (anno.target().asMethod().hasAnnotation(PRODUCES)) { - knownUnitNames.add(unitName); - } - } else if (anno.target().kind() == AnnotationTarget.Kind.FIELD) { - for (AnnotationInstance i : anno.target().asField().annotations()) { - if (i.name().equals(PRODUCES)) { - knownUnitNames.add(unitName); - break; - } + private boolean isUserDefinedProducerMissing(IndexView index, DotName annotationName) { + for (AnnotationInstance annotationInstance : index.getAnnotations(annotationName)) { + if (annotationInstance.target().kind() == AnnotationTarget.Kind.METHOD) { + if (annotationInstance.target().asMethod().hasAnnotation(PRODUCES)) { + return false; } - } else if (anno.target().kind() == AnnotationTarget.Kind.CLASS) { - for (AnnotationInstance i : anno.target().asClass().classAnnotations()) { + } else if (annotationInstance.target().kind() == AnnotationTarget.Kind.FIELD) { + for (AnnotationInstance i : annotationInstance.target().asField().annotations()) { if (i.name().equals(PRODUCES)) { - knownUnitNames.add(unitName); - break; + return false; } } } } + return true; } @Override @@ -232,23 +94,4 @@ public int getPriority() { return 100; } - private static class InMemoryClassOutput implements ClassOutput { - private final AtomicReference bytes; - private final ProcessorContext processorContext; - - public InMemoryClassOutput(AtomicReference bytes, ProcessorContext processorContext) { - this.bytes = bytes; - this.processorContext = processorContext; - } - - @Override - public void write(String name, byte[] data) { - try { - bytes.set(data); - processorContext.addGeneratedClass(true, name, data); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } } diff --git a/jpa/runtime/pom.xml b/jpa/runtime/pom.xml index 48fd2e4c25682..88e60b109126d 100644 --- a/jpa/runtime/pom.xml +++ b/jpa/runtime/pom.xml @@ -20,6 +20,12 @@ org.jboss.shamrock shamrock-core-runtime + + org.jboss.shamrock + shamrock-arc-runtime + compile + true + diff --git a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/DefaultEntityManagerFactoryProducer.java b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/DefaultEntityManagerFactoryProducer.java new file mode 100644 index 0000000000000..ba7e53f35f4c3 --- /dev/null +++ b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/DefaultEntityManagerFactoryProducer.java @@ -0,0 +1,15 @@ +package org.jboss.shamrock.jpa.runtime; + +import javax.enterprise.inject.Produces; +import javax.inject.Singleton; +import javax.persistence.EntityManagerFactory; +import javax.persistence.PersistenceUnit; + +@Singleton +public class DefaultEntityManagerFactoryProducer { + + @Produces + @PersistenceUnit + EntityManagerFactory entityManagerFactory; + +} diff --git a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/DefaultEntityManagerProducer.java b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/DefaultEntityManagerProducer.java new file mode 100644 index 0000000000000..fce9619659c8a --- /dev/null +++ b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/DefaultEntityManagerProducer.java @@ -0,0 +1,15 @@ +package org.jboss.shamrock.jpa.runtime; + +import javax.enterprise.inject.Produces; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +@Singleton +public class DefaultEntityManagerProducer { + + @Produces + @PersistenceContext + EntityManager entityManager; + +} diff --git a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/ForwardingEntityManager.java b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/ForwardingEntityManager.java new file mode 100644 index 0000000000000..a2aa93d14d401 --- /dev/null +++ b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/ForwardingEntityManager.java @@ -0,0 +1,282 @@ +package org.jboss.shamrock.jpa.runtime; + +import java.util.List; +import java.util.Map; + +import javax.persistence.EntityGraph; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; +import javax.persistence.FlushModeType; +import javax.persistence.LockModeType; +import javax.persistence.Query; +import javax.persistence.StoredProcedureQuery; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaDelete; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.CriteriaUpdate; +import javax.persistence.metamodel.Metamodel; + +public abstract class ForwardingEntityManager implements EntityManager { + + protected abstract EntityManager delegate(); + + @Override + public void persist(Object entity) { + delegate().persist(entity); + } + + @Override + public T merge(T entity) { + return delegate().merge(entity); + } + + @Override + public void remove(Object entity) { + delegate().remove(entity); + } + + @Override + public T find(Class entityClass, Object primaryKey) { + return delegate().find(entityClass, primaryKey); + } + + @Override + public T find(Class entityClass, Object primaryKey, Map properties) { + return delegate().find(entityClass, primaryKey, properties); + } + + @Override + public T find(Class entityClass, Object primaryKey, LockModeType lockMode) { + return delegate().find(entityClass, primaryKey, lockMode); + } + + @Override + public T find(Class entityClass, Object primaryKey, LockModeType lockMode, Map properties) { + return delegate().find(entityClass, primaryKey, lockMode, properties); + } + + @Override + public T getReference(Class entityClass, Object primaryKey) { + return delegate().getReference(entityClass, primaryKey); + } + + @Override + public void flush() { + delegate().flush(); + } + + @Override + public void setFlushMode(FlushModeType flushMode) { + delegate().setFlushMode(flushMode); + } + + @Override + public FlushModeType getFlushMode() { + return delegate().getFlushMode(); + } + + @Override + public void lock(Object entity, LockModeType lockMode) { + delegate().lock(entity, lockMode); + } + + @Override + public void lock(Object entity, LockModeType lockMode, Map properties) { + delegate().lock(entity, lockMode, properties); + } + + @Override + public void refresh(Object entity) { + delegate().refresh(entity); + } + + @Override + public void refresh(Object entity, Map properties) { + delegate().refresh(entity, properties); + } + + @Override + public void refresh(Object entity, LockModeType lockMode) { + delegate().refresh(entity, lockMode); + } + + @Override + public void refresh(Object entity, LockModeType lockMode, Map properties) { + delegate().refresh(entity, lockMode, properties); + } + + @Override + public void clear() { + delegate().clear(); + } + + @Override + public void detach(Object entity) { + delegate().detach(entity); + } + + @Override + public boolean contains(Object entity) { + return delegate().contains(entity); + } + + @Override + public LockModeType getLockMode(Object entity) { + return delegate().getLockMode(entity); + } + + @Override + public void setProperty(String propertyName, Object value) { + delegate().setProperty(propertyName, value); + } + + @Override + public Map getProperties() { + return delegate().getProperties(); + } + + @Override + public Query createQuery(String qlString) { + return delegate().createQuery(qlString); + } + + @Override + public TypedQuery createQuery(CriteriaQuery criteriaQuery) { + return delegate().createQuery(criteriaQuery); + } + + @Override + public Query createQuery(CriteriaUpdate updateQuery) { + return delegate().createQuery(updateQuery); + } + + @Override + public Query createQuery(CriteriaDelete deleteQuery) { + return delegate().createQuery(deleteQuery); + } + + @Override + public TypedQuery createQuery(String qlString, Class resultClass) { + return delegate().createQuery(qlString, resultClass); + } + + @Override + public Query createNamedQuery(String name) { + return delegate().createNamedQuery(name); + } + + @Override + public TypedQuery createNamedQuery(String name, Class resultClass) { + return delegate().createNamedQuery(name, resultClass); + } + + @Override + public Query createNativeQuery(String sqlString) { + return delegate().createNativeQuery(sqlString); + } + + @Override + public Query createNativeQuery(String sqlString, Class resultClass) { + return delegate().createNativeQuery(sqlString, resultClass); + } + + @Override + public Query createNativeQuery(String sqlString, String resultSetMapping) { + return delegate().createNativeQuery(sqlString, resultSetMapping); + } + + @Override + public StoredProcedureQuery createNamedStoredProcedureQuery(String name) { + return delegate().createNamedStoredProcedureQuery(name); + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName) { + return delegate().createStoredProcedureQuery(procedureName); + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses) { + return delegate().createStoredProcedureQuery(procedureName, resultClasses); + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName, String... resultSetMappings) { + return delegate().createStoredProcedureQuery(procedureName, resultSetMappings); + } + + @Override + public void joinTransaction() { + delegate().joinTransaction(); + } + + @Override + public boolean isJoinedToTransaction() { + return delegate().isJoinedToTransaction(); + } + + @Override + public T unwrap(Class cls) { + return delegate().unwrap(cls); + } + + @Override + public Object getDelegate() { + return delegate().getDelegate(); + } + + @Override + public void close() { + delegate().close(); + } + + @Override + public boolean isOpen() { + return delegate().isOpen(); + } + + @Override + public EntityTransaction getTransaction() { + return delegate().getTransaction(); + } + + @Override + public EntityManagerFactory getEntityManagerFactory() { + return delegate().getEntityManagerFactory(); + } + + @Override + public CriteriaBuilder getCriteriaBuilder() { + return delegate().getCriteriaBuilder(); + } + + @Override + public Metamodel getMetamodel() { + return delegate().getMetamodel(); + } + + @Override + public EntityGraph createEntityGraph(Class rootType) { + return delegate().createEntityGraph(rootType); + } + + @Override + public EntityGraph createEntityGraph(String graphName) { + return delegate().createEntityGraph(graphName); + } + + @Override + public EntityGraph getEntityGraph(String graphName) { + return delegate().getEntityGraph(graphName); + } + + @Override + public List> getEntityGraphs(Class entityClass) { + return delegate().getEntityGraphs(entityClass); + } + + + +} diff --git a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPAConfig.java b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPAConfig.java new file mode 100644 index 0000000000000..7fc7ac4ed5b94 --- /dev/null +++ b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPAConfig.java @@ -0,0 +1,64 @@ +package org.jboss.shamrock.jpa.runtime; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.annotation.PreDestroy; +import javax.inject.Singleton; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +import org.jboss.logging.Logger; + + +@Singleton +public class JPAConfig { + + private static final Logger LOGGER = Logger.getLogger(JPAConfig.class.getName()); + + private final AtomicBoolean jtaEnabled; + + private final Map persistenceUnits; + + public JPAConfig() { + this.jtaEnabled = new AtomicBoolean(); + this.persistenceUnits = new HashMap<>(); + } + + void setJtaEnabled(boolean value) { + jtaEnabled.set(value); + } + + public EntityManagerFactory getEntityManagerFactory(String unitName) { + if (unitName == null || unitName.isEmpty()) { + if (persistenceUnits.size() == 1) { + return persistenceUnits.values().iterator().next(); + } else { + throw new IllegalStateException("Unable to identify the default PU: " + persistenceUnits); + } + } + return persistenceUnits.get(unitName); + } + + void bootstrapPersistenceUnit(String unitName) { + persistenceUnits.put(unitName, Persistence.createEntityManagerFactory(unitName)); + } + + boolean isJtaEnabled() { + return jtaEnabled.get(); + } + + @PreDestroy + void destroy() { + for (EntityManagerFactory factory : persistenceUnits.values()) { + try { + factory.close(); + } catch (Exception e) { + LOGGER.warn("Unable to close the EntityManagerFactory: " + factory, e); + } + } + persistenceUnits.clear(); + } + +} 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 f3d1150c902a0..97dc59f1eef1a 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,14 +1,8 @@ package org.jboss.shamrock.jpa.runtime; -import java.io.Closeable; -import java.io.IOException; -import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.List; -import javax.enterprise.util.AnnotationLiteral; -import javax.persistence.EntityManager; -import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import org.hibernate.boot.archive.scan.spi.Scanner; @@ -16,10 +10,8 @@ import org.hibernate.protean.Hibernate; import org.hibernate.protean.impl.PersistenceUnitsHolder; import org.jboss.logging.Logger; -import org.jboss.shamrock.jpa.runtime.cdi.SystemEntityManager; import org.jboss.shamrock.runtime.BeanContainer; import org.jboss.shamrock.runtime.ContextObject; -import org.jboss.shamrock.runtime.StartupContext; /** * @author Emmanuel Bernard emmanuel@hibernate.org @@ -41,28 +33,12 @@ public void callHibernateFeatureInit() { Hibernate.featureInit(); } - public void boostrapPu(@ContextObject("bean.container") BeanContainer beanContainer, boolean synthetic, StartupContext startupContext) { - //TODO: we need to take qualifiers into account, at the moment we can only have one EM, but this is probably fine for the PoC - final EntityManagerFactory emf; - if (synthetic) { - emf = beanContainer.instance(EntityManagerFactory.class, new AnnotationLiteral() { + public void initializeJpa(@ContextObject("bean.container") BeanContainer beanContainer, boolean jtaEnabled) { + beanContainer.instance(JPAConfig.class).setJtaEnabled(jtaEnabled); + } - @Override - public Class annotationType() { - return SystemEntityManager.class; - } - }); - emf.getProperties(); - } else { - emf = beanContainer.instance(EntityManagerFactory.class); - emf.getProperties(); - } - startupContext.addCloseable(new Closeable() { - @Override - public void close() throws IOException { - emf.close(); - } - }); + public void bootstrapPersistenceUnit(@ContextObject("bean.container") BeanContainer beanContainer, String unitName) { + beanContainer.instance(JPAConfig.class).bootstrapPersistenceUnit(unitName); } public void initMetadata(List parsedPersistenceXmlDescriptors, Scanner scanner, @ContextObject("bean.container") BeanContainer beanContainer) { diff --git a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPAResourceReferenceProvider.java b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPAResourceReferenceProvider.java new file mode 100644 index 0000000000000..942645e1d5630 --- /dev/null +++ b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPAResourceReferenceProvider.java @@ -0,0 +1,62 @@ +package org.jboss.shamrock.jpa.runtime; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Set; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceUnit; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.InstanceHandle; +import org.jboss.protean.arc.ResourceReferenceProvider; + +public class JPAResourceReferenceProvider implements ResourceReferenceProvider { + + @Override + public InstanceHandle get(Type type, Set annotations) { + JPAConfig jpaConfig = Arc.container().instance(JPAConfig.class).get(); + if (EntityManagerFactory.class.equals(type)) { + PersistenceUnit pu = getAnnotation(annotations, PersistenceUnit.class); + if (pu != null) { + return () -> jpaConfig.getEntityManagerFactory(pu.unitName()); + } + } + if (EntityManager.class.equals(type)) { + PersistenceContext pc = getAnnotation(annotations, PersistenceContext.class); + if (pc != null) { + if (jpaConfig.isJtaEnabled()) { + TransactionEntityManagers transactionEntityManagers = Arc.container() + .instance(TransactionEntityManagers.class).get(); + ForwardingEntityManager entityManager = new ForwardingEntityManager() { + + @Override + protected EntityManager delegate() { + return transactionEntityManagers.getEntityManager(pc.unitName()); + } + }; + return () -> entityManager; + } else { + EntityManagerFactory entityManagerFactory = jpaConfig.getEntityManagerFactory(pc.unitName()); + EntityManager entityManager = entityManagerFactory.createEntityManager(); + return new InstanceHandle() { + + @Override + public Object get() { + return entityManager; + } + + @Override + public void destroy() { + entityManager.close(); + } + }; + } + } + } + return null; + } + +} diff --git a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/TransactionEntityManagers.java b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/TransactionEntityManagers.java new file mode 100644 index 0000000000000..f3030691df307 --- /dev/null +++ b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/TransactionEntityManagers.java @@ -0,0 +1,43 @@ +package org.jboss.shamrock.jpa.runtime; + +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.PreDestroy; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; + +@RequestScoped +public class TransactionEntityManagers { + + @Inject + TransactionSynchronizationRegistry tsr; + + @Inject + TransactionManager tm; + + @Inject + JPAConfig jpaConfig; + + private final Map managers; + + public TransactionEntityManagers() { + this.managers = new HashMap<>(); + } + + EntityManager getEntityManager(String unitName) { + return managers.computeIfAbsent(unitName, + un -> new TransactionScopedEntityManager(tm, tsr, jpaConfig.getEntityManagerFactory(un))); + } + + @PreDestroy + void destroy() { + for (TransactionScopedEntityManager manager : managers.values()) { + manager.requestDone(); + } + } + +} diff --git a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/cdi/TransactionScopedEntityManager.java b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/TransactionScopedEntityManager.java similarity index 99% rename from jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/cdi/TransactionScopedEntityManager.java rename to jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/TransactionScopedEntityManager.java index 22605e35d9ea0..f8bc56244e395 100644 --- a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/cdi/TransactionScopedEntityManager.java +++ b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/TransactionScopedEntityManager.java @@ -1,4 +1,4 @@ -package org.jboss.shamrock.jpa.runtime.cdi; +package org.jboss.shamrock.jpa.runtime; import java.util.List; import java.util.Map; diff --git a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/cdi/SystemEntityManager.java b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/cdi/SystemEntityManager.java deleted file mode 100644 index bc523771d4726..0000000000000 --- a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/cdi/SystemEntityManager.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.jboss.shamrock.jpa.runtime.cdi; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Qualifier; - -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface SystemEntityManager { -} diff --git a/transactions/deployment/src/main/java/org/jboss/shamrock/transactions/TransactionsSetup.java b/transactions/deployment/src/main/java/org/jboss/shamrock/transactions/TransactionsSetup.java index 3321c4e364aad..23964f2db6013 100644 --- a/transactions/deployment/src/main/java/org/jboss/shamrock/transactions/TransactionsSetup.java +++ b/transactions/deployment/src/main/java/org/jboss/shamrock/transactions/TransactionsSetup.java @@ -1,12 +1,13 @@ package org.jboss.shamrock.transactions; +import org.jboss.shamrock.deployment.Capabilities; import org.jboss.shamrock.deployment.SetupContext; import org.jboss.shamrock.deployment.ShamrockSetup; public class TransactionsSetup implements ShamrockSetup { @Override public void setup(SetupContext context) { - context.addCapability("transactions"); + context.addCapability(Capabilities.TRANSACTIONS); context.addResourceProcessor(new TransactionsProcessor()); } } From 36bb218944c149234c1e701da7eb9615fd2dc441 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 8 Oct 2018 18:06:36 +0200 Subject: [PATCH 3/3] Minor cleanup and optimizations --- .../arc/deployment/ArcAnnotationProcessor.java | 2 +- .../jboss/shamrock/deployment/BeanDeployment.java | 7 ++++--- .../org/jboss/protean/arc/processor/DotNames.java | 3 --- .../jpa/cdi/HibernateCdiResourceProcessor.java | 15 ++++++++------- .../org/jboss/shamrock/jpa/runtime/JPAConfig.java | 15 +++++++++++++-- .../jpa/runtime/JPADeploymentTemplate.java | 4 ++++ 6 files changed, 30 insertions(+), 16 deletions(-) diff --git a/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java b/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java index 4a2d433c9a5ff..eb36305530fd9 100644 --- a/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java +++ b/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java @@ -105,7 +105,7 @@ public void process(ArchiveContext archiveContext, ProcessorContext processorCon builder.setIndex(index); builder.setAdditionalBeanDefiningAnnotations(additionalBeanDefiningAnnotations); builder.setSharedAnnotationLiterals(false); - builder.addResourceAnnotations(beanDeployment.getResourceAnnotations().stream().map(ran -> DotName.createSimple(ran)).collect(Collectors.toList())); + builder.addResourceAnnotations(beanDeployment.getResourceAnnotations()); builder.setReflectionRegistration(new ReflectionRegistration() { @Override public void registerMethod(MethodInfo methodInfo) { diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/BeanDeployment.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/BeanDeployment.java index 601bb3c166144..06f122b287aed 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/BeanDeployment.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/BeanDeployment.java @@ -11,6 +11,7 @@ import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.DotName; public class BeanDeployment { @@ -21,7 +22,7 @@ public class BeanDeployment { // Lite profile private final List, Collection>> annotationTransformers = new ArrayList<>(); - private final List resourceAnnotations = new ArrayList<>(); + private final List resourceAnnotations = new ArrayList<>(); // Full profile private final List extensions = new ArrayList<>(); @@ -46,7 +47,7 @@ public void addExtension(String extensionClass) { extensions.add(extensionClass); } - public void addResourceAnnotation(String resourceAnnotation) { + public void addResourceAnnotation(DotName resourceAnnotation) { resourceAnnotations.add(resourceAnnotation); } @@ -66,7 +67,7 @@ public List getExtensions() { return extensions; } - public List getResourceAnnotations() { + public List getResourceAnnotations() { return resourceAnnotations; } diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/DotNames.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/DotNames.java index c764d64951897..3734365173a30 100644 --- a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/DotNames.java +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/DotNames.java @@ -58,9 +58,6 @@ final class DotNames { static final DotName TYPED = DotName.createSimple(Typed.class.getName()); static final DotName CLASS = DotName.createSimple(Class.class.getName()); - static final DotName PERSISTENCE_CONTEXT = DotName.createSimple("javax.persistence.PersistenceContext"); - static final DotName PERSISTENCE_UNIT = DotName.createSimple("javax.persistence.PersistenceUnit"); - private DotNames() { } diff --git a/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/cdi/HibernateCdiResourceProcessor.java b/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/cdi/HibernateCdiResourceProcessor.java index 8e47c70c1a009..a5b88f7f0618f 100644 --- a/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/cdi/HibernateCdiResourceProcessor.java +++ b/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/cdi/HibernateCdiResourceProcessor.java @@ -42,8 +42,13 @@ public void process(ArchiveContext archiveContext, ProcessorContext processorCon JPADeploymentTemplate template = recorder.getRecordingProxy(JPADeploymentTemplate.class); beanDeployment.addAdditionalBean(JPAConfig.class, TransactionEntityManagers.class); - beanDeployment.addResourceAnnotation(PERSISTENCE_CONTEXT.toString()); - beanDeployment.addResourceAnnotation(PERSISTENCE_UNIT.toString()); + + if (processorContext.isCapabilityPresent(Capabilities.CDI_ARC)) { + processorContext.createResource("META-INF/services/org.jboss.protean.arc.ResourceReferenceProvider", + "org.jboss.shamrock.jpa.runtime.JPAResourceReferenceProvider".getBytes()); + beanDeployment.addResourceAnnotation(PERSISTENCE_CONTEXT); + beanDeployment.addResourceAnnotation(PERSISTENCE_UNIT); + } template.initializeJpa(null, processorContext.isCapabilityPresent(Capabilities.TRANSACTIONS)); @@ -53,6 +58,7 @@ public void process(ArchiveContext archiveContext, ProcessorContext processorCon for (PersistenceUnitDescriptor persistenceUnitDescriptor : pus) { template.bootstrapPersistenceUnit(null, persistenceUnitDescriptor.getName()); } + template.initDefaultPersistenceUnit(null); if (pus.size() == 1) { // There is only one persistence unit - register CDI beans for EM and EMF if no @@ -64,11 +70,6 @@ public void process(ArchiveContext archiveContext, ProcessorContext processorCon beanDeployment.addAdditionalBean(DefaultEntityManagerProducer.class); } } - - if (processorContext.isCapabilityPresent(Capabilities.CDI_ARC)) { - processorContext.createResource("META-INF/services/org.jboss.protean.arc.ResourceReferenceProvider", - "org.jboss.shamrock.jpa.runtime.JPAResourceReferenceProvider".getBytes()); - } } } diff --git a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPAConfig.java b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPAConfig.java index 7fc7ac4ed5b94..34425e575822b 100644 --- a/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPAConfig.java +++ b/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/JPAConfig.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import javax.annotation.PreDestroy; import javax.inject.Singleton; @@ -11,7 +12,6 @@ import org.jboss.logging.Logger; - @Singleton public class JPAConfig { @@ -21,9 +21,12 @@ public class JPAConfig { private final Map persistenceUnits; + private final AtomicReference defaultPersistenceUnitName; + public JPAConfig() { this.jtaEnabled = new AtomicBoolean(); this.persistenceUnits = new HashMap<>(); + this.defaultPersistenceUnitName = new AtomicReference(); } void setJtaEnabled(boolean value) { @@ -33,7 +36,9 @@ void setJtaEnabled(boolean value) { public EntityManagerFactory getEntityManagerFactory(String unitName) { if (unitName == null || unitName.isEmpty()) { if (persistenceUnits.size() == 1) { - return persistenceUnits.values().iterator().next(); + String defaultUnitName = defaultPersistenceUnitName.get(); + return defaultUnitName != null ? persistenceUnits.get(defaultUnitName) + : persistenceUnits.values().iterator().next(); } else { throw new IllegalStateException("Unable to identify the default PU: " + persistenceUnits); } @@ -45,6 +50,12 @@ void bootstrapPersistenceUnit(String unitName) { persistenceUnits.put(unitName, Persistence.createEntityManagerFactory(unitName)); } + void initDefaultPersistenceUnit() { + if (persistenceUnits.size() == 1) { + defaultPersistenceUnitName.set(persistenceUnits.keySet().iterator().next()); + } + } + boolean isJtaEnabled() { return jtaEnabled.get(); } 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 97dc59f1eef1a..7b8ee28c6049c 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 @@ -41,6 +41,10 @@ public void bootstrapPersistenceUnit(@ContextObject("bean.container") BeanContai beanContainer.instance(JPAConfig.class).bootstrapPersistenceUnit(unitName); } + public void initDefaultPersistenceUnit(@ContextObject("bean.container") BeanContainer beanContainer) { + beanContainer.instance(JPAConfig.class).initDefaultPersistenceUnit(); + } + public void initMetadata(List parsedPersistenceXmlDescriptors, Scanner scanner, @ContextObject("bean.container") BeanContainer beanContainer) { //this initializes the JPA metadata, and also sets the datasource if no connection URL has been set and a DataSource