Skip to content

Commit

Permalink
Merge pull request #6 from emmanuelbernard/embeddable
Browse files Browse the repository at this point in the history
[jpa] Add support for @embeddable, @Embedded, superclass and interfaces
  • Loading branch information
stuartwdouglas authored Aug 6, 2018
2 parents 989b4f0 + 39f3922 commit 74dabb9
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -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 [email protected]
*/
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.jboss.shamrock.example.jpa;

/**
* @author Emmanuel Bernard [email protected]
*/
public class Animal {
private double weight;

public double getWeight() {
return weight;
}

public void setWeight(double weight) {
this.weight = weight;
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
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 [email protected]
*/
@Entity
public class Customer {
public class Customer extends Human {
@Id
// no getter explicitly to test field only reflective access
private Long id;

private Address address;
private WorkAddress workAddress;

private String name;

public String getName() {
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.jboss.shamrock.example.jpa;

import javax.persistence.MappedSuperclass;

/**
* Mapped superclass test
*
* @author Emmanuel Bernard [email protected]
*/
@MappedSuperclass
public class Human extends Animal {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,25 @@ public class JPATestEndpoint extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
makeSureNonEntityAreDCE(resp);
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");
Expand All @@ -41,6 +57,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");
Expand Down
Original file line number Diff line number Diff line change
@@ -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 [email protected]
*/
@Embeddable
public class WorkAddress {
private String company;

public String getCompany() {
return company;
}

public void setCompany(String company) {
this.company = company;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package org.jboss.shamrock.jpa;

import org.jboss.jandex.AnnotationInstance;
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;
import org.jboss.shamrock.deployment.codegen.BytecodeRecorder;

import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import java.util.Collection;

Expand All @@ -22,6 +22,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 {
Expand All @@ -34,21 +36,82 @@ 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<AnnotationInstance> 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<AnnotationInstance> annotations = index.getAnnotations(EMBEDDED);
if (annotations != null && annotations.size() > 0) {
for (AnnotationInstance annotation : annotations) {
AnnotationTarget target = annotation.target();
DotName jpaClassName = null;
switch (target.kind()) {
case FIELD:
// TODO could fail if that's an array or a generic type
jpaClassName = target.asField().type().name();
break;
case METHOD:
// TODO could fail if that's an array or a generic type
jpaClassName = target.asMethod().returnType().name();
break;
default:
throw new IllegalStateException("[internal error] @Embedded placed on a unknown element: " + target);
}
template.enlistPersistenceUnit();
addClassHierarchyToReflectiveList(processorContext, index, jpaClassName);
}
}
}

private void enlistJPAModelClasses(DotName dotName, ProcessorContext processorContext, JPADeploymentTemplate template, IndexView index) {
Collection<AnnotationInstance> jpaAnnotations = index.getAnnotations(dotName);
if (jpaAnnotations != null && jpaAnnotations.size() > 0) {
for (AnnotationInstance annotation : jpaAnnotations) {
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
Expand Down

0 comments on commit 74dabb9

Please sign in to comment.