Skip to content

Commit

Permalink
Merge pull request #10078 from gytis/enchance-entity-id-access
Browse files Browse the repository at this point in the history
Abstract REST Data resource and entity info
  • Loading branch information
FroMage authored Aug 17, 2020
2 parents 8279189 + 6250527 commit 3bd79a0
Show file tree
Hide file tree
Showing 48 changed files with 572 additions and 430 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource;
import io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource;
import io.quarkus.rest.data.panache.deployment.DataAccessImplementor;
import io.quarkus.rest.data.panache.deployment.RestDataEntityInfo;
import io.quarkus.rest.data.panache.deployment.RestDataResourceBuildItem;
import io.quarkus.rest.data.panache.deployment.RestDataResourceInfo;

Expand All @@ -35,29 +37,33 @@ FeatureBuildItem feature() {

@BuildStep
void findEntityResources(CombinedIndexBuildItem index, BuildProducer<RestDataResourceBuildItem> resourcesProducer) {
RestDataEntityInfoProvider entityInfoProvider = new RestDataEntityInfoProvider(index.getIndex());
for (ClassInfo classInfo : index.getIndex().getKnownDirectImplementors(PANACHE_ENTITY_RESOURCE_INTERFACE)) {
validateResource(index.getIndex(), classInfo);
List<Type> generics = getGenericTypes(classInfo);
String entityClassName = generics.get(0).toString();
String idClassName = generics.get(1).toString();
RestDataResourceInfo resourceInfo = HibernateOrmRestDataResourceInfo
.withEntityAccess(classInfo, idClassName, entityClassName);
String entityClassName = getGenericTypes(classInfo).get(0).toString();
String idClassName = getGenericTypes(classInfo).get(1).toString();
RestDataEntityInfo entityInfo = entityInfoProvider.get(entityClassName, idClassName);
DataAccessImplementor dataAccessImplementor = new EntityDataAccessImplementor(entityClassName);
RestDataResourceInfo resourceInfo = new RestDataResourceInfo(classInfo.toString(), entityInfo,
dataAccessImplementor);
resourcesProducer.produce(new RestDataResourceBuildItem(resourceInfo));
}
}

@BuildStep
void findRepositoryResources(CombinedIndexBuildItem index,
BuildProducer<RestDataResourceBuildItem> resourcesProducer,
void findRepositoryResources(CombinedIndexBuildItem index, BuildProducer<RestDataResourceBuildItem> resourcesProducer,
BuildProducer<UnremovableBeanBuildItem> unremovableBeansProducer) {
RestDataEntityInfoProvider entityInfoProvider = new RestDataEntityInfoProvider(index.getIndex());
for (ClassInfo classInfo : index.getIndex().getKnownDirectImplementors(PANACHE_REPOSITORY_RESOURCE_INTERFACE)) {
validateResource(index.getIndex(), classInfo);
List<Type> generics = getGenericTypes(classInfo);
String repositoryClassName = generics.get(0).toString();
String entityClassName = generics.get(1).toString();
String idClassName = generics.get(2).toString();
RestDataResourceInfo resourceInfo = HibernateOrmRestDataResourceInfo
.withRepositoryAccess(classInfo, idClassName, entityClassName, repositoryClassName);
RestDataEntityInfo entityInfo = entityInfoProvider.get(entityClassName, idClassName);
DataAccessImplementor dataAccessImplementor = new RepositoryDataAccessImplementor(repositoryClassName);
RestDataResourceInfo resourceInfo = new RestDataResourceInfo(classInfo.toString(), entityInfo,
dataAccessImplementor);
resourcesProducer.produce(new RestDataResourceBuildItem(resourceInfo));
unremovableBeansProducer.produce(
new UnremovableBeanBuildItem(new UnremovableBeanBuildItem.BeanClassNameExclusion(repositoryClassName)));
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment;

import javax.persistence.Id;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;

import io.quarkus.deployment.bean.JavaBeanUtil;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.rest.data.panache.deployment.RestDataEntityInfo;

final class RestDataEntityInfoProvider {

private final IndexView index;

RestDataEntityInfoProvider(IndexView index) {
this.index = index;
}

RestDataEntityInfo get(String entityType, String idType) {
ClassInfo classInfo = index.getClassByName(DotName.createSimple(entityType));
FieldInfo idField = getIdField(classInfo);
return new RestDataEntityInfo(classInfo.toString(), idType, idField, getSetter(classInfo, idField));
}

private FieldInfo getIdField(ClassInfo classInfo) {
ClassInfo tmpClassInfo = classInfo;
while (tmpClassInfo != null) {
for (FieldInfo field : tmpClassInfo.fields()) {
if (field.hasAnnotation(DotName.createSimple(Id.class.getName()))) {
return field;
}
}
if (classInfo.superName() != null) {
tmpClassInfo = index.getClassByName(classInfo.superName());
} else {
tmpClassInfo = null;
}
}
throw new IllegalArgumentException("Couldn't find id field of " + classInfo);
}

private MethodDescriptor getSetter(ClassInfo entityClass, FieldInfo field) {
if (entityClass == null) {
return null;
}
MethodInfo methodInfo = entityClass.method(JavaBeanUtil.getSetterName(field.name()), field.type());
if (methodInfo != null) {
return MethodDescriptor.of(methodInfo);
} else if (entityClass.superName() != null) {
return getSetter(index.getClassByName(entityClass.superName()), field);
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ void shouldGetComplexObjects() {
given().accept("application/json")
.when().get("/collections/full")
.then().statusCode(200)
.and().body("name", is(equalTo("full")))
.and().body("id", is(equalTo("full")))
.and().body("name", is(equalTo("full collection")))
.and().body("items.id", contains(1, 2))
.and().body("items.name", contains("first", "second"));
}
Expand All @@ -65,7 +66,8 @@ void shouldGetComplexHalObjects() {
given().accept("application/hal+json")
.when().get("/collections/full")
.then().statusCode(200)
.and().body("name", is(equalTo("full")))
.and().body("id", is(equalTo("full")))
.and().body("name", is(equalTo("full collection")))
.and().body("items.id", contains(1, 2))
.and().body("items.name", contains("first", "second"))
.and().body("_links.add.href", endsWith("/collections"))
Expand Down Expand Up @@ -105,7 +107,8 @@ void shouldListComplexObjects() {
given().accept("application/json")
.when().get("/collections")
.then().statusCode(200)
.and().body("name", contains("empty", "full"))
.and().body("id", contains("empty", "full"))
.and().body("name", contains("empty collection", "full collection"))
.and().body("items.id[0]", is(empty()))
.and().body("items.id[1]", contains(1, 2))
.and().body("items.name[1]", contains("first", "second"));
Expand All @@ -116,7 +119,8 @@ void shouldListComplexHalObjects() {
given().accept("application/hal+json")
.when().get("/collections")
.then().statusCode(200)
.and().body("_embedded.collections.name", contains("empty", "full"))
.and().body("_embedded.collections.id", contains("empty", "full"))
.and().body("_embedded.collections.name", contains("empty collection", "full collection"))
.and().body("_embedded.collections.items.id[0]", is(empty()))
.and().body("_embedded.collections.items.id[1]", contains(1, 2))
.and().body("_embedded.collections.items.name[1]", contains("first", "second"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,18 @@

import static io.restassured.RestAssured.given;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.hibernate.orm.rest.data.panache.deployment.entity.CollectionsController;
import io.quarkus.test.QuarkusDevModeTest;

public class HotReloadTest {
public abstract class AbstractHotReloadTest {

@RegisterExtension
public final static QuarkusDevModeTest TEST = new QuarkusDevModeTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addPackage(CollectionsController.class.getPackage()) // entity package
.addAsResource("application.properties")
.addAsResource("import.sql"));
protected abstract QuarkusDevModeTest getTestArchive();

@Test
public void shouldModifyPathAndDisableHal() {
TEST.modifySourceFile(CollectionsController.class,
getTestArchive().modifySourceFile(CollectionsController.class,
s -> s.replace("@ResourceProperties(hal = true, paged = false)", "@ResourceProperties(path = \"col\")"));
given().accept("application/json")
.when().get("/col")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,15 @@
import java.util.Objects;
import java.util.stream.Stream;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;

import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.entity.Collection;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.entity.CollectionsController;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.entity.Item;
import io.quarkus.rest.data.panache.MethodProperties;
import io.quarkus.rest.data.panache.ResourceProperties;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.response.Response;

class PathCustomisationTest {

@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(Collection.class, CollectionsController.class, Item.class,
CustomPathCollectionsController.class)
.addAsResource("application.properties")
.addAsResource("import.sql"));
public abstract class AbstractPathCustomisationTest {

@ParameterizedTest
@ArgumentsSource(TestArgumentsProvider.class)
Expand All @@ -46,46 +28,27 @@ void testGet(String path, String accept) {
@ParameterizedTest
@ArgumentsSource(TestArgumentsProvider.class)
void testCreateAndDelete(String path, String accept) {
String name = "test-" + Objects.hash(path, accept);
Response response = given().body("{\"name\": \"" + name + "\"}")
String id = "test-" + Objects.hash(path, accept);
Response response = given().body("{\"id\": \"" + id + "\", \"name\": \"test collection\"}")
.and().contentType("application/json")
.and().accept(accept)
.when().post(path)
.thenReturn();
assertThat(response.getStatusCode()).isEqualTo(201);
when().delete(path + "/" + name)
when().delete(path + "/" + id)
.then().statusCode(204);
}

@ParameterizedTest
@ArgumentsSource(TestArgumentsProvider.class)
void testUpdate(String path, String accept) {
given().body("{\"name\": \"test\"}")
given().body("{\"id\": \"empty\", \"name\": \"updated collection\"}")
.and().contentType("application/json")
.and().accept(accept)
.when().put(path + "/empty")
.then().statusCode(204);
}

@ResourceProperties(path = "custom-collections", hal = true)
public interface CustomPathCollectionsController extends PanacheEntityResource<Collection, String> {

@MethodProperties(path = "api")
javax.ws.rs.core.Response list();

@MethodProperties(path = "api")
Collection get(String name);

@MethodProperties(path = "api")
javax.ws.rs.core.Response add(Collection collection);

@MethodProperties(path = "api")
javax.ws.rs.core.Response update(String name, Collection collection);

@MethodProperties(path = "api")
void delete(String name);
}

static class TestArgumentsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public abstract class AbstractPostMethodTest {
void shouldCreateSimpleObject() {
Response response = given().accept("application/json")
.and().contentType("application/json")
.and().body("{\"name\": \"test-simple\", \"collection\": {\"name\": \"full\"}}")
.and().body("{\"name\": \"test-simple\", \"collection\": {\"id\": \"full\"}}")
.when().post("/items")
.thenReturn();
assertThat(response.statusCode()).isEqualTo(201);
Expand All @@ -33,7 +33,7 @@ void shouldCreateSimpleObject() {
void shouldCreateSimpleHalObject() {
Response response = given().accept("application/hal+json")
.and().contentType("application/json")
.and().body("{\"name\": \"test-simple-hal\", \"collection\": {\"name\": \"full\"}}")
.and().body("{\"name\": \"test-simple-hal\", \"collection\": {\"id\": \"full\"}}")
.when().post("/items")
.thenReturn();
assertThat(response.statusCode()).isEqualTo(201);
Expand All @@ -53,23 +53,25 @@ void shouldCreateSimpleHalObject() {
void shouldCreateComplexObjects() {
given().accept("application/json")
.and().contentType("application/json")
.and().body("{\"name\": \"test-complex\"}")
.and().body("{\"id\": \"test-complex\", \"name\": \"test collection\"}")
.when().post("/collections")
.then().statusCode(201)
.and().header("Location", endsWith("/test-complex"))
.and().body("name", is(equalTo("test-complex")))
.and().body("id", is(equalTo("test-complex")))
.and().body("name", is(equalTo("test collection")))
.and().body("items", is(empty()));
}

@Test
void shouldCreateComplexHalObjects() {
given().accept("application/hal+json")
.and().contentType("application/json")
.and().body("{\"name\": \"test-complex-hal\"}")
.and().body("{\"id\": \"test-complex-hal\", \"name\": \"test collection\"}")
.when().post("/collections")
.then().statusCode(201)
.and().header("Location", endsWith("/test-complex-hal"))
.and().body("name", is(equalTo("test-complex-hal")))
.and().body("id", is(equalTo("test-complex-hal")))
.and().body("name", is(equalTo("test collection")))
.and().body("items", is(empty()))
.and().body("_links.add.href", endsWith("/collections"))
.and().body("_links.list.href", endsWith("/collections"))
Expand Down
Loading

0 comments on commit 3bd79a0

Please sign in to comment.