From c7e79a9d3ddf72a490b7a04179a4198f7e6e6bfb Mon Sep 17 00:00:00 2001 From: Emmanuel Bernard Date: Mon, 6 Aug 2018 14:26:39 +0200 Subject: [PATCH 1/2] [jpa] Add support for @Embeddable and @Embedded --- .../jboss/shamrock/example/jpa/Address.java | 37 +++++++++++++ .../jboss/shamrock/example/jpa/Customer.java | 24 ++++++++ .../shamrock/example/jpa/JPATestEndpoint.java | 37 +++++++++++++ .../shamrock/example/jpa/WorkAddress.java | 21 +++++++ .../shamrock/jpa/JPAAnnotationProcessor.java | 55 +++++++++++++++---- 5 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Address.java create mode 100644 examples/strict/src/main/java/org/jboss/shamrock/example/jpa/WorkAddress.java diff --git a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Address.java b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Address.java new file mode 100644 index 0000000000000..5b1b2ef334cbb --- /dev/null +++ b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Address.java @@ -0,0 +1,37 @@ +package org.jboss.shamrock.example.jpa; + +/** + * This is an enmarked @Embeddable class. + * Let's see if just being referenced by the main entity is enough to be detected. + * + * @author Emmanuel Bernard emmanuel@hibernate.org + */ +public class Address { + private String street1; + private String street2; + private String zipCode; + + public String getStreet1() { + return street1; + } + + public void setStreet1(String street1) { + this.street1 = street1; + } + + public String getStreet2() { + return street2; + } + + public void setStreet2(String street2) { + this.street2 = street2; + } + + public String getZipCode() { + return zipCode; + } + + public void setZipCode(String zipCode) { + this.zipCode = zipCode; + } +} diff --git a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Customer.java b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Customer.java index edf2492abbf0a..a32b7b5c4d450 100644 --- a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Customer.java +++ b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Customer.java @@ -1,9 +1,12 @@ package org.jboss.shamrock.example.jpa; +import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.Id; /** + * Used to test reflection references for JPA + * * @author Emmanuel Bernard emmanuel@hibernate.org */ @Entity @@ -12,6 +15,9 @@ public class Customer { // no getter explicitly to test field only reflective access private Long id; + private Address address; + private WorkAddress workAddress; + private String name; public String getName() { @@ -21,4 +27,22 @@ public String getName() { public void setName(String name) { this.name = name; } + + // Address is referenced but not marked as @Embeddable + @Embedded + public Address getAddress() { + return address; + } + + public WorkAddress getWorkAddress() { + return workAddress; + } + + public void setWorkAddress(WorkAddress workAddress) { + this.workAddress = workAddress; + } + + public void setAddress(Address address) { + this.address = address; + } } diff --git a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/JPATestEndpoint.java b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/JPATestEndpoint.java index fb2b0a40d4bf1..f175eb1c0c0bf 100644 --- a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/JPATestEndpoint.java +++ b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/JPATestEndpoint.java @@ -15,6 +15,8 @@ public class JPATestEndpoint extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { makeSureNonEntityAreDCE(resp); makeSureEntitiesAreAccessibleViaReflection(resp); + makeSureNonAnnotatedEmbeddableAreAccessibleViaReflection(resp); + makeSureAnnotatedEmbeddableAreAccessibleViaReflection(resp); resp.getWriter().write("OK"); } @@ -41,6 +43,41 @@ private void makeSureEntitiesAreAccessibleViaReflection(HttpServletResponse resp } } + private void makeSureAnnotatedEmbeddableAreAccessibleViaReflection(HttpServletResponse resp) throws IOException { + try { + String className = getTrickedClassName("org.jboss.shamrock.example.jpa.WorkAddress"); + + Class custClass = Class.forName(className); + Object instance = custClass.newInstance(); + Method setter = custClass.getDeclaredMethod("setCompany", String.class); + Method getter = custClass.getDeclaredMethod("getCompany"); + setter.invoke(instance, "Red Hat"); + if (! "Red Hat".equals(getter.invoke(instance))) { + resp.getWriter().write("@Embeddable embeddable should be reachable and usable"); + } + } + catch (Exception e) { + resp.getWriter().write(e.toString()); + } + } + private void makeSureNonAnnotatedEmbeddableAreAccessibleViaReflection(HttpServletResponse resp) throws IOException { + try { + String className = getTrickedClassName("org.jboss.shamrock.example.jpa.Address"); + + Class custClass = Class.forName(className); + Object instance = custClass.newInstance(); + Method setter = custClass.getDeclaredMethod("setStreet1", String.class); + Method getter = custClass.getDeclaredMethod("getStreet1"); + setter.invoke(instance, "1 rue du General Leclerc"); + if (! "1 rue du General Leclerc".equals(getter.invoke(instance))) { + resp.getWriter().write("Non @Embeddable embeddable getter / setter should be reachable and usable"); + } + } + catch (Exception e) { + resp.getWriter().write(e.toString()); + } + } + private void makeSureNonEntityAreDCE(HttpServletResponse resp) { try { String className = getTrickedClassName("org.jboss.shamrock.example.jpa.NotAnEntityNotReferenced"); diff --git a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/WorkAddress.java b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/WorkAddress.java new file mode 100644 index 0000000000000..2a2c37e0b3c6e --- /dev/null +++ b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/WorkAddress.java @@ -0,0 +1,21 @@ +package org.jboss.shamrock.example.jpa; + +import javax.persistence.Embeddable; + +/** + * Class marked @Embeddable explicitly so it is picked up. + * + * @author Emmanuel Bernard emmanuel@hibernate.org + */ +@Embeddable +public class WorkAddress { + private String company; + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } +} diff --git a/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/JPAAnnotationProcessor.java b/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/JPAAnnotationProcessor.java index feb61a137cbae..194ba9ca4fd9c 100644 --- a/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/JPAAnnotationProcessor.java +++ b/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/JPAAnnotationProcessor.java @@ -1,6 +1,7 @@ package org.jboss.shamrock.jpa; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; import org.jboss.shamrock.deployment.ArchiveContext; @@ -8,6 +9,8 @@ import org.jboss.shamrock.deployment.ResourceProcessor; import org.jboss.shamrock.deployment.codegen.BytecodeRecorder; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; import javax.persistence.Entity; import java.util.Collection; @@ -22,6 +25,8 @@ public class JPAAnnotationProcessor implements ResourceProcessor { private static final DotName JPA_ENTITY = DotName.createSimple(Entity.class.getName()); + private static final DotName EMBEDDABLE = DotName.createSimple(Embeddable.class.getName()); + private static final DotName EMBEDDED = DotName.createSimple(Embedded.class.getName()); @Override public void process(ArchiveContext archiveContext, ProcessorContext processorContext) throws Exception { @@ -34,21 +39,49 @@ public void process(ArchiveContext archiveContext, ProcessorContext processorCon // list all entities and create a JPADeploymentTemplate out of it // Not functional as we will need one deployment template per persistence unit final IndexView index = archiveContext.getIndex(); - Collection jpaAnnotations = index.getAnnotations(JPA_ENTITY); - if (jpaAnnotations != null && jpaAnnotations.size() > 0) { - // TODO priority? - try (BytecodeRecorder context = processorContext.addStaticInitTask(100)) { - JPADeploymentTemplate template = context.getRecordingProxy(JPADeploymentTemplate.class); - for (AnnotationInstance annotation : jpaAnnotations) { - String entityClass = annotation.target().asClass().toString(); - System.out.println(entityClass); - processorContext.addReflectiveClass(true, true, entityClass); - template.addEntity(annotation.target().asClass().toString()); + // TODO what priority to give JPA? + try (BytecodeRecorder context = processorContext.addStaticInitTask(100)) { + JPADeploymentTemplate template = context.getRecordingProxy(JPADeploymentTemplate.class); + enlistJPAModelClasses(JPA_ENTITY, processorContext, template, index); + enlistJPAModelClasses(EMBEDDABLE, processorContext, template, index); + enlistReturnType(processorContext, index); + + template.enlistPersistenceUnit(); + } + } + + private void enlistReturnType(ProcessorContext processorContext, IndexView index) { + Collection annotations = index.getAnnotations(EMBEDDED); + if (annotations != null && annotations.size() > 0) { + for (AnnotationInstance annotation : annotations) { + AnnotationTarget target = annotation.target(); + String jpaClassName = null; + switch (target.kind()) { + case FIELD: + // TODO could fail if that's an array or a generic type + jpaClassName = target.asField().type().toString(); + break; + case METHOD: + // TODO could fail if that's an array or a generic type + jpaClassName = target.asMethod().returnType().toString(); + break; + default: + throw new IllegalStateException("[internal error] @Embedded placed on a unknown element: " + target); } - template.enlistPersistenceUnit(); + processorContext.addReflectiveClass(true, true, jpaClassName); } } + } + private void enlistJPAModelClasses(DotName dotName, ProcessorContext processorContext, JPADeploymentTemplate template, IndexView index) { + Collection jpaAnnotations = index.getAnnotations(dotName); + if (jpaAnnotations != null && jpaAnnotations.size() > 0) { + for (AnnotationInstance annotation : jpaAnnotations) { + String entityClass = annotation.target().asClass().toString(); + processorContext.addReflectiveClass(true, true, entityClass); + template.addEntity(annotation.target().asClass().toString()); + } + } } @Override From 39f39228cf8aabeadfabc62f1ecbde94b0f690b9 Mon Sep 17 00:00:00 2001 From: Emmanuel Bernard Date: Mon, 6 Aug 2018 17:54:54 +0200 Subject: [PATCH 2/2] [jpa] Add support for superclasses and interfaces of entities --- .../jboss/shamrock/example/jpa/Animal.java | 16 ++++++ .../jboss/shamrock/example/jpa/Customer.java | 2 +- .../org/jboss/shamrock/example/jpa/Human.java | 21 ++++++++ .../shamrock/example/jpa/JPATestEndpoint.java | 14 +++++ .../shamrock/jpa/JPAAnnotationProcessor.java | 52 +++++++++++++++---- 5 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Animal.java create mode 100644 examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Human.java diff --git a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Animal.java b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Animal.java new file mode 100644 index 0000000000000..405c85b0a603d --- /dev/null +++ b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Animal.java @@ -0,0 +1,16 @@ +package org.jboss.shamrock.example.jpa; + +/** + * @author Emmanuel Bernard emmanuel@hibernate.org + */ +public class Animal { + private double weight; + + public double getWeight() { + return weight; + } + + public void setWeight(double weight) { + this.weight = weight; + } +} diff --git a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Customer.java b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Customer.java index a32b7b5c4d450..943d1e1536577 100644 --- a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Customer.java +++ b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Customer.java @@ -10,7 +10,7 @@ * @author Emmanuel Bernard emmanuel@hibernate.org */ @Entity -public class Customer { +public class Customer extends Human { @Id // no getter explicitly to test field only reflective access private Long id; diff --git a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Human.java b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Human.java new file mode 100644 index 0000000000000..487af53d5a6cf --- /dev/null +++ b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/Human.java @@ -0,0 +1,21 @@ +package org.jboss.shamrock.example.jpa; + +import javax.persistence.MappedSuperclass; + +/** + * Mapped superclass test + * + * @author Emmanuel Bernard emmanuel@hibernate.org + */ +@MappedSuperclass +public class Human extends Animal { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/JPATestEndpoint.java b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/JPATestEndpoint.java index f175eb1c0c0bf..53e49d4e484dd 100644 --- a/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/JPATestEndpoint.java +++ b/examples/strict/src/main/java/org/jboss/shamrock/example/jpa/JPATestEndpoint.java @@ -17,9 +17,23 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO makeSureEntitiesAreAccessibleViaReflection(resp); makeSureNonAnnotatedEmbeddableAreAccessibleViaReflection(resp); makeSureAnnotatedEmbeddableAreAccessibleViaReflection(resp); + makeSureClassAreAccessibleViaReflection("org.jboss.shamrock.example.jpa.Human", "Unable to enlist @MappedSuperclass", resp); + makeSureClassAreAccessibleViaReflection("org.jboss.shamrock.example.jpa.Animal", "Unable to enlist entity superclass", resp); resp.getWriter().write("OK"); } + private void makeSureClassAreAccessibleViaReflection(String className, String error, HttpServletResponse resp) throws IOException { + try { + className = getTrickedClassName(className); + + Class custClass = Class.forName(className); + Object instance = custClass.newInstance(); + } + catch (Exception e) { + resp.getWriter().write(error + " " + e.toString()); + } + } + private void makeSureEntitiesAreAccessibleViaReflection(HttpServletResponse resp) throws IOException { try { String className = getTrickedClassName("org.jboss.shamrock.example.jpa.Customer"); diff --git a/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/JPAAnnotationProcessor.java b/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/JPAAnnotationProcessor.java index 194ba9ca4fd9c..09d50d777d719 100644 --- a/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/JPAAnnotationProcessor.java +++ b/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/JPAAnnotationProcessor.java @@ -1,9 +1,6 @@ package org.jboss.shamrock.jpa; -import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.AnnotationTarget; -import org.jboss.jandex.DotName; -import org.jboss.jandex.IndexView; +import org.jboss.jandex.*; import org.jboss.shamrock.deployment.ArchiveContext; import org.jboss.shamrock.deployment.ProcessorContext; import org.jboss.shamrock.deployment.ResourceProcessor; @@ -55,20 +52,20 @@ private void enlistReturnType(ProcessorContext processorContext, IndexView index if (annotations != null && annotations.size() > 0) { for (AnnotationInstance annotation : annotations) { AnnotationTarget target = annotation.target(); - String jpaClassName = null; + DotName jpaClassName = null; switch (target.kind()) { case FIELD: // TODO could fail if that's an array or a generic type - jpaClassName = target.asField().type().toString(); + jpaClassName = target.asField().type().name(); break; case METHOD: // TODO could fail if that's an array or a generic type - jpaClassName = target.asMethod().returnType().toString(); + jpaClassName = target.asMethod().returnType().name(); break; default: throw new IllegalStateException("[internal error] @Embedded placed on a unknown element: " + target); } - processorContext.addReflectiveClass(true, true, jpaClassName); + addClassHierarchyToReflectiveList(processorContext, index, jpaClassName); } } } @@ -77,13 +74,46 @@ private void enlistJPAModelClasses(DotName dotName, ProcessorContext processorCo Collection jpaAnnotations = index.getAnnotations(dotName); if (jpaAnnotations != null && jpaAnnotations.size() > 0) { for (AnnotationInstance annotation : jpaAnnotations) { - String entityClass = annotation.target().asClass().toString(); - processorContext.addReflectiveClass(true, true, entityClass); - template.addEntity(annotation.target().asClass().toString()); + DotName targetDotName = annotation.target().asClass().name(); + addClassHierarchyToReflectiveList(processorContext, index, targetDotName); + template.addEntity(targetDotName.toString()); } } } + /** + * Add the class to the reflective list with full method and field access. + * Add the superclasses recursively as well as the interfaces. + * + * TODO this approach fails if the Jandex index is not complete (e.g. misses somes interface or super types) + * TODO should we also return the return types of all methods and fields? It could container Enums for example. + */ + private void addClassHierarchyToReflectiveList(ProcessorContext processorContext, IndexView index, DotName className) { + // If type is not Object + // recursively add superclass and interfaces + if (className == null) { + // java.lang.Object + return; + } + ClassInfo classInfo = index.getClassByName(className); + if (classInfo == null) { + if (className == ClassType.OBJECT_TYPE.name()) { + return; + } + else { + throw new IllegalStateException("The Jandex index is not complete, missing: " + className.toString()); + } + } + // add class for reflection + processorContext.addReflectiveClass(true, true, className.toString()); + // add superclass recursively + addClassHierarchyToReflectiveList(processorContext, index, classInfo.superName()); + // add interfaces recursively + for (DotName interfaceDotName : classInfo.interfaceNames()) { + addClassHierarchyToReflectiveList(processorContext, index, interfaceDotName); + } + } + @Override public int getPriority() { // Because we are the best