Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[jpa] Add support for @Embeddable, @Embedded, superclass and interfaces #6

Merged
merged 2 commits into from
Aug 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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