entities);
+ public void implementAddList(ClassCreator classCreator, String repositoryInterfaceName) {
+ MethodCreator methodCreator = classCreator.getMethodCreator("addAll", List.class, Iterable.class);
+ if (entityClassHelper.isListCrudRepository(repositoryInterfaceName)) {
+ ResultHandle entity = methodCreator.getMethodParam(0);
+ ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterfaceName);
+ ResultHandle result = methodCreator.invokeInterfaceMethod(SAVE_LIST, repository, entity);
+ methodCreator.returnValue(result);
+ } else {
+ methodCreator.throwException(RuntimeException.class, "Method not implemented");
+ }
+ LOGGER.infof("Method code: %s ", methodCreator.toString());
+ methodCreator.close();
+ }
+
+ public void implementUpdate(ClassCreator classCreator, String repositoryInterfaceName, String entityType) {
+ MethodCreator methodCreator = classCreator.getMethodCreator("update", Object.class, Object.class, Object.class);
+ if (entityClassHelper.isCrudRepository(repositoryInterfaceName)) {
+ ResultHandle id = methodCreator.getMethodParam(0);
+ ResultHandle entity = methodCreator.getMethodParam(1);
+ // Set entity ID before executing an update to make sure that a requested object ID matches a given entity ID.
+ setId(methodCreator, entityType, entity, id);
+ ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterfaceName);
+ ResultHandle result = methodCreator.invokeInterfaceMethod(UPDATE, repository, entity);
+ methodCreator.returnValue(result);
+ } else {
+ methodCreator.throwException(RuntimeException.class, "Method not implemented");
+ }
+ LOGGER.infof("Method code: %s ", methodCreator.toString());
+ methodCreator.close();
+ }
+
+ public void implementDelete(ClassCreator classCreator, String repositoryInterfaceName) {
+ MethodCreator methodCreator = classCreator.getMethodCreator("delete", boolean.class, Object.class);
+
+ if (entityClassHelper.isCrudRepository(repositoryInterfaceName)) {
+ ResultHandle id = methodCreator.getMethodParam(0);
+ ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterfaceName);
+ ResultHandle entity = findById(methodCreator, id, repository);
+ AssignableResultHandle result = methodCreator.createVariable(boolean.class);
+ BranchResult entityExists = methodCreator.ifNotNull(entity);
+ entityExists.trueBranch().invokeInterfaceMethod(DELETE, repository, id);
+ entityExists.trueBranch().assign(result, entityExists.trueBranch().load(true));
+ entityExists.falseBranch().assign(result, entityExists.falseBranch().load(false));
+
+ methodCreator.returnValue(result);
+ } else {
+ methodCreator.throwException(RuntimeException.class, "Method not implemented");
+ }
+ LOGGER.infof("Method code: %s ", methodCreator.toString());
+ methodCreator.close();
+ }
+
+ private ResultHandle findById(BytecodeCreator creator, ResultHandle id, ResultHandle repository) {
+ ResultHandle optional = creator.invokeInterfaceMethod(GET, repository, id);
+ return creator.invokeVirtualMethod(ofMethod(Optional.class, "orElse", Object.class, Object.class),
+ optional, creator.loadNull());
+ }
+
+ private void setId(BytecodeCreator creator, String entityType, ResultHandle entity, ResultHandle id) {
+ FieldInfo idField = entityClassHelper.getIdField(entityType);
+ MethodDescriptor idSetter = entityClassHelper.getSetter(entityType, idField);
+ creator.invokeVirtualMethod(idSetter, entity, id);
+ }
+
+ /**
+ *
+ * Pageable toPageable(Page panachePage) {
+ * return PageRequest.of(panachePage.index, panachePage.size);
+ * }
+ *
+ */
+ private ResultHandle toPageable(MethodCreator creator, ResultHandle panachePage) {
+ ResultHandle index = creator.readInstanceField(of(PANACHE_PAGE, "index", int.class), panachePage);
+ ResultHandle size = creator.readInstanceField(of(PANACHE_PAGE, "size", int.class), panachePage);
+ return creator.invokeStaticMethod(
+ ofMethod(PageRequest.class, "of", PageRequest.class, int.class, int.class), index, size);
+ }
+
+ /**
+ *
+ * Pageable toPageable(Page panachePage, io.quarkus.panache.common.Sort panacheSort) {
+ * Sort springSort = toSpringSort(panacheSort);
+ * return PageRequest.of(panachePage.index, panachePage.size, springSort);
+ * }
+ *
+ */
+ private ResultHandle toPageable(MethodCreator creator, ResultHandle panachePage, ResultHandle panacheSort) {
+ ResultHandle index = creator.readInstanceField(of(PANACHE_PAGE, "index", int.class), panachePage);
+ ResultHandle size = creator.readInstanceField(of(PANACHE_PAGE, "size", int.class), panachePage);
+ ResultHandle springSort = toSpringSort(creator, panacheSort);
+ return creator.invokeStaticMethod(
+ ofMethod(PageRequest.class, "of", PageRequest.class, int.class, int.class,
+ org.springframework.data.domain.Sort.class),
+ index, size, springSort);
+ }
+
+ /**
+ *
+ * Sort toSpringSort(io.quarkus.panache.common.Sort sort) {
+ * Sort springSort;
+ * springSort = Sort.unsorted();
+ * List columns = sort.getColumns();
+ * Iterator columnsIterator = columns.iterator();
+ * while (columnsIterator.hasNext()) {
+ * io.quarkus.panache.common.Sort.Column column = columnsIterator.next();
+ * Sort.Order[] orderArray = new Sort.Order[1];
+ * String columnName = column.getName();
+ * io.quarkus.panache.common.Sort.Direction direction = column.getDirection();
+ * io.quarkus.panache.common.Sort.Direction ascending = io.quarkus.panache.common.Sort.Direction
+ * .valueOf("Ascending");
+ * if (ascending.equals(direction)) {
+ * orderArray[0] = Sort.Order.asc(columnName);
+ * } else {
+ * orderArray[0] = Sort.Order.desc(columnName);
+ * }
+ * Sort subSort = Sort.by(orderArray);
+ * springSort = subSort.and(subSort);
+ * }
+ * return springSort;
+ * }
+ *
+ */
+ private ResultHandle toSpringSort(MethodCreator creator, ResultHandle panacheSort) {
+ AssignableResultHandle springSort = creator.createVariable(org.springframework.data.domain.Sort.class);
+ creator.assign(springSort, creator.invokeStaticMethod(
+ ofMethod(org.springframework.data.domain.Sort.class, "unsorted", org.springframework.data.domain.Sort.class)));
+
+ // Loop through the columns
+ ResultHandle columns = creator.invokeVirtualMethod(
+ ofMethod(PANACHE_SORT, "getColumns", List.class), panacheSort);
+ ResultHandle columnsIterator = creator.invokeInterfaceMethod(
+ ofMethod(List.class, "iterator", Iterator.class), columns);
+ BytecodeCreator loopCreator = creator.whileLoop(c -> iteratorHasNext(c, columnsIterator)).block();
+ ResultHandle column = loopCreator.invokeInterfaceMethod(
+ ofMethod(Iterator.class, "next", Object.class), columnsIterator);
+ addColumn(loopCreator, springSort, column);
+
+ return springSort;
+ }
+
+ private BranchResult iteratorHasNext(BytecodeCreator creator, ResultHandle iterator) {
+ return creator.ifTrue(
+ creator.invokeInterfaceMethod(ofMethod(Iterator.class, "hasNext", boolean.class), iterator));
+ }
+
+ private void addColumn(BytecodeCreator creator, AssignableResultHandle springSort, ResultHandle column) {
+ ResultHandle orderArray = creator.newArray(org.springframework.data.domain.Sort.Order.class, 1);
+ setOrder(creator, orderArray, column);
+ ResultHandle subSort = creator.invokeStaticMethod(
+ ofMethod(org.springframework.data.domain.Sort.class, "by", org.springframework.data.domain.Sort.class,
+ org.springframework.data.domain.Sort.Order[].class),
+ orderArray);
+ creator.assign(springSort, creator.invokeVirtualMethod(
+ ofMethod(org.springframework.data.domain.Sort.class, "and", org.springframework.data.domain.Sort.class,
+ org.springframework.data.domain.Sort.class),
+ springSort, subSort));
+ }
+
+ private void setOrder(BytecodeCreator creator, ResultHandle orderArray, ResultHandle column) {
+ ResultHandle columnName = creator.invokeVirtualMethod(
+ ofMethod(PANACHE_COLUMN, "getName", String.class), column);
+ ResultHandle direction = creator.invokeVirtualMethod(
+ ofMethod(PANACHE_COLUMN, "getDirection", PANACHE_DIRECTION), column);
+ BranchResult isAscendingBranch = isAscending(creator, direction);
+ isAscendingBranch.trueBranch()
+ .writeArrayValue(orderArray, 0, isAscendingBranch.trueBranch().invokeStaticMethod(
+ ofMethod(org.springframework.data.domain.Sort.Order.class, "asc",
+ org.springframework.data.domain.Sort.Order.class, String.class),
+ columnName));
+ isAscendingBranch.falseBranch()
+ .writeArrayValue(orderArray, 0, isAscendingBranch.falseBranch().invokeStaticMethod(
+ ofMethod(org.springframework.data.domain.Sort.Order.class, "desc",
+ org.springframework.data.domain.Sort.Order.class, String.class),
+ columnName));
+ }
+
+ private BranchResult isAscending(BytecodeCreator creator, ResultHandle panacheDirection) {
+ ResultHandle ascending = creator.invokeStaticMethod(
+ ofMethod(PANACHE_DIRECTION, "valueOf", PANACHE_DIRECTION, String.class), creator.load("Ascending"));
+ return creator.ifTrue(creator.invokeVirtualMethod(
+ ofMethod(PANACHE_DIRECTION, "equals", boolean.class, Object.class), ascending, panacheDirection));
+ }
+
+}
diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/RepositoryPropertiesProvider.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/RepositoryPropertiesProvider.java
new file mode 100644
index 0000000000000..68e630c90ac4c
--- /dev/null
+++ b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/RepositoryPropertiesProvider.java
@@ -0,0 +1,46 @@
+package io.quarkus.spring.data.rest.deployment;
+
+import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.ADD;
+import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.DELETE;
+import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.GET;
+import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.LIST;
+import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.LIST_ITERABLE;
+import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.LIST_PAGED;
+import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.LIST_SORTED;
+import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.SAVE_LIST;
+import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.UPDATE;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import org.jboss.jandex.DotName;
+import org.jboss.jandex.IndexView;
+import org.jboss.jandex.MethodInfo;
+import org.springframework.data.domain.Pageable;
+
+public class RepositoryPropertiesProvider extends ResourcePropertiesProvider {
+
+ private static final DotName PAGEABLE = DotName.createSimple(Pageable.class.getName());
+
+ public RepositoryPropertiesProvider(IndexView index, boolean paged) {
+ super(index, paged);
+ }
+
+ protected Map> getMethodPredicates() {
+ Map> methodPredicates = new HashMap<>();
+ methodPredicates.put("list", methodInfo -> methodInfo.name().equals(LIST.getName()));
+ methodPredicates.put("listIterable", methodInfo -> methodInfo.name().equals(LIST_ITERABLE.getName()));
+ methodPredicates.put("listPaged", methodInfo -> methodInfo.name().equals(LIST_PAGED.getName())
+ && methodInfo.parametersCount() == 1
+ && methodInfo.parameterType(0).name().equals(PAGEABLE));
+ methodPredicates.put("listSorted",
+ methodInfo -> methodInfo.name().equals(LIST_SORTED.getName()) && methodInfo.parameterTypes().isEmpty());
+ methodPredicates.put("addAll", methodInfo -> methodInfo.name().equals(SAVE_LIST.getName()));
+ methodPredicates.put("get", methodInfo -> methodInfo.name().equals(GET.getName()));
+ methodPredicates.put("add", methodInfo -> methodInfo.name().equals(ADD.getName()));
+ methodPredicates.put("update", methodInfo -> methodInfo.name().equals(UPDATE.getName()));
+ methodPredicates.put("delete", methodInfo -> methodInfo.name().equals(DELETE.getName()));
+ return methodPredicates;
+ }
+}
diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourceImplementor.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourceImplementor.java
index e3a95d8fcfd06..4f0f503378822 100644
--- a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourceImplementor.java
+++ b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourceImplementor.java
@@ -33,7 +33,12 @@ public String implement(ClassOutput classOutput, String resourceType, String ent
.build();
classCreator.addAnnotation(ApplicationScoped.class);
+ methodsImplementor.implementListIterable(classCreator, resourceType);
methodsImplementor.implementList(classCreator, resourceType);
+ methodsImplementor.implementListSort(classCreator, resourceType);
+ methodsImplementor.implementListPaged(classCreator, resourceType);
+ methodsImplementor.implementAddList(classCreator, resourceType);
+ methodsImplementor.implementListById(classCreator, resourceType);
methodsImplementor.implementListPageCount(classCreator, resourceType);
methodsImplementor.implementGet(classCreator, resourceType);
methodsImplementor.implementAdd(classCreator, resourceType);
@@ -41,6 +46,7 @@ public String implement(ClassOutput classOutput, String resourceType, String ent
methodsImplementor.implementDelete(classCreator, resourceType);
classCreator.close();
+
LOGGER.tracef("Completed generation of '%s'", className);
return className;
}
diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourceMethodsImplementor.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourceMethodsImplementor.java
index 6e81dc5b74352..8024240813660 100644
--- a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourceMethodsImplementor.java
+++ b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourceMethodsImplementor.java
@@ -1,18 +1,51 @@
package io.quarkus.spring.data.rest.deployment;
+import static io.quarkus.gizmo.MethodDescriptor.ofMethod;
+
+import java.lang.annotation.Annotation;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.InstanceHandle;
+import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.ClassCreator;
+import io.quarkus.gizmo.ResultHandle;
public interface ResourceMethodsImplementor {
void implementList(ClassCreator classCreator, String repositoryInterface);
+ void implementListIterable(ClassCreator classCreator, String repositoryInterface);
+
+ void implementListPaged(ClassCreator classCreator, String repositoryInterface);
+
void implementListPageCount(ClassCreator classCreator, String repositoryInterface);
+ void implementListById(ClassCreator classCreator, String repositoryInterface);
+
+ public void implementListSort(ClassCreator classCreator, String repositoryInterface);
+
void implementGet(ClassCreator classCreator, String repositoryInterface);
void implementAdd(ClassCreator classCreator, String repositoryInterface);
+ void implementAddList(ClassCreator classCreator, String repositoryInterface);
+
void implementUpdate(ClassCreator classCreator, String repositoryInterface, String entityType);
void implementDelete(ClassCreator classCreator, String repositoryInterface);
+
+ default ResultHandle getRepositoryInstance(BytecodeCreator creator, String repositoryInterface) {
+ ResultHandle arcContainer = creator.invokeStaticMethod(ofMethod(Arc.class, "container", ArcContainer.class));
+ ResultHandle instanceHandle = creator.invokeInterfaceMethod(
+ ofMethod(ArcContainer.class, "instance", InstanceHandle.class, Class.class, Annotation[].class),
+ arcContainer, creator.loadClassFromTCCL(repositoryInterface), creator.newArray(Annotation.class, 0));
+ ResultHandle instance = creator.invokeInterfaceMethod(
+ ofMethod(InstanceHandle.class, "get", Object.class), instanceHandle);
+ creator.ifNull(instance)
+ .trueBranch()
+ .throwException(RuntimeException.class, repositoryInterface + " instance was not found");
+
+ return instance;
+ }
}
diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/SpringDataRestProcessor.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/SpringDataRestProcessor.java
index aaa9c9209b21b..8c6122950b36b 100644
--- a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/SpringDataRestProcessor.java
+++ b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/SpringDataRestProcessor.java
@@ -16,6 +16,8 @@
import org.jboss.jandex.Type;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.ListCrudRepository;
+import org.springframework.data.repository.ListPagingAndSortingRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
@@ -35,25 +37,27 @@
import io.quarkus.rest.data.panache.deployment.properties.ResourcePropertiesBuildItem;
import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem;
import io.quarkus.resteasy.reactive.spi.ExceptionMapperBuildItem;
-import io.quarkus.spring.data.rest.deployment.crud.CrudMethodsImplementor;
-import io.quarkus.spring.data.rest.deployment.crud.CrudPropertiesProvider;
-import io.quarkus.spring.data.rest.deployment.paging.PagingAndSortingMethodsImplementor;
-import io.quarkus.spring.data.rest.deployment.paging.PagingAndSortingPropertiesProvider;
import io.quarkus.spring.data.rest.runtime.RestDataPanacheExceptionMapper;
import io.quarkus.spring.data.rest.runtime.jta.TransactionalUpdateExecutor;
class SpringDataRestProcessor {
private static final DotName CRUD_REPOSITORY_INTERFACE = DotName.createSimple(CrudRepository.class.getName());
+ private static final DotName LIST_CRUD_REPOSITORY_INTERFACE = DotName.createSimple(ListCrudRepository.class.getName());
private static final DotName PAGING_AND_SORTING_REPOSITORY_INTERFACE = DotName
.createSimple(PagingAndSortingRepository.class.getName());
+ private static final DotName LIST_PAGING_AND_SORTING_REPOSITORY_INTERFACE = DotName
+ .createSimple(ListPagingAndSortingRepository.class.getName());
+
private static final DotName JPA_REPOSITORY_INTERFACE = DotName.createSimple(JpaRepository.class.getName());
private static final List EXCLUDED_INTERFACES = Arrays.asList(
CRUD_REPOSITORY_INTERFACE,
+ LIST_CRUD_REPOSITORY_INTERFACE,
PAGING_AND_SORTING_REPOSITORY_INTERFACE,
+ LIST_PAGING_AND_SORTING_REPOSITORY_INTERFACE,
JPA_REPOSITORY_INTERFACE);
@BuildStep
@@ -78,30 +82,22 @@ AdditionalBeanBuildItem registerTransactionalExecutor() {
}
@BuildStep
- void registerCrudRepositories(CombinedIndexBuildItem indexBuildItem, Capabilities capabilities,
- BuildProducer implementationsProducer,
- BuildProducer restDataResourceProducer,
- BuildProducer resourcePropertiesProducer,
- BuildProducer unremovableBeansProducer) {
- IndexView index = indexBuildItem.getIndex();
-
- implementResources(capabilities, implementationsProducer, restDataResourceProducer, resourcePropertiesProducer,
- unremovableBeansProducer, new CrudMethodsImplementor(index), new CrudPropertiesProvider(index),
- getRepositoriesToImplement(index, CRUD_REPOSITORY_INTERFACE));
- }
-
- @BuildStep
- void registerPagingAndSortingRepositories(CombinedIndexBuildItem indexBuildItem, Capabilities capabilities,
+ void registerRepositories(CombinedIndexBuildItem indexBuildItem, Capabilities capabilities,
BuildProducer implementationsProducer,
BuildProducer restDataResourceProducer,
BuildProducer resourcePropertiesProducer,
BuildProducer unremovableBeansProducer) {
IndexView index = indexBuildItem.getIndex();
+ EntityClassHelper entityClassHelper = new EntityClassHelper(index);
+ List repositoriesToImplement = getRepositoriesToImplement(index, CRUD_REPOSITORY_INTERFACE,
+ LIST_CRUD_REPOSITORY_INTERFACE,
+ PAGING_AND_SORTING_REPOSITORY_INTERFACE, LIST_PAGING_AND_SORTING_REPOSITORY_INTERFACE,
+ JPA_REPOSITORY_INTERFACE);
implementResources(capabilities, implementationsProducer, restDataResourceProducer, resourcePropertiesProducer,
- unremovableBeansProducer, new PagingAndSortingMethodsImplementor(index),
- new PagingAndSortingPropertiesProvider(index),
- getRepositoriesToImplement(index, PAGING_AND_SORTING_REPOSITORY_INTERFACE, JPA_REPOSITORY_INTERFACE));
+ unremovableBeansProducer, new RepositoryMethodsImplementor(index, entityClassHelper),
+ index,
+ repositoriesToImplement);
}
/**
@@ -114,11 +110,18 @@ private void implementResources(Capabilities capabilities,
BuildProducer resourcePropertiesProducer,
BuildProducer unremovableBeansProducer,
ResourceMethodsImplementor methodsImplementor,
- ResourcePropertiesProvider propertiesProvider,
+ IndexView index,
+ // ResourcePropertiesProvider propertiesProvider,
List repositoriesToImplement) {
ClassOutput classOutput = new GeneratedBeanGizmoAdaptor(implementationsProducer);
ResourceImplementor resourceImplementor = new ResourceImplementor(methodsImplementor);
+ EntityClassHelper entityClassHelper = new EntityClassHelper(index);
for (ClassInfo classInfo : repositoriesToImplement) {
+ boolean paged = false;
+ if (entityClassHelper.isPagingAndSortingRepository(classInfo.name().toString())) {
+ paged = true;
+ }
+ ResourcePropertiesProvider propertiesProvider = new RepositoryPropertiesProvider(index, paged);
List generics = getGenericTypes(classInfo);
String repositoryName = classInfo.name().toString();
String entityType = generics.get(0).toString();
diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/crud/CrudMethodsImplementor.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/crud/CrudMethodsImplementor.java
deleted file mode 100644
index 0af60b943c288..0000000000000
--- a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/crud/CrudMethodsImplementor.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package io.quarkus.spring.data.rest.deployment.crud;
-
-import static io.quarkus.gizmo.MethodDescriptor.ofMethod;
-
-import java.lang.annotation.Annotation;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-import org.jboss.jandex.FieldInfo;
-import org.jboss.jandex.IndexView;
-import org.springframework.data.repository.CrudRepository;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.ArcContainer;
-import io.quarkus.arc.InstanceHandle;
-import io.quarkus.gizmo.AssignableResultHandle;
-import io.quarkus.gizmo.BranchResult;
-import io.quarkus.gizmo.BytecodeCreator;
-import io.quarkus.gizmo.ClassCreator;
-import io.quarkus.gizmo.MethodCreator;
-import io.quarkus.gizmo.MethodDescriptor;
-import io.quarkus.gizmo.ResultHandle;
-import io.quarkus.panache.common.Page;
-import io.quarkus.panache.common.Sort;
-import io.quarkus.rest.data.panache.deployment.Constants;
-import io.quarkus.spring.data.rest.deployment.ResourceMethodsImplementor;
-
-public class CrudMethodsImplementor implements ResourceMethodsImplementor {
-
- public static final MethodDescriptor LIST = ofMethod(CrudRepository.class, "findAll", Iterable.class);
-
- public static final MethodDescriptor GET = ofMethod(CrudRepository.class, "findById", Optional.class, Object.class);
-
- public static final MethodDescriptor ADD = ofMethod(CrudRepository.class, "save", Object.class, Object.class);
-
- public static final MethodDescriptor UPDATE = ofMethod(CrudRepository.class, "save", Object.class, Object.class);
-
- public static final MethodDescriptor DELETE = ofMethod(CrudRepository.class, "deleteById", void.class, Object.class);
-
- private final EntityClassHelper entityClassHelper;
-
- public CrudMethodsImplementor(IndexView index) {
- this.entityClassHelper = new EntityClassHelper(index);
- }
-
- public void implementList(ClassCreator classCreator, String repositoryInterface) {
- MethodCreator methodCreator = classCreator.getMethodCreator("list", List.class, Page.class, Sort.class,
- String.class, Map.class);
-
- ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterface);
- ResultHandle result = methodCreator.invokeInterfaceMethod(LIST, repository);
-
- methodCreator.returnValue(result);
- methodCreator.close();
- }
-
- public void implementListPageCount(ClassCreator classCreator, String repositoryInterface) {
- MethodCreator methodCreator = classCreator.getMethodCreator(Constants.PAGE_COUNT_METHOD_PREFIX + "list",
- int.class, Page.class);
- methodCreator.throwException(RuntimeException.class, "Method not implemented");
- methodCreator.close();
- }
-
- public void implementGet(ClassCreator classCreator, String repositoryInterface) {
- MethodCreator methodCreator = classCreator.getMethodCreator("get", Object.class, Object.class);
-
- ResultHandle id = methodCreator.getMethodParam(0);
- ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterface);
- ResultHandle result = findById(methodCreator, id, repository);
-
- methodCreator.returnValue(result);
- methodCreator.close();
- }
-
- public void implementAdd(ClassCreator classCreator, String repositoryInterface) {
- MethodCreator methodCreator = classCreator.getMethodCreator("add", Object.class, Object.class);
-
- ResultHandle entity = methodCreator.getMethodParam(0);
- ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterface);
- ResultHandle result = methodCreator.invokeInterfaceMethod(ADD, repository, entity);
-
- methodCreator.returnValue(result);
- methodCreator.close();
- }
-
- public void implementUpdate(ClassCreator classCreator, String repositoryInterface, String entityType) {
- MethodCreator methodCreator = classCreator.getMethodCreator("update", Object.class, Object.class, Object.class);
-
- ResultHandle id = methodCreator.getMethodParam(0);
- ResultHandle entity = methodCreator.getMethodParam(1);
- // Set entity ID before executing an update to make sure that a requested object ID matches a given entity ID.
- setId(methodCreator, entityType, entity, id);
- ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterface);
- ResultHandle result = methodCreator.invokeInterfaceMethod(UPDATE, repository, entity);
-
- methodCreator.returnValue(result);
- methodCreator.close();
- }
-
- public void implementDelete(ClassCreator classCreator, String repositoryInterface) {
- MethodCreator methodCreator = classCreator.getMethodCreator("delete", boolean.class, Object.class);
-
- ResultHandle id = methodCreator.getMethodParam(0);
- ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterface);
- ResultHandle entity = findById(methodCreator, id, repository);
- AssignableResultHandle result = methodCreator.createVariable(boolean.class);
- BranchResult entityExists = methodCreator.ifNotNull(entity);
- entityExists.trueBranch().invokeInterfaceMethod(DELETE, repository, id);
- entityExists.trueBranch().assign(result, entityExists.trueBranch().load(true));
- entityExists.falseBranch().assign(result, entityExists.falseBranch().load(false));
-
- methodCreator.returnValue(result);
- methodCreator.close();
- }
-
- private ResultHandle findById(BytecodeCreator creator, ResultHandle id, ResultHandle repository) {
- ResultHandle optional = creator.invokeInterfaceMethod(GET, repository, id);
- return creator.invokeVirtualMethod(ofMethod(Optional.class, "orElse", Object.class, Object.class),
- optional, creator.loadNull());
- }
-
- private void setId(BytecodeCreator creator, String entityType, ResultHandle entity, ResultHandle id) {
- FieldInfo idField = entityClassHelper.getIdField(entityType);
- MethodDescriptor idSetter = entityClassHelper.getSetter(entityType, idField);
- creator.invokeVirtualMethod(idSetter, entity, id);
- }
-
- protected ResultHandle getRepositoryInstance(BytecodeCreator creator, String repositoryInterface) {
- ResultHandle arcContainer = creator.invokeStaticMethod(ofMethod(Arc.class, "container", ArcContainer.class));
- ResultHandle instanceHandle = creator.invokeInterfaceMethod(
- ofMethod(ArcContainer.class, "instance", InstanceHandle.class, Class.class, Annotation[].class),
- arcContainer, creator.loadClassFromTCCL(repositoryInterface), creator.newArray(Annotation.class, 0));
- ResultHandle instance = creator.invokeInterfaceMethod(
- ofMethod(InstanceHandle.class, "get", Object.class), instanceHandle);
- creator.ifNull(instance)
- .trueBranch()
- .throwException(RuntimeException.class, repositoryInterface + " instance was not found");
-
- return instance;
- }
-}
diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/crud/CrudPropertiesProvider.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/crud/CrudPropertiesProvider.java
deleted file mode 100644
index 19652d2a38af5..0000000000000
--- a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/crud/CrudPropertiesProvider.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package io.quarkus.spring.data.rest.deployment.crud;
-
-import static io.quarkus.spring.data.rest.deployment.crud.CrudMethodsImplementor.ADD;
-import static io.quarkus.spring.data.rest.deployment.crud.CrudMethodsImplementor.DELETE;
-import static io.quarkus.spring.data.rest.deployment.crud.CrudMethodsImplementor.GET;
-import static io.quarkus.spring.data.rest.deployment.crud.CrudMethodsImplementor.LIST;
-import static io.quarkus.spring.data.rest.deployment.crud.CrudMethodsImplementor.UPDATE;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Predicate;
-
-import org.jboss.jandex.IndexView;
-import org.jboss.jandex.MethodInfo;
-
-import io.quarkus.spring.data.rest.deployment.ResourcePropertiesProvider;
-
-public class CrudPropertiesProvider extends ResourcePropertiesProvider {
-
- public CrudPropertiesProvider(IndexView index) {
- super(index, false);
- }
-
- protected Map> getMethodPredicates() {
- Map> methodPredicates = new HashMap<>();
- methodPredicates.put("list",
- methodInfo -> methodInfo.name().equals(LIST.getName()) && methodInfo.parameterTypes().isEmpty());
- methodPredicates.put("get", methodInfo -> methodInfo.name().equals(GET.getName()));
- methodPredicates.put("add", methodInfo -> methodInfo.name().equals(ADD.getName()));
- methodPredicates.put("update", methodInfo -> methodInfo.name().equals(UPDATE.getName()));
- methodPredicates.put("delete", methodInfo -> methodInfo.name().equals(DELETE.getName()));
- return methodPredicates;
- }
-}
diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/crud/EntityClassHelper.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/crud/EntityClassHelper.java
deleted file mode 100644
index a1e1dddf00991..0000000000000
--- a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/crud/EntityClassHelper.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package io.quarkus.spring.data.rest.deployment.crud;
-
-import jakarta.persistence.Id;
-
-import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
-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 org.jboss.jandex.Type;
-
-import io.quarkus.deployment.bean.JavaBeanUtil;
-import io.quarkus.gizmo.MethodDescriptor;
-
-class EntityClassHelper {
-
- private final IndexView index;
-
- EntityClassHelper(IndexView index) {
- this.index = index;
- }
-
- FieldInfo getIdField(String className) {
- return getIdField(index.getClassByName(DotName.createSimple(className)));
- }
-
- 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 (tmpClassInfo.superName() != null) {
- tmpClassInfo = index.getClassByName(tmpClassInfo.superName());
- } else {
- tmpClassInfo = null;
- }
- }
- throw new IllegalArgumentException("Couldn't find id field of " + classInfo);
- }
-
- MethodDescriptor getSetter(String className, FieldInfo field) {
- return getSetter(index.getClassByName(DotName.createSimple(className)), field);
- }
-
- private MethodDescriptor getSetter(ClassInfo entityClass, FieldInfo field) {
- MethodDescriptor setter = getMethod(entityClass, JavaBeanUtil.getSetterName(field.name()), field.type());
- if (setter != null) {
- return setter;
- }
- return MethodDescriptor.ofMethod(entityClass.toString(),
- EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + field.name(), void.class, field.type().name().toString());
- }
-
- private MethodDescriptor getMethod(ClassInfo entityClass, String name, Type... parameters) {
- if (entityClass == null) {
- return null;
- }
- MethodInfo methodInfo = entityClass.method(name, parameters);
- if (methodInfo != null) {
- return MethodDescriptor.of(methodInfo);
- } else if (entityClass.superName() != null) {
- return getMethod(index.getClassByName(entityClass.superName()), name, parameters);
- }
- return null;
- }
-}
diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/paging/PagingAndSortingMethodsImplementor.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/paging/PagingAndSortingMethodsImplementor.java
deleted file mode 100644
index 089d6fb2b7351..0000000000000
--- a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/paging/PagingAndSortingMethodsImplementor.java
+++ /dev/null
@@ -1,182 +0,0 @@
-package io.quarkus.spring.data.rest.deployment.paging;
-
-import static io.quarkus.gizmo.FieldDescriptor.of;
-import static io.quarkus.gizmo.MethodDescriptor.ofMethod;
-
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.jboss.jandex.IndexView;
-import org.springframework.data.domain.PageRequest;
-import org.springframework.data.domain.Pageable;
-import org.springframework.data.domain.Sort;
-import org.springframework.data.repository.PagingAndSortingRepository;
-
-import io.quarkus.gizmo.AssignableResultHandle;
-import io.quarkus.gizmo.BranchResult;
-import io.quarkus.gizmo.BytecodeCreator;
-import io.quarkus.gizmo.ClassCreator;
-import io.quarkus.gizmo.MethodCreator;
-import io.quarkus.gizmo.MethodDescriptor;
-import io.quarkus.gizmo.ResultHandle;
-import io.quarkus.panache.common.Page;
-import io.quarkus.rest.data.panache.deployment.Constants;
-import io.quarkus.spring.data.rest.deployment.crud.CrudMethodsImplementor;
-
-public class PagingAndSortingMethodsImplementor extends CrudMethodsImplementor {
-
- public static final MethodDescriptor LIST_PAGED = ofMethod(PagingAndSortingRepository.class, "findAll",
- org.springframework.data.domain.Page.class, Pageable.class);
-
- private static final Class> PANACHE_PAGE = io.quarkus.panache.common.Page.class;
-
- private static final Class> PANACHE_SORT = io.quarkus.panache.common.Sort.class;
-
- private static final Class> PANACHE_COLUMN = io.quarkus.panache.common.Sort.Column.class;
-
- private static final Class> PANACHE_DIRECTION = io.quarkus.panache.common.Sort.Direction.class;
-
- public PagingAndSortingMethodsImplementor(IndexView index) {
- super(index);
- }
-
- public void implementList(ClassCreator classCreator, String repositoryInterface) {
- MethodCreator methodCreator = classCreator.getMethodCreator("list", List.class, Page.class,
- io.quarkus.panache.common.Sort.class, String.class, Map.class);
-
- ResultHandle page = methodCreator.getMethodParam(0);
- ResultHandle sort = methodCreator.getMethodParam(1);
- ResultHandle pageable = toPageable(methodCreator, page, sort);
- ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterface);
- ResultHandle resultPage = methodCreator.invokeInterfaceMethod(LIST_PAGED, repository, pageable);
- ResultHandle result = methodCreator.invokeInterfaceMethod(
- ofMethod(org.springframework.data.domain.Page.class, "getContent", List.class), resultPage);
-
- methodCreator.returnValue(result);
- methodCreator.close();
- }
-
- public void implementListPageCount(ClassCreator classCreator, String repositoryInterface) {
- MethodCreator methodCreator = classCreator.getMethodCreator(Constants.PAGE_COUNT_METHOD_PREFIX + "list",
- int.class, Page.class);
- ResultHandle page = methodCreator.getMethodParam(0);
- ResultHandle pageable = toPageable(methodCreator, page);
- ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterface);
- ResultHandle resultPage = methodCreator.invokeInterfaceMethod(LIST_PAGED, repository, pageable);
- ResultHandle pageCount = methodCreator.invokeInterfaceMethod(
- ofMethod(org.springframework.data.domain.Page.class, "getTotalPages", int.class), resultPage);
-
- methodCreator.returnValue(pageCount);
- methodCreator.close();
- }
-
- /**
- *
- * Pageable toPageable(Page panachePage, io.quarkus.panache.common.Sort panacheSort) {
- * Sort springSort = toSpringSort(panacheSort);
- * return PageRequest.of(panachePage.index, panachePage.size, springSort);
- * }
- *
- */
- private ResultHandle toPageable(MethodCreator creator, ResultHandle panachePage, ResultHandle panacheSort) {
- ResultHandle index = creator.readInstanceField(of(PANACHE_PAGE, "index", int.class), panachePage);
- ResultHandle size = creator.readInstanceField(of(PANACHE_PAGE, "size", int.class), panachePage);
- ResultHandle springSort = toSpringSort(creator, panacheSort);
- return creator.invokeStaticMethod(
- ofMethod(PageRequest.class, "of", PageRequest.class, int.class, int.class, Sort.class),
- index, size, springSort);
- }
-
- /**
- *
- * Pageable toPageable(Page panachePage) {
- * return PageRequest.of(panachePage.index, panachePage.size);
- * }
- *
- */
- private ResultHandle toPageable(MethodCreator creator, ResultHandle panachePage) {
- ResultHandle index = creator.readInstanceField(of(PANACHE_PAGE, "index", int.class), panachePage);
- ResultHandle size = creator.readInstanceField(of(PANACHE_PAGE, "size", int.class), panachePage);
- return creator.invokeStaticMethod(
- ofMethod(PageRequest.class, "of", PageRequest.class, int.class, int.class), index, size);
- }
-
- /**
- *
- * Sort toSpringSort(io.quarkus.panache.common.Sort sort) {
- * Sort springSort;
- * springSort = Sort.unsorted();
- * List columns = sort.getColumns();
- * Iterator columnsIterator = columns.iterator();
- * while (columnsIterator.hasNext()) {
- * io.quarkus.panache.common.Sort.Column column = columnsIterator.next();
- * Sort.Order[] orderArray = new Sort.Order[1];
- * String columnName = column.getName();
- * io.quarkus.panache.common.Sort.Direction direction = column.getDirection();
- * io.quarkus.panache.common.Sort.Direction ascending = io.quarkus.panache.common.Sort.Direction
- * .valueOf("Ascending");
- * if (ascending.equals(direction)) {
- * orderArray[0] = Sort.Order.asc(columnName);
- * } else {
- * orderArray[0] = Sort.Order.desc(columnName);
- * }
- * Sort subSort = Sort.by(orderArray);
- * springSort = subSort.and(subSort);
- * }
- * return springSort;
- * }
- *
- */
- private ResultHandle toSpringSort(MethodCreator creator, ResultHandle panacheSort) {
- AssignableResultHandle springSort = creator.createVariable(Sort.class);
- creator.assign(springSort, creator.invokeStaticMethod(ofMethod(Sort.class, "unsorted", Sort.class)));
-
- // Loop through the columns
- ResultHandle columns = creator.invokeVirtualMethod(
- ofMethod(PANACHE_SORT, "getColumns", List.class), panacheSort);
- ResultHandle columnsIterator = creator.invokeInterfaceMethod(
- ofMethod(List.class, "iterator", Iterator.class), columns);
- BytecodeCreator loopCreator = creator.whileLoop(c -> iteratorHasNext(c, columnsIterator)).block();
- ResultHandle column = loopCreator.invokeInterfaceMethod(
- ofMethod(Iterator.class, "next", Object.class), columnsIterator);
- addColumn(loopCreator, springSort, column);
-
- return springSort;
- }
-
- private BranchResult iteratorHasNext(BytecodeCreator creator, ResultHandle iterator) {
- return creator.ifTrue(
- creator.invokeInterfaceMethod(ofMethod(Iterator.class, "hasNext", boolean.class), iterator));
- }
-
- private void addColumn(BytecodeCreator creator, AssignableResultHandle springSort, ResultHandle column) {
- ResultHandle orderArray = creator.newArray(Sort.Order.class, 1);
- setOrder(creator, orderArray, column);
- ResultHandle subSort = creator.invokeStaticMethod(
- ofMethod(Sort.class, "by", Sort.class, Sort.Order[].class), orderArray);
- creator.assign(springSort, creator.invokeVirtualMethod(
- ofMethod(Sort.class, "and", Sort.class, Sort.class), springSort, subSort));
- }
-
- private void setOrder(BytecodeCreator creator, ResultHandle orderArray, ResultHandle column) {
- ResultHandle columnName = creator.invokeVirtualMethod(
- ofMethod(PANACHE_COLUMN, "getName", String.class), column);
- ResultHandle direction = creator.invokeVirtualMethod(
- ofMethod(PANACHE_COLUMN, "getDirection", PANACHE_DIRECTION), column);
- BranchResult isAscendingBranch = isAscending(creator, direction);
- isAscendingBranch.trueBranch()
- .writeArrayValue(orderArray, 0, isAscendingBranch.trueBranch().invokeStaticMethod(
- ofMethod(Sort.Order.class, "asc", Sort.Order.class, String.class), columnName));
- isAscendingBranch.falseBranch()
- .writeArrayValue(orderArray, 0, isAscendingBranch.falseBranch().invokeStaticMethod(
- ofMethod(Sort.Order.class, "desc", Sort.Order.class, String.class), columnName));
- }
-
- private BranchResult isAscending(BytecodeCreator creator, ResultHandle panacheDirection) {
- ResultHandle ascending = creator.invokeStaticMethod(
- ofMethod(PANACHE_DIRECTION, "valueOf", PANACHE_DIRECTION, String.class), creator.load("Ascending"));
- return creator.ifTrue(creator.invokeVirtualMethod(
- ofMethod(PANACHE_DIRECTION, "equals", boolean.class, Object.class), ascending, panacheDirection));
- }
-}
diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/paging/PagingAndSortingPropertiesProvider.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/paging/PagingAndSortingPropertiesProvider.java
deleted file mode 100644
index c72c8cbace296..0000000000000
--- a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/paging/PagingAndSortingPropertiesProvider.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package io.quarkus.spring.data.rest.deployment.paging;
-
-import static io.quarkus.spring.data.rest.deployment.crud.CrudMethodsImplementor.ADD;
-import static io.quarkus.spring.data.rest.deployment.crud.CrudMethodsImplementor.DELETE;
-import static io.quarkus.spring.data.rest.deployment.crud.CrudMethodsImplementor.GET;
-import static io.quarkus.spring.data.rest.deployment.crud.CrudMethodsImplementor.UPDATE;
-import static io.quarkus.spring.data.rest.deployment.paging.PagingAndSortingMethodsImplementor.LIST_PAGED;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Predicate;
-
-import org.jboss.jandex.DotName;
-import org.jboss.jandex.IndexView;
-import org.jboss.jandex.MethodInfo;
-import org.springframework.data.domain.Pageable;
-
-import io.quarkus.spring.data.rest.deployment.ResourcePropertiesProvider;
-
-public class PagingAndSortingPropertiesProvider extends ResourcePropertiesProvider {
-
- private static final DotName PAGEABLE = DotName.createSimple(Pageable.class.getName());
-
- public PagingAndSortingPropertiesProvider(IndexView index) {
- super(index, true);
- }
-
- protected Map> getMethodPredicates() {
- Map> methodPredicates = new HashMap<>();
- methodPredicates.put("list", methodInfo -> methodInfo.name().equals(LIST_PAGED.getName())
- && methodInfo.parametersCount() == 1
- && methodInfo.parameterType(0).name().equals(PAGEABLE));
- methodPredicates.put("get", methodInfo -> methodInfo.name().equals(GET.getName()));
- methodPredicates.put("add", methodInfo -> methodInfo.name().equals(ADD.getName()));
- methodPredicates.put("update", methodInfo -> methodInfo.name().equals(UPDATE.getName()));
- methodPredicates.put("delete", methodInfo -> methodInfo.name().equals(DELETE.getName()));
- return methodPredicates;
- }
-}
diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/AbstractEntity.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/AbstractEntity.java
similarity index 89%
rename from extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/AbstractEntity.java
rename to extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/AbstractEntity.java
index 85b06c2d21ccf..8608d7aad001d 100644
--- a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/AbstractEntity.java
+++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/AbstractEntity.java
@@ -1,4 +1,4 @@
-package io.quarkus.spring.data.rest.crud;
+package io.quarkus.spring.data.rest;
import java.io.Serializable;
diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/CrudAndPagedRecordsRepository.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/CrudAndPagedRecordsRepository.java
new file mode 100644
index 0000000000000..6adef240059e1
--- /dev/null
+++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/CrudAndPagedRecordsRepository.java
@@ -0,0 +1,9 @@
+package io.quarkus.spring.data.rest;
+
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.PagingAndSortingRepository;
+
+import io.quarkus.spring.data.rest.paged.Record;
+
+public interface CrudAndPagedRecordsRepository extends PagingAndSortingRepository, CrudRepository {
+}
diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/CrudAndPagedResourceTest.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/CrudAndPagedResourceTest.java
new file mode 100644
index 0000000000000..b0e69ad9c3128
--- /dev/null
+++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/CrudAndPagedResourceTest.java
@@ -0,0 +1,437 @@
+package io.quarkus.spring.data.rest;
+
+import static io.restassured.RestAssured.given;
+import static io.restassured.RestAssured.when;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.is;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import jakarta.ws.rs.core.Link;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.spring.data.rest.paged.Record;
+import io.quarkus.test.QuarkusUnitTest;
+import io.restassured.http.Header;
+import io.restassured.http.Headers;
+import io.restassured.path.json.JsonPath;
+import io.restassured.response.Response;
+
+class CrudAndPagedResourceTest {
+ @RegisterExtension
+ static final QuarkusUnitTest TEST = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(AbstractEntity.class, Record.class, CrudAndPagedRecordsRepository.class)
+ .addAsResource("application.properties")
+ .addAsResource("import.sql"));
+
+ @Test
+ void shouldGet() {
+ given().accept("application/json")
+ .when().get("/crud-and-paged-records/1")
+ .then().statusCode(200)
+ .and().body("id", is(equalTo(1)))
+ .and().body("name", is(equalTo("first")));
+ }
+
+ @Test
+ void shouldNotGetNonExistent() {
+ given().accept("application/json")
+ .when().get("/crud-and-paged-records/1000")
+ .then().statusCode(404);
+ }
+
+ @Test
+ void shouldGetHal() {
+ given().accept("application/hal+json")
+ .when().get("/crud-and-paged-records/1")
+ .then().statusCode(200)
+ .and().body("id", is(equalTo(1)))
+ .and().body("name", is(equalTo("first")))
+ .and().body("_links.add.href", endsWith("/crud-and-paged-records"))
+ .and().body("_links.list.href", endsWith("/crud-and-paged-records"))
+ .and().body("_links.self.href", endsWith("/crud-and-paged-records/1"))
+ .and().body("_links.update.href", endsWith("/crud-and-paged-records/1"))
+ .and().body("_links.remove.href", endsWith("/crud-and-paged-records/1"));
+ }
+
+ @Test
+ void shouldNotGetNonExistentHal() {
+ given().accept("application/hal+json")
+ .when().get("/crud-and-paged-records/1000")
+ .then().statusCode(404);
+ }
+
+ @Test
+ void shouldList() {
+ Response response = given().accept("application/json")
+ .when().get("/crud-and-paged-records")
+ .thenReturn();
+
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.body().jsonPath().getList("id")).contains(1, 2);
+ assertThat(response.body().jsonPath().getList("name")).contains("first", "second");
+
+ Map expectedLinks = new HashMap<>(2);
+ expectedLinks.put("first", "/crud-and-paged-records?page=0&size=20");
+ expectedLinks.put("last", "/crud-and-paged-records?page=0&size=20");
+ assertLinks(response.headers(), expectedLinks);
+ }
+
+ @Test
+ void shouldListHal() {
+ given().accept("application/hal+json")
+ .when().get("/crud-and-paged-records")
+ .then().statusCode(200).log().all()
+ .and().body("_embedded.crud-and-paged-records.id", hasItems(1, 2))
+ .and().body("_embedded.crud-and-paged-records.name", hasItems("first", "second"))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.add.href",
+ hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records")))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.list.href",
+ hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records")))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.self.href",
+ hasItems(endsWith("/crud-and-paged-records/1"), endsWith("/crud-and-paged-records/2")))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.update.href",
+ hasItems(endsWith("/crud-and-paged-records/1"), endsWith("/crud-and-paged-records/2")))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.remove.href",
+ hasItems(endsWith("/crud-and-paged-records/1"), endsWith("/crud-and-paged-records/2")))
+ .and().body("_links.add.href", endsWith("/crud-and-paged-records"))
+ .and().body("_links.list.href", endsWith("/crud-and-paged-records"))
+ .and().body("_links.first.href", endsWith("/crud-and-paged-records?page=0&size=20"))
+ .and().body("_links.last.href", endsWith("/crud-and-paged-records?page=0&size=20"));
+ }
+
+ @Test
+ void shouldListFirstPage() {
+ Response initResponse = given().accept("application/json")
+ .when().get("/crud-and-paged-records")
+ .thenReturn();
+ List ids = initResponse.body().jsonPath().getList("id");
+ List names = initResponse.body().jsonPath().getList("name");
+ int lastPage = ids.size() - 1;
+
+ Response response = given().accept("application/json")
+ .and().queryParam("page", 0)
+ .and().queryParam("size", 1)
+ .when().get("/crud-and-paged-records")
+ .thenReturn();
+
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.body().jsonPath().getList("id")).containsOnly(ids.get(0));
+ assertThat(response.body().jsonPath().getList("name")).containsOnly(names.get(0));
+
+ Map expectedLinks = new HashMap<>(3);
+ expectedLinks.put("first", "/crud-and-paged-records?page=0&size=1");
+ expectedLinks.put("last", "/crud-and-paged-records?page=" + lastPage + "&size=1");
+ expectedLinks.put("next", "/crud-and-paged-records?page=1&size=1");
+ assertLinks(response.headers(), expectedLinks);
+ }
+
+ @Test
+ void shouldListFirstPageHal() {
+ Response initResponse = given().accept("application/json")
+ .when().get("/crud-and-paged-records")
+ .thenReturn();
+ List ids = initResponse.body().jsonPath().getList("id");
+ List names = initResponse.body().jsonPath().getList("name");
+ int lastPage = ids.size() - 1;
+
+ given().accept("application/hal+json")
+ .and().queryParam("page", 0)
+ .and().queryParam("size", 1)
+ .when().get("/crud-and-paged-records")
+ .then().statusCode(200)
+ .and().body("_embedded.crud-and-paged-records.id", contains(ids.get(0)))
+ .and().body("_embedded.crud-and-paged-records.name", contains(names.get(0)))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.add.href",
+ hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records")))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.list.href",
+ hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records")))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.self.href",
+ contains(endsWith("/crud-and-paged-records/" + ids.get(0))))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.update.href",
+ contains(endsWith("/crud-and-paged-records/" + ids.get(0))))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.remove.href",
+ contains(endsWith("/crud-and-paged-records/" + ids.get(0))))
+ .and().body("_links.add.href", endsWith("/crud-and-paged-records"))
+ .and().body("_links.list.href", endsWith("/crud-and-paged-records"))
+ .and().body("_links.first.href", endsWith("/crud-and-paged-records?page=0&size=1"))
+ .and().body("_links.last.href", endsWith("/crud-and-paged-records?page=" + lastPage + "&size=1"))
+ .and().body("_links.next.href", endsWith("/crud-and-paged-records?page=1&size=1"));
+ }
+
+ @Test
+ void shouldListLastPage() {
+ Response initResponse = given().accept("application/json")
+ .when().get("/crud-and-paged-records")
+ .thenReturn();
+ List ids = initResponse.body().jsonPath().getList("id");
+ List names = initResponse.body().jsonPath().getList("name");
+ int lastPage = ids.size() - 1;
+
+ Response response = given().accept("application/json")
+ .and().queryParam("page", lastPage)
+ .and().queryParam("size", 1)
+ .when().get("/crud-and-paged-records")
+ .thenReturn();
+
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.body().jsonPath().getList("id")).containsOnly(ids.get(lastPage));
+ assertThat(response.body().jsonPath().getList("name")).containsOnly(names.get(lastPage));
+
+ Map expectedLinks = new HashMap<>(3);
+ expectedLinks.put("first", "/crud-and-paged-records?page=0&size=1");
+ expectedLinks.put("last", "/crud-and-paged-records?page=" + lastPage + "&size=1");
+ expectedLinks.put("previous", "/crud-and-paged-records?page=" + (lastPage - 1) + "&size=1");
+ assertLinks(response.headers(), expectedLinks);
+ }
+
+ @Test
+ void shouldListLastPageHal() {
+ Response initResponse = given().accept("application/json")
+ .when().get("/crud-and-paged-records")
+ .thenReturn();
+ List ids = initResponse.body().jsonPath().getList("id");
+ List names = initResponse.body().jsonPath().getList("name");
+ int lastPage = ids.size() - 1;
+
+ given().accept("application/hal+json")
+ .and().queryParam("page", lastPage)
+ .and().queryParam("size", 1)
+ .when().get("/crud-and-paged-records")
+ .then().statusCode(200)
+ .and().body("_embedded.crud-and-paged-records.id", contains(ids.get(lastPage)))
+ .and().body("_embedded.crud-and-paged-records.name", contains(names.get(lastPage)))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.add.href",
+ hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records")))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.list.href",
+ hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records")))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.self.href",
+ contains(endsWith("/crud-and-paged-records/" + ids.get(lastPage))))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.update.href",
+ contains(endsWith("/crud-and-paged-records/" + ids.get(lastPage))))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.remove.href",
+ contains(endsWith("/crud-and-paged-records/" + ids.get(lastPage))))
+ .and().body("_links.add.href", endsWith("/crud-and-paged-records"))
+ .and().body("_links.list.href", endsWith("/crud-and-paged-records"))
+ .and().body("_links.first.href", endsWith("/crud-and-paged-records?page=0&size=1"))
+ .and().body("_links.last.href", endsWith("/crud-and-paged-records?page=" + lastPage + "&size=1"))
+ .and().body("_links.previous.href", endsWith("/crud-and-paged-records?page=" + (lastPage - 1) + "&size=1"));
+ }
+
+ @Test
+ void shouldNotGetNonExistentPage() {
+ given().accept("application/json")
+ .and().queryParam("page", 100)
+ .when().get("/crud-and-paged-records")
+ .then().statusCode(200)
+ .and().body("id", is(empty()));
+ }
+
+ @Test
+ void shouldNotGetNegativePageOrSize() {
+ given().accept("application/json")
+ .and().queryParam("page", -1)
+ .and().queryParam("size", -1)
+ .when().get("/crud-and-paged-records")
+ .then().statusCode(200)
+ // Invalid page and size parameters are replaced with defaults
+ .and().body("id", hasItems(1, 2));
+ }
+
+ @Test
+ void shouldListAscending() {
+ Response response = given().accept("application/json")
+ .when().get("/crud-and-paged-records?sort=name,id")
+ .thenReturn();
+
+ List actualNames = response.body().jsonPath().getList("name");
+ List expectedNames = new LinkedList<>(actualNames);
+ expectedNames.sort(Comparator.naturalOrder());
+ assertThat(actualNames).isEqualTo(expectedNames);
+ }
+
+ @Test
+ void shouldListDescending() {
+ Response response = given().accept("application/json")
+ .when().get("/crud-and-paged-records?sort=-name,id")
+ .thenReturn();
+
+ List actualNames = response.body().jsonPath().getList("name");
+ List expectedNames = new LinkedList<>(actualNames);
+ expectedNames.sort(Comparator.reverseOrder());
+ assertThat(actualNames).isEqualTo(expectedNames);
+ }
+
+ @Test
+ void shouldCreate() {
+ Response response = given().accept("application/json")
+ .and().contentType("application/json")
+ .and().body("{\"name\": \"test-create\"}")
+ .when().post("/crud-and-paged-records")
+ .thenReturn();
+ assertThat(response.statusCode()).isEqualTo(201);
+
+ String location = response.header("Location");
+ int id = Integer.parseInt(location.substring(response.header("Location").lastIndexOf("/") + 1));
+ JsonPath body = response.body().jsonPath();
+ assertThat(body.getInt("id")).isEqualTo(id);
+ assertThat(body.getString("name")).isEqualTo("test-create");
+
+ given().accept("application/json")
+ .when().get(location)
+ .then().statusCode(200)
+ .and().body("id", is(equalTo(id)))
+ .and().body("name", is(equalTo("test-create")));
+ }
+
+ @Test
+ void shouldCreateHal() {
+ Response response = given().accept("application/hal+json")
+ .and().contentType("application/json")
+ .and().body("{\"name\": \"test-create-hal\"}")
+ .when().post("/crud-and-paged-records")
+ .thenReturn();
+ assertThat(response.statusCode()).isEqualTo(201);
+
+ String location = response.header("Location");
+ int id = Integer.parseInt(location.substring(response.header("Location").lastIndexOf("/") + 1));
+ JsonPath body = response.body().jsonPath();
+ assertThat(body.getInt("id")).isEqualTo(id);
+ assertThat(body.getString("name")).isEqualTo("test-create-hal");
+ assertThat(body.getString("_links.add.href")).endsWith("/crud-and-paged-records");
+ assertThat(body.getString("_links.list.href")).endsWith("/crud-and-paged-records");
+ assertThat(body.getString("_links.self.href")).endsWith("/crud-and-paged-records/" + id);
+ assertThat(body.getString("_links.update.href")).endsWith("/crud-and-paged-records/" + id);
+ assertThat(body.getString("_links.remove.href")).endsWith("/crud-and-paged-records/" + id);
+
+ given().accept("application/json")
+ .when().get(location)
+ .then().statusCode(200)
+ .and().body("id", is(equalTo(id)))
+ .and().body("name", is(equalTo("test-create-hal")));
+ }
+
+ @Test
+ void shouldCreateAndUpdate() {
+ Response createResponse = given().accept("application/json")
+ .and().contentType("application/json")
+ .and().body("{\"id\": \"101\", \"name\": \"test-update-create\"}")
+ .when().put("/crud-and-paged-records/101")
+ .thenReturn();
+ assertThat(createResponse.statusCode()).isEqualTo(201);
+
+ String location = createResponse.header("Location");
+ int id = Integer.parseInt(location.substring(createResponse.header("Location").lastIndexOf("/") + 1));
+ JsonPath body = createResponse.body().jsonPath();
+ assertThat(body.getInt("id")).isEqualTo(id);
+ assertThat(body.getString("name")).isEqualTo("test-update-create");
+
+ given().accept("application/json")
+ .and().contentType("application/json")
+ .and().body("{\"id\": \"" + id + "\", \"name\": \"test-update\"}")
+ .when().put(location)
+ .then()
+ .statusCode(204);
+ given().accept("application/json")
+ .when().get(location)
+ .then().statusCode(200)
+ .and().body("id", is(equalTo(id)))
+ .and().body("name", is(equalTo("test-update")));
+ }
+
+ @Test
+ void shouldCreateAndUpdateHal() {
+ Response createResponse = given().accept("application/hal+json")
+ .and().contentType("application/json")
+ .and().body("{\"id\": \"102\", \"name\": \"test-update-create-hal\"}")
+ .when().put("/crud-and-paged-records/102")
+ .thenReturn();
+ assertThat(createResponse.statusCode()).isEqualTo(201);
+
+ String location = createResponse.header("Location");
+ int id = Integer.parseInt(location.substring(createResponse.header("Location").lastIndexOf("/") + 1));
+ JsonPath body = createResponse.body().jsonPath();
+ assertThat(body.getInt("id")).isEqualTo(id);
+ assertThat(body.getString("name")).isEqualTo("test-update-create-hal");
+ assertThat(body.getString("_links.add.href")).endsWith("/crud-and-paged-records");
+ assertThat(body.getString("_links.list.href")).endsWith("/crud-and-paged-records");
+ assertThat(body.getString("_links.self.href")).endsWith("/crud-and-paged-records/" + id);
+ assertThat(body.getString("_links.update.href")).endsWith("/crud-and-paged-records/" + id);
+ assertThat(body.getString("_links.remove.href")).endsWith("/crud-and-paged-records/" + id);
+
+ given().accept("application/json")
+ .and().contentType("application/json")
+ .and().body("{\"id\": \"" + id + "\", \"name\": \"test-update-hal\"}")
+ .when().put(location)
+ .then()
+ .statusCode(204);
+ given().accept("application/json")
+ .when().get(location)
+ .then().statusCode(200)
+ .and().body("id", is(equalTo(id)))
+ .and().body("name", is(equalTo("test-update-hal")));
+ }
+
+ @Test
+ void shouldCreateAndDelete() {
+ Response createResponse = given().accept("application/json")
+ .and().contentType("application/json")
+ .and().body("{\"name\": \"test-delete\"}")
+ .when().post("/crud-and-paged-records")
+ .thenReturn();
+ assertThat(createResponse.statusCode()).isEqualTo(201);
+
+ String location = createResponse.header("Location");
+ when().delete(location)
+ .then().statusCode(204);
+ when().get(location)
+ .then().statusCode(404);
+ }
+
+ @Test
+ void shouldNotDeleteNonExistent() {
+ when().delete("/crud-and-paged-records/1000")
+ .then().statusCode(404);
+ }
+
+ private void assertLinks(Headers headers, Map expectedLinks) {
+ List links = new LinkedList<>();
+ for (Header header : headers.getList("Link")) {
+ links.add(Link.valueOf(header.getValue()));
+ }
+ assertThat(links).hasSize(expectedLinks.size());
+ for (Map.Entry expectedLink : expectedLinks.entrySet()) {
+ assertThat(links).anySatisfy(link -> {
+ assertThat(link.getUri().toString()).endsWith(expectedLink.getValue());
+ assertThat(link.getRel()).isEqualTo(expectedLink.getKey());
+ });
+ }
+ }
+}
diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/DefaultCrudResourceTest.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/DefaultCrudResourceTest.java
index 6895f61bd66eb..a716dcceaed74 100644
--- a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/DefaultCrudResourceTest.java
+++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/DefaultCrudResourceTest.java
@@ -11,6 +11,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import io.quarkus.spring.data.rest.AbstractEntity;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/ModifiedCrudResourceTest.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/ModifiedCrudResourceTest.java
index 2545315da06cd..c388c1473ef1f 100644
--- a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/ModifiedCrudResourceTest.java
+++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/ModifiedCrudResourceTest.java
@@ -11,6 +11,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import io.quarkus.spring.data.rest.AbstractEntity;
import io.quarkus.test.QuarkusUnitTest;
public class ModifiedCrudResourceTest {
diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/Record.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/Record.java
index 0770a80a07a75..ad98d811c75b2 100644
--- a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/Record.java
+++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/Record.java
@@ -2,6 +2,8 @@
import jakarta.persistence.Entity;
+import io.quarkus.spring.data.rest.AbstractEntity;
+
@Entity
public class Record extends AbstractEntity {
diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/AbstractEntity.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/AbstractEntity.java
deleted file mode 100644
index 66484a0d200e5..0000000000000
--- a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/AbstractEntity.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package io.quarkus.spring.data.rest.paged;
-
-import java.io.Serializable;
-
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.Id;
-import jakarta.persistence.MappedSuperclass;
-
-@MappedSuperclass
-public abstract class AbstractEntity {
-
- @Id
- @GeneratedValue
- private IdType id;
-
- public IdType getId() {
- return id;
- }
-}
diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceBisTest.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceBisTest.java
new file mode 100644
index 0000000000000..a110521962bd2
--- /dev/null
+++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceBisTest.java
@@ -0,0 +1,73 @@
+package io.quarkus.spring.data.rest.paged;
+
+import static io.restassured.RestAssured.given;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.hasItems;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import jakarta.ws.rs.core.Link;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.spring.data.rest.AbstractEntity;
+import io.quarkus.spring.data.rest.CrudAndPagedRecordsRepository;
+import io.quarkus.test.QuarkusUnitTest;
+import io.restassured.http.Header;
+import io.restassured.http.Headers;
+
+class DefaultPagedResourceBisTest {
+ @RegisterExtension
+ static final QuarkusUnitTest TEST = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(AbstractEntity.class, Record.class, CrudAndPagedRecordsRepository.class)
+ .addAsResource("application.properties")
+ .addAsResource("import.sql"));
+
+ @Test
+ // @Disabled
+ void shouldListHal() {
+ given().accept("application/hal+json")
+ .when().get("/crud-and-paged-records")
+ .then().statusCode(200).log().all()
+ .and().body("_embedded.crud-and-paged-records.id", hasItems(1, 2))
+ .and().body("_embedded.crud-and-paged-records.name", hasItems("first", "second"))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.add.href",
+ hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records")))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.list.href",
+ hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records")))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.self.href",
+ hasItems(endsWith("/crud-and-paged-records/1"), endsWith("/crud-and-paged-records/2")))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.update.href",
+ hasItems(endsWith("/crud-and-paged-records/1"), endsWith("/crud-and-paged-records/2")))
+ .and()
+ .body("_embedded.crud-and-paged-records._links.remove.href",
+ hasItems(endsWith("/crud-and-paged-records/1"), endsWith("/crud-and-paged-records/2")))
+ .and().body("_links.add.href", endsWith("/crud-and-paged-records"))
+ .and().body("_links.list.href", endsWith("/crud-and-paged-records"))
+ .and().body("_links.first.href", endsWith("/crud-and-paged-records?page=0&size=20"))
+ .and().body("_links.last.href", endsWith("/crud-and-paged-records?page=0&size=20"));
+ }
+
+ private void assertLinks(Headers headers, Map expectedLinks) {
+ List links = new LinkedList<>();
+ for (Header header : headers.getList("Link")) {
+ links.add(Link.valueOf(header.getValue()));
+ }
+ assertThat(links).hasSize(expectedLinks.size());
+ for (Map.Entry expectedLink : expectedLinks.entrySet()) {
+ assertThat(links).anySatisfy(link -> {
+ assertThat(link.getUri().toString()).endsWith(expectedLink.getValue());
+ assertThat(link.getRel()).isEqualTo(expectedLink.getKey());
+ });
+ }
+ }
+}
diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceTest.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceTest.java
index 087f42baa2473..86d894e4c80d8 100644
--- a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceTest.java
+++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceTest.java
@@ -1,12 +1,10 @@
package io.quarkus.spring.data.rest.paged;
import static io.restassured.RestAssured.given;
-import static io.restassured.RestAssured.when;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.endsWith;
-import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.is;
@@ -21,10 +19,10 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import io.quarkus.spring.data.rest.AbstractEntity;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.http.Header;
import io.restassured.http.Headers;
-import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
class DefaultPagedResourceTest {
@@ -35,43 +33,6 @@ class DefaultPagedResourceTest {
.addAsResource("application.properties")
.addAsResource("import.sql"));
- @Test
- void shouldGet() {
- given().accept("application/json")
- .when().get("/default-records/1")
- .then().statusCode(200)
- .and().body("id", is(equalTo(1)))
- .and().body("name", is(equalTo("first")));
- }
-
- @Test
- void shouldNotGetNonExistent() {
- given().accept("application/json")
- .when().get("/default-records/1000")
- .then().statusCode(404);
- }
-
- @Test
- void shouldGetHal() {
- given().accept("application/hal+json")
- .when().get("/default-records/1")
- .then().statusCode(200)
- .and().body("id", is(equalTo(1)))
- .and().body("name", is(equalTo("first")))
- .and().body("_links.add.href", endsWith("/default-records"))
- .and().body("_links.list.href", endsWith("/default-records"))
- .and().body("_links.self.href", endsWith("/default-records/1"))
- .and().body("_links.update.href", endsWith("/default-records/1"))
- .and().body("_links.remove.href", endsWith("/default-records/1"));
- }
-
- @Test
- void shouldNotGetNonExistentHal() {
- given().accept("application/hal+json")
- .when().get("/default-records/1000")
- .then().statusCode(404);
- }
-
@Test
void shouldList() {
Response response = given().accept("application/json")
@@ -288,138 +249,6 @@ void shouldListDescending() {
assertThat(actualNames).isEqualTo(expectedNames);
}
- @Test
- void shouldCreate() {
- Response response = given().accept("application/json")
- .and().contentType("application/json")
- .and().body("{\"name\": \"test-create\"}")
- .when().post("/default-records")
- .thenReturn();
- assertThat(response.statusCode()).isEqualTo(201);
-
- String location = response.header("Location");
- int id = Integer.parseInt(location.substring(response.header("Location").lastIndexOf("/") + 1));
- JsonPath body = response.body().jsonPath();
- assertThat(body.getInt("id")).isEqualTo(id);
- assertThat(body.getString("name")).isEqualTo("test-create");
-
- given().accept("application/json")
- .when().get(location)
- .then().statusCode(200)
- .and().body("id", is(equalTo(id)))
- .and().body("name", is(equalTo("test-create")));
- }
-
- @Test
- void shouldCreateHal() {
- Response response = given().accept("application/hal+json")
- .and().contentType("application/json")
- .and().body("{\"name\": \"test-create-hal\"}")
- .when().post("/default-records")
- .thenReturn();
- assertThat(response.statusCode()).isEqualTo(201);
-
- String location = response.header("Location");
- int id = Integer.parseInt(location.substring(response.header("Location").lastIndexOf("/") + 1));
- JsonPath body = response.body().jsonPath();
- assertThat(body.getInt("id")).isEqualTo(id);
- assertThat(body.getString("name")).isEqualTo("test-create-hal");
- assertThat(body.getString("_links.add.href")).endsWith("/default-records");
- assertThat(body.getString("_links.list.href")).endsWith("/default-records");
- assertThat(body.getString("_links.self.href")).endsWith("/default-records/" + id);
- assertThat(body.getString("_links.update.href")).endsWith("/default-records/" + id);
- assertThat(body.getString("_links.remove.href")).endsWith("/default-records/" + id);
-
- given().accept("application/json")
- .when().get(location)
- .then().statusCode(200)
- .and().body("id", is(equalTo(id)))
- .and().body("name", is(equalTo("test-create-hal")));
- }
-
- @Test
- void shouldCreateAndUpdate() {
- Response createResponse = given().accept("application/json")
- .and().contentType("application/json")
- .and().body("{\"id\": \"101\", \"name\": \"test-update-create\"}")
- .when().put("/default-records/101")
- .thenReturn();
- assertThat(createResponse.statusCode()).isEqualTo(201);
-
- String location = createResponse.header("Location");
- int id = Integer.parseInt(location.substring(createResponse.header("Location").lastIndexOf("/") + 1));
- JsonPath body = createResponse.body().jsonPath();
- assertThat(body.getInt("id")).isEqualTo(id);
- assertThat(body.getString("name")).isEqualTo("test-update-create");
-
- given().accept("application/json")
- .and().contentType("application/json")
- .and().body("{\"id\": \"" + id + "\", \"name\": \"test-update\"}")
- .when().put(location)
- .then()
- .statusCode(204);
- given().accept("application/json")
- .when().get(location)
- .then().statusCode(200)
- .and().body("id", is(equalTo(id)))
- .and().body("name", is(equalTo("test-update")));
- }
-
- @Test
- void shouldCreateAndUpdateHal() {
- Response createResponse = given().accept("application/hal+json")
- .and().contentType("application/json")
- .and().body("{\"id\": \"102\", \"name\": \"test-update-create-hal\"}")
- .when().put("/default-records/102")
- .thenReturn();
- assertThat(createResponse.statusCode()).isEqualTo(201);
-
- String location = createResponse.header("Location");
- int id = Integer.parseInt(location.substring(createResponse.header("Location").lastIndexOf("/") + 1));
- JsonPath body = createResponse.body().jsonPath();
- assertThat(body.getInt("id")).isEqualTo(id);
- assertThat(body.getString("name")).isEqualTo("test-update-create-hal");
- assertThat(body.getString("_links.add.href")).endsWith("/default-records");
- assertThat(body.getString("_links.list.href")).endsWith("/default-records");
- assertThat(body.getString("_links.self.href")).endsWith("/default-records/" + id);
- assertThat(body.getString("_links.update.href")).endsWith("/default-records/" + id);
- assertThat(body.getString("_links.remove.href")).endsWith("/default-records/" + id);
-
- given().accept("application/json")
- .and().contentType("application/json")
- .and().body("{\"id\": \"" + id + "\", \"name\": \"test-update-hal\"}")
- .when().put(location)
- .then()
- .statusCode(204);
- given().accept("application/json")
- .when().get(location)
- .then().statusCode(200)
- .and().body("id", is(equalTo(id)))
- .and().body("name", is(equalTo("test-update-hal")));
- }
-
- @Test
- void shouldCreateAndDelete() {
- Response createResponse = given().accept("application/json")
- .and().contentType("application/json")
- .and().body("{\"name\": \"test-delete\"}")
- .when().post("/default-records")
- .thenReturn();
- assertThat(createResponse.statusCode()).isEqualTo(201);
-
- String location = createResponse.header("Location");
- when().delete(location)
- .then().statusCode(204);
- when().get(location)
- .then().statusCode(404);
- }
-
- @Test
- void shouldNotDeleteNonExistent() {
- when().delete("/default-records/1000")
- .then().statusCode(404);
- }
-
private void assertLinks(Headers headers, Map expectedLinks) {
List links = new LinkedList<>();
for (Header header : headers.getList("Link")) {
diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/EmptyListRecord.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/EmptyListRecord.java
index 28eeca7512a23..16ac35b6f3c42 100644
--- a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/EmptyListRecord.java
+++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/EmptyListRecord.java
@@ -2,6 +2,8 @@
import jakarta.persistence.Entity;
+import io.quarkus.spring.data.rest.AbstractEntity;
+
@Entity
public class EmptyListRecord extends AbstractEntity {
diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/EmptyListRecordsPagedResourceTest.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/EmptyListRecordsPagedResourceTest.java
index 2666fa8531130..8e2bf9710b646 100644
--- a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/EmptyListRecordsPagedResourceTest.java
+++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/EmptyListRecordsPagedResourceTest.java
@@ -5,6 +5,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import io.quarkus.spring.data.rest.AbstractEntity;
import io.quarkus.test.QuarkusUnitTest;
class EmptyListRecordsPagedResourceTest {
diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/ModifiedPagedResourceTest.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/ModifiedPagedResourceTest.java
index 44bc84d8f9ac6..f02481582e0fb 100644
--- a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/ModifiedPagedResourceTest.java
+++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/ModifiedPagedResourceTest.java
@@ -11,6 +11,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import io.quarkus.spring.data.rest.AbstractEntity;
import io.quarkus.test.QuarkusUnitTest;
public class ModifiedPagedResourceTest {
diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/Record.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/Record.java
index 882df5bff7bd1..cd699b4b18939 100644
--- a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/Record.java
+++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/Record.java
@@ -2,6 +2,8 @@
import jakarta.persistence.Entity;
+import io.quarkus.spring.data.rest.AbstractEntity;
+
@Entity
public class Record extends AbstractEntity {
diff --git a/extensions/spring-web/core/common-runtime/src/main/java/io/quarkus/spring/web/runtime/common/ResponseStatusExceptionMapper.java b/extensions/spring-web/core/common-runtime/src/main/java/io/quarkus/spring/web/runtime/common/ResponseStatusExceptionMapper.java
index c0c5e8d747048..8c1edeaf5a648 100644
--- a/extensions/spring-web/core/common-runtime/src/main/java/io/quarkus/spring/web/runtime/common/ResponseStatusExceptionMapper.java
+++ b/extensions/spring-web/core/common-runtime/src/main/java/io/quarkus/spring/web/runtime/common/ResponseStatusExceptionMapper.java
@@ -14,8 +14,8 @@ public class ResponseStatusExceptionMapper implements ExceptionMapper {
+public interface SongRepository extends ListCrudRepository, ListPagingAndSortingRepository {
@Query(value = "SELECT s FROM Song s JOIN s.likes l WHERE l.id = :personId")
List findPersonLikedSongs(@Param("personId") Long personId);
diff --git a/integration-tests/spring-data-rest/src/main/java/io/quarkus/it/spring/data/rest/BooksRepository.java b/integration-tests/spring-data-rest/src/main/java/io/quarkus/it/spring/data/rest/BooksRepository.java
index b6a6f2df5d290..a7c86d4fc6ded 100644
--- a/integration-tests/spring-data-rest/src/main/java/io/quarkus/it/spring/data/rest/BooksRepository.java
+++ b/integration-tests/spring-data-rest/src/main/java/io/quarkus/it/spring/data/rest/BooksRepository.java
@@ -1,6 +1,7 @@
package io.quarkus.it.spring.data.rest;
+import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
-public interface BooksRepository extends PagingAndSortingRepository {
+public interface BooksRepository extends PagingAndSortingRepository, CrudRepository {
}