From d5c451e9b1013f53593f5222ee78ddad06c3e95a Mon Sep 17 00:00:00 2001 From: Auri Munoz Date: Wed, 27 Mar 2024 08:34:10 +0100 Subject: [PATCH] Migrate to latests version for Spring dependencies --- bom/application/pom.xml | 13 +- .../spring/data/deployment/DotNames.java | 11 +- .../deployment/SpringDataJPAProcessor.java | 4 + .../deployment/BookListCrudRepository.java | 6 + .../BookListCrudRepositoryTest.java | 79 ++++ .../BookListPagingAndSortingRepository.java | 6 + .../BookPagingAndSortingRepositoryTest.java | 111 +++++ .../src/test/resources/import_hp_books.sql | 7 + extensions/spring-data-jpa/runtime/pom.xml | 9 + ...gframework_data_domain_Sort_TypedSort.java | 16 + .../rest/deployment/EntityClassHelper.java | 110 +++++ .../RepositoryMethodsImplementor.java | 399 ++++++++++++++++ .../RepositoryPropertiesProvider.java | 46 ++ .../rest/deployment/ResourceImplementor.java | 6 + .../ResourceMethodsImplementor.java | 33 ++ .../deployment/SpringDataRestProcessor.java | 47 +- .../crud/CrudMethodsImplementor.java | 142 ------ .../crud/CrudPropertiesProvider.java | 34 -- .../deployment/crud/EntityClassHelper.java | 70 --- .../PagingAndSortingMethodsImplementor.java | 182 -------- .../PagingAndSortingPropertiesProvider.java | 39 -- .../data/rest/{crud => }/AbstractEntity.java | 2 +- .../rest/CrudAndPagedRecordsRepository.java | 9 + .../data/rest/CrudAndPagedResourceTest.java | 437 ++++++++++++++++++ .../rest/crud/DefaultCrudResourceTest.java | 1 + .../rest/crud/ModifiedCrudResourceTest.java | 1 + .../quarkus/spring/data/rest/crud/Record.java | 2 + .../data/rest/paged/AbstractEntity.java | 19 - .../paged/DefaultPagedResourceBisTest.java | 73 +++ .../rest/paged/DefaultPagedResourceTest.java | 173 +------ .../data/rest/paged/EmptyListRecord.java | 2 + .../EmptyListRecordsPagedResourceTest.java | 1 + .../rest/paged/ModifiedPagedResourceTest.java | 1 + .../spring/data/rest/paged/Record.java | 2 + .../common/ResponseStatusExceptionMapper.java | 4 +- .../it/spring/data/jpa/SongRepository.java | 5 +- .../it/spring/data/rest/BooksRepository.java | 3 +- 37 files changed, 1414 insertions(+), 691 deletions(-) create mode 100644 extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepository.java create mode 100644 extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepositoryTest.java create mode 100644 extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListPagingAndSortingRepository.java create mode 100644 extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookPagingAndSortingRepositoryTest.java create mode 100644 extensions/spring-data-jpa/deployment/src/test/resources/import_hp_books.sql create mode 100644 extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/spring/data/runtime/Target_org_springframework_data_domain_Sort_TypedSort.java create mode 100644 extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/EntityClassHelper.java create mode 100644 extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/RepositoryMethodsImplementor.java create mode 100644 extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/RepositoryPropertiesProvider.java delete mode 100644 extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/crud/CrudMethodsImplementor.java delete mode 100644 extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/crud/CrudPropertiesProvider.java delete mode 100644 extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/crud/EntityClassHelper.java delete mode 100644 extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/paging/PagingAndSortingMethodsImplementor.java delete mode 100644 extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/paging/PagingAndSortingPropertiesProvider.java rename extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/{crud => }/AbstractEntity.java (89%) create mode 100644 extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/CrudAndPagedRecordsRepository.java create mode 100644 extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/CrudAndPagedResourceTest.java delete mode 100644 extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/AbstractEntity.java create mode 100644 extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceBisTest.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 2967c7882bd0a..63a134b6e70c6 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -171,10 +171,10 @@ 3.26.0 0.3.0 4.13.1 - 5.2.SP7 - 2.1.SP2 - 5.4.Final - 2.1.SP1 + 6.1.SP2 + 3.2.SP1 + 6.2 + 3.2 5.12.0 5.8.0 2.0.3.Final @@ -5887,6 +5887,11 @@ quarkus-spring-beans-api ${quarkus-spring-api.version} + + io.quarkus + quarkus-spring-aop-api + ${quarkus-spring-api.version} + io.quarkus quarkus-spring-data-jpa-api diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/DotNames.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/DotNames.java index bf7010086e965..24dee1ae5375c 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/DotNames.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/DotNames.java @@ -43,6 +43,8 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.ListCrudRepository; +import org.springframework.data.repository.ListPagingAndSortingRepository; import org.springframework.data.repository.NoRepositoryBean; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.Repository; @@ -55,15 +57,22 @@ public final class DotNames { .createSimple(Repository.class.getName()); public static final DotName SPRING_DATA_CRUD_REPOSITORY = DotName .createSimple(CrudRepository.class.getName()); + + public static final DotName SPRING_DATA_LIST_CRUD_REPOSITORY = DotName + .createSimple(ListCrudRepository.class.getName()); public static final DotName SPRING_DATA_PAGING_REPOSITORY = DotName .createSimple(PagingAndSortingRepository.class.getName()); + + public static final DotName SPRING_DATA_LIST_PAGING_REPOSITORY = DotName + .createSimple(ListPagingAndSortingRepository.class.getName()); public static final DotName SPRING_DATA_JPA_REPOSITORY = DotName .createSimple(JpaRepository.class.getName()); public static final DotName SPRING_DATA_REPOSITORY_DEFINITION = DotName .createSimple(RepositoryDefinition.class.getName()); public static final Set SUPPORTED_REPOSITORIES = new HashSet<>(Arrays.asList( - SPRING_DATA_JPA_REPOSITORY, SPRING_DATA_PAGING_REPOSITORY, SPRING_DATA_CRUD_REPOSITORY, SPRING_DATA_REPOSITORY)); + SPRING_DATA_JPA_REPOSITORY, SPRING_DATA_PAGING_REPOSITORY, SPRING_DATA_LIST_PAGING_REPOSITORY, + SPRING_DATA_CRUD_REPOSITORY, SPRING_DATA_LIST_CRUD_REPOSITORY, SPRING_DATA_REPOSITORY)); public static final DotName SPRING_DATA_NO_REPOSITORY_BEAN = DotName .createSimple(NoRepositoryBean.class.getName()); diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/SpringDataJPAProcessor.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/SpringDataJPAProcessor.java index 775fc0ea4a301..c21689a83f796 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/SpringDataJPAProcessor.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/SpringDataJPAProcessor.java @@ -27,6 +27,8 @@ import org.springframework.data.domain.Persistable; 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 org.springframework.data.repository.Repository; import org.springframework.data.repository.query.QueryByExampleExecutor; @@ -80,7 +82,9 @@ void contributeClassesToIndex(BuildProducer a additionalIndexedClasses.produce(new AdditionalIndexedClassesBuildItem( Repository.class.getName(), CrudRepository.class.getName(), + ListCrudRepository.class.getName(), PagingAndSortingRepository.class.getName(), + ListPagingAndSortingRepository.class.getName(), JpaRepository.class.getName(), QueryByExampleExecutor.class.getName())); } diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepository.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepository.java new file mode 100644 index 0000000000000..e429d08949499 --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepository.java @@ -0,0 +1,6 @@ +package io.quarkus.spring.data.deployment; + +import org.springframework.data.repository.ListCrudRepository; + +public interface BookListCrudRepository extends ListCrudRepository { +} diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepositoryTest.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepositoryTest.java new file mode 100644 index 0000000000000..5b1a6a39cff29 --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepositoryTest.java @@ -0,0 +1,79 @@ +package io.quarkus.spring.data.deployment; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.List; + +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class BookListCrudRepositoryTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("import_books.sql", "import.sql") + .addClasses(Book.class, BookListCrudRepository.class)) + .withConfigurationResource("application.properties"); + + @Inject + BookListCrudRepository repo; + + @Test + @Order(1) + @Transactional + public void shouldListAllBooks() { + List all = repo.findAll(); + assertThat(all).isNotEmpty(); + assertThat(all).hasSize(3); + assertThat(all.stream().map(Book::getName)).containsExactlyInAnyOrder("Talking to Strangers", "The Ascent of Money", + "A Short History of Everything"); + } + + @Test + @Order(2) + @Transactional + public void shouldListBooksWithIds() { + List ids = Arrays.asList(1, 2); + List all = repo.findAllById(ids); + assertThat(all).isNotEmpty(); + assertThat(all).hasSize(2); + assertThat(all.stream().map(Book::getName)).containsExactlyInAnyOrder("Talking to Strangers", "The Ascent of Money"); + } + + @Test + @Order(3) + @Transactional + public void shouldSaveBooks() { + Book harryPotterAndTheChamberOfSecrets = populateBook(4, "Harry Potter and the Chamber of Secrets"); + Book harryPotterAndThePrisonerOfAzkaban = populateBook(5, "Harry Potter and the Prisoner of Azkaban"); + Book harryPotterAndTheGlobetOfFire = populateBook(6, "Harry Potter and the Globet of Fire"); + List books = Arrays.asList(harryPotterAndTheChamberOfSecrets, harryPotterAndThePrisonerOfAzkaban, + harryPotterAndTheGlobetOfFire); + List all = repo.saveAll(books); + assertThat(all).isNotEmpty(); + assertThat(all).hasSize(3); + assertThat(all.stream().map(Book::getName)).containsExactlyInAnyOrder("Harry Potter and the Chamber of Secrets", + "Harry Potter and the Prisoner of Azkaban", "Harry Potter and the Globet of Fire"); + } + + private Book populateBook(Integer id, String title) { + Book book = new Book(); + book.setBid(id); + book.setName(title); + return book; + } + +} diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListPagingAndSortingRepository.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListPagingAndSortingRepository.java new file mode 100644 index 0000000000000..c94143348a55d --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListPagingAndSortingRepository.java @@ -0,0 +1,6 @@ +package io.quarkus.spring.data.deployment; + +import org.springframework.data.repository.ListPagingAndSortingRepository; + +public interface BookListPagingAndSortingRepository extends ListPagingAndSortingRepository { +} diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookPagingAndSortingRepositoryTest.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookPagingAndSortingRepositoryTest.java new file mode 100644 index 0000000000000..071d9af930e35 --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookPagingAndSortingRepositoryTest.java @@ -0,0 +1,111 @@ +package io.quarkus.spring.data.deployment; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; + +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 org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import io.quarkus.test.QuarkusUnitTest; + +public class BookPagingAndSortingRepositoryTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("import_hp_books.sql", "import.sql") + .addClasses(Book.class, BookListPagingAndSortingRepository.class)) + .withConfigurationResource("application.properties"); + + @Inject + BookListPagingAndSortingRepository repo; + + @Test + // @Order(1) + @Transactional + public void shouldReturnFirstPageOfTwoBooks() { + Pageable pageRequest = PageRequest.of(0, 2); + Page result = repo.findAll(pageRequest); + + assertThat(result).isNotEmpty(); + assertThat(result).hasSize(2); + assertThat(result.stream().map(Book::getBid)).containsExactly(1, 2); + } + + @Test + @Transactional + public void shouldReturnSecondPageOfSizeTwoBooks() { + Pageable pageRequest = PageRequest.of(1, 2); + Page result = repo.findAll(pageRequest); + + assertThat(result).isNotEmpty(); + assertThat(result).hasSize(2); + assertThat(result.stream().map(Book::getBid)).containsExactly(3, 4); + } + + @Test + @Transactional + public void shouldReturnLastPage() { + Pageable pageRequest = PageRequest.of(2, 2); + Page result = repo.findAll(pageRequest); + + assertThat(result).isNotEmpty(); + assertThat(result).hasSize(2); + assertThat(result.stream().map(Book::getBid)).containsExactly(5, 6); + } + + @Test + @Transactional + void shouldReturnSortedByNameAscAndPagedResult() { + Pageable pageRequest = PageRequest.of(0, 3, Sort.by("name")); + + Page result = repo.findAll(pageRequest); + assertThat(result).isNotEmpty(); + assertThat(result).hasSize(3); + assertThat(result.stream().map(Book::getBid)).containsExactly(2, 7, 4); + + } + + @Test + @Transactional + void shouldReturnSortedByNameDescAndPagedResult() { + Pageable pageRequest = PageRequest.of(0, 5, Sort.by("name").descending()); + + Page result = repo.findAll(pageRequest); + assertThat(result).isNotEmpty(); + assertThat(result).hasSize(5); + assertThat(result.stream().map(Book::getBid)).containsExactly(3, 1, 5, 6, 4); + + } + + @Test + @Transactional + void shouldReturnAllBooksSortedByNameDescResult() { + List result = repo.findAll(Sort.by("name").descending()); + assertThat(result).isNotEmpty(); + assertThat(result).hasSize(7); + assertThat(result.stream().map(Book::getBid)).containsExactly(3, 1, 5, 6, 4, 7, 2); + + } + + @Test + @Transactional + void shouldReturnAllBooksSortedByNameAscResult() { + List result = repo.findAll(Sort.by("name")); + assertThat(result).isNotEmpty(); + assertThat(result).hasSize(7); + assertThat(result.stream().map(Book::getBid)).containsExactly(2, 7, 4, 6, 5, 1, 3); + + } + +} diff --git a/extensions/spring-data-jpa/deployment/src/test/resources/import_hp_books.sql b/extensions/spring-data-jpa/deployment/src/test/resources/import_hp_books.sql new file mode 100644 index 0000000000000..aee4a7c8e0f02 --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/resources/import_hp_books.sql @@ -0,0 +1,7 @@ +INSERT INTO book(bid, name) VALUES (1, 'Harry Potter and the Philosophers Stone'); +INSERT INTO book(bid, name) VALUES (2, 'Harry Potter and the Chamber of Secrets'); +INSERT INTO book(bid, name) VALUES (3, 'Harry Potter and the Prisoner of Azkaban'); +INSERT INTO book(bid, name) VALUES (4, 'Harry Potter and the Goblet of Fire'); +INSERT INTO book(bid, name) VALUES (5, 'Harry Potter and the Order of the Phoenix'); +INSERT INTO book(bid, name) VALUES (6, 'Harry Potter and the Half-Blood Prince'); +INSERT INTO book(bid, name) VALUES (7, 'Harry Potter and the Deathly Hallows'); \ No newline at end of file diff --git a/extensions/spring-data-jpa/runtime/pom.xml b/extensions/spring-data-jpa/runtime/pom.xml index 58a40263b61b2..cc271582e030e 100644 --- a/extensions/spring-data-jpa/runtime/pom.xml +++ b/extensions/spring-data-jpa/runtime/pom.xml @@ -38,10 +38,19 @@ io.quarkus quarkus-spring-core-api + + io.quarkus + quarkus-spring-aop-api + io.quarkus quarkus-spring-boot-orm-api + + org.graalvm.sdk + nativeimage + provided + diff --git a/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/spring/data/runtime/Target_org_springframework_data_domain_Sort_TypedSort.java b/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/spring/data/runtime/Target_org_springframework_data_domain_Sort_TypedSort.java new file mode 100644 index 0000000000000..7069a91bc2805 --- /dev/null +++ b/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/spring/data/runtime/Target_org_springframework_data_domain_Sort_TypedSort.java @@ -0,0 +1,16 @@ +package io.quarkus.spring.data.runtime; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.annotate.TargetElement; + +@TargetClass(className = "org.springframework.data.domain.Sort", innerClass = "TypedSort") +public final class Target_org_springframework_data_domain_Sort_TypedSort { + + @Substitute + @TargetElement(name = TargetElement.CONSTRUCTOR_NAME) + public Target_org_springframework_data_domain_Sort_TypedSort(Class type) { + + } + +} diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/EntityClassHelper.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/EntityClassHelper.java new file mode 100644 index 0000000000000..8d03d2ecb9add --- /dev/null +++ b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/EntityClassHelper.java @@ -0,0 +1,110 @@ +package io.quarkus.spring.data.rest.deployment; + +import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.CRUD_REPOSITORY_INTERFACE; +import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.JPA_REPOSITORY_INTERFACE; +import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.LIST_CRUD_REPOSITORY_INTERFACE; +import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.LIST_PAGING_AND_SORTING_REPOSITORY_INTERFACE; +import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.PAGING_AND_SORTING_REPOSITORY_INTERFACE; + +import java.util.List; + +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; + +public class EntityClassHelper { + + private final IndexView index; + + public EntityClassHelper(IndexView index) { + this.index = index; + } + + public FieldInfo getIdField(String className) { + return getIdField(index.getClassByName(DotName.createSimple(className))); + } + + public 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); + } + + public MethodDescriptor getSetter(String className, FieldInfo field) { + return getSetter(index.getClassByName(DotName.createSimple(className)), field); + } + + public 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()); + } + + public 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; + } + + public boolean isRepositoryInstanceOf(DotName target, String repositoryName) { + ClassInfo classByName = index.getClassByName(repositoryName); + List types = classByName.interfaceTypes(); + return types.stream().anyMatch(type -> type.name().equals(target)); + } + + public boolean isCrudRepository(String repositoryName) { + return isRepositoryInstanceOf(CRUD_REPOSITORY_INTERFACE, repositoryName) + || isRepositoryInstanceOf(LIST_CRUD_REPOSITORY_INTERFACE, repositoryName) + || isRepositoryInstanceOf(JPA_REPOSITORY_INTERFACE, repositoryName); + } + + public boolean isListCrudRepository(String repositoryName) { + return isRepositoryInstanceOf(LIST_CRUD_REPOSITORY_INTERFACE, repositoryName) + || isRepositoryInstanceOf(JPA_REPOSITORY_INTERFACE, repositoryName); + } + + public boolean isPagingAndSortingRepository(String repositoryName) { + return isRepositoryInstanceOf(PAGING_AND_SORTING_REPOSITORY_INTERFACE, repositoryName) + || isRepositoryInstanceOf(LIST_PAGING_AND_SORTING_REPOSITORY_INTERFACE, repositoryName) + || isRepositoryInstanceOf(JPA_REPOSITORY_INTERFACE, repositoryName); + } + + public boolean isListPagingAndSortingRepository(String repositoryName) { + return isRepositoryInstanceOf(LIST_PAGING_AND_SORTING_REPOSITORY_INTERFACE, repositoryName) + || isRepositoryInstanceOf(JPA_REPOSITORY_INTERFACE, repositoryName); + } + + public boolean containsPagedRepository(List repositories) { + return repositories.stream().anyMatch(r -> isPagingAndSortingRepository(r.name().toString())); + } +} diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/RepositoryMethodsImplementor.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/RepositoryMethodsImplementor.java new file mode 100644 index 0000000000000..099b21998494b --- /dev/null +++ b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/RepositoryMethodsImplementor.java @@ -0,0 +1,399 @@ +package io.quarkus.spring.data.rest.deployment; + +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 java.util.Optional; + +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.IndexView; +import org.jboss.logging.Logger; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +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.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; + +public class RepositoryMethodsImplementor implements ResourceMethodsImplementor { + + private static final Logger LOGGER = Logger.getLogger(RepositoryMethodsImplementor.class); + + //CrudRepository + 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); + public static final MethodDescriptor LIST_ITERABLE = ofMethod(CrudRepository.class, "findAll", Iterable.class); + + //ListCrudRepository + public static final MethodDescriptor LIST = ofMethod(ListCrudRepository.class, "findAll", List.class); + public static final MethodDescriptor LIST_BY_ID = ofMethod(ListCrudRepository.class, "findAllById", List.class, + Iterable.class); + public static final MethodDescriptor SAVE_LIST = ofMethod(ListCrudRepository.class, "saveAll", List.class, Iterable.class); + + //PagingAndSortingRepository + public static final MethodDescriptor LIST_PAGED = ofMethod(PagingAndSortingRepository.class, "findAll", + org.springframework.data.domain.Page.class, Pageable.class); + + //ListPagingAndSortingRepository + public static final MethodDescriptor LIST_SORTED = ofMethod(ListPagingAndSortingRepository.class, "findAll", + List.class, org.springframework.data.domain.Sort.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 static final DotName CRUD_REPOSITORY_INTERFACE = DotName.createSimple(CrudRepository.class.getName()); + public static final DotName LIST_CRUD_REPOSITORY_INTERFACE = DotName.createSimple(ListCrudRepository.class.getName()); + + public static final DotName PAGING_AND_SORTING_REPOSITORY_INTERFACE = DotName + .createSimple(PagingAndSortingRepository.class.getName()); + + public static final DotName LIST_PAGING_AND_SORTING_REPOSITORY_INTERFACE = DotName + .createSimple(ListPagingAndSortingRepository.class.getName()); + + public static final DotName JPA_REPOSITORY_INTERFACE = DotName.createSimple(JpaRepository.class.getName()); + + protected final EntityClassHelper entityClassHelper; + + public RepositoryMethodsImplementor(IndexView index, EntityClassHelper entityClassHelper) { + this.entityClassHelper = entityClassHelper; + } + + // CrudRepository Iterable findAll(); + public void implementListIterable(ClassCreator classCreator, String repositoryInterfaceName) { + if (entityClassHelper.isCrudRepository(repositoryInterfaceName) + && !entityClassHelper.isPagingAndSortingRepository(repositoryInterfaceName)) { + MethodCreator methodCreator = classCreator.getMethodCreator("list", List.class, Page.class, Sort.class, + String.class, Map.class); + ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterfaceName); + ResultHandle result = methodCreator.invokeInterfaceMethod(LIST_ITERABLE, repository); + methodCreator.returnValue(result); + LOGGER.infof("Method code: %s ", methodCreator.getMethodDescriptor().toString()); + methodCreator.close(); + } + } + + //ListCrudRepository List findAll(); + public void implementList(ClassCreator classCreator, String repositoryInterfaceName) { + if (entityClassHelper.isListCrudRepository(repositoryInterfaceName)) { + MethodCreator methodCreator = classCreator.getMethodCreator("list", List.class, Page.class, Sort.class, + String.class, Map.class); + ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterfaceName); + ResultHandle result = methodCreator.invokeInterfaceMethod(LIST, repository); + methodCreator.returnValue(result); + LOGGER.infof("Method code: %s ", methodCreator.toString()); + methodCreator.close(); + } + } + + // PagingAndSortingRepository Page findAll(Pageable pageable); + public void implementListPaged(ClassCreator classCreator, String repositoryInterfaceName) { + if (entityClassHelper.isPagingAndSortingRepository(repositoryInterfaceName)) { + 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, repositoryInterfaceName); + 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); + LOGGER.infof("Method code: %s ", methodCreator.toString()); + methodCreator.close(); + } + } + + //ListPagingAndSortingRepository List findAll(Sort sort); + public void implementListSort(ClassCreator classCreator, String repositoryInterfaceName) { + if (entityClassHelper.isListPagingAndSortingRepository(repositoryInterfaceName)) { + 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, repositoryInterfaceName); + ResultHandle resultPage = methodCreator.invokeInterfaceMethod(LIST_SORTED, repository, pageable); + ResultHandle result = methodCreator.invokeInterfaceMethod( + ofMethod(org.springframework.data.domain.Page.class, "getContent", List.class), resultPage); + + methodCreator.returnValue(result); + LOGGER.infof("Method code: %s ", methodCreator.toString()); + methodCreator.close(); + } + } + + //PagingAndSortingRepository Page findAll(Pageable pageable); + public void implementListPageCount(ClassCreator classCreator, String repositoryInterfaceName) { + MethodCreator methodCreator = classCreator.getMethodCreator(Constants.PAGE_COUNT_METHOD_PREFIX + "list", + int.class, Page.class); + if (entityClassHelper.isPagingAndSortingRepository(repositoryInterfaceName)) { + ResultHandle page = methodCreator.getMethodParam(0); + ResultHandle pageable = toPageable(methodCreator, page); + ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterfaceName); + 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); + } else { + methodCreator.throwException(RuntimeException.class, "Method not implemented"); + } + LOGGER.infof("Method code: %s ", methodCreator.toString()); + methodCreator.close(); + } + + //ListCrudRepository List findAllById(Iterable ids); + public void implementListById(ClassCreator classCreator, String repositoryInterfaceName) { + if (entityClassHelper.isListCrudRepository(repositoryInterfaceName)) { + MethodCreator methodCreator = classCreator.getMethodCreator("list", List.class, Iterable.class); + ResultHandle ids = methodCreator.getMethodParam(0); + ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterfaceName); + ResultHandle result = methodCreator.invokeInterfaceMethod(LIST_BY_ID, repository, ids); + methodCreator.returnValue(result); + LOGGER.infof("Method code: %s ", methodCreator.toString()); + methodCreator.close(); + } + } + + // CrudRepository Optional findById(ID id); + public void implementGet(ClassCreator classCreator, String repositoryInterfaceName) { + MethodCreator methodCreator = classCreator.getMethodCreator("get", Object.class, Object.class); + if (entityClassHelper.isCrudRepository(repositoryInterfaceName)) { + ResultHandle id = methodCreator.getMethodParam(0); + ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterfaceName); + ResultHandle result = findById(methodCreator, id, repository); + methodCreator.returnValue(result); + } else { + methodCreator.throwException(RuntimeException.class, "Method not implemented"); + } + LOGGER.infof("Method code: %s ", methodCreator.toString()); + methodCreator.close(); + } + + // CrudRepository S save(S entity); + public void implementAdd(ClassCreator classCreator, String repositoryInterfaceName) { + MethodCreator methodCreator = classCreator.getMethodCreator("add", Object.class, Object.class); + if (entityClassHelper.isCrudRepository(repositoryInterfaceName)) { + ResultHandle entity = methodCreator.getMethodParam(0); + ResultHandle repository = getRepositoryInstance(methodCreator, repositoryInterfaceName); + ResultHandle result = methodCreator.invokeInterfaceMethod(ADD, repository, entity); + + methodCreator.returnValue(result); + } else { + methodCreator.throwException(RuntimeException.class, "Method not implemented"); + + } + LOGGER.infof("Method code: %s ", methodCreator.toString()); + methodCreator.close(); + } + + //ListCrudRepository List saveAll(Iterable 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 { }