From fd242fd1d59dcd85921c46734686f3bc04d9ff35 Mon Sep 17 00:00:00 2001 From: em Date: Fri, 7 Jun 2024 14:50:26 +0200 Subject: [PATCH] [#1906] Add new Spring Data/Boot 3.3 integration --- .github/workflows/ci.yaml | 166 ++-- .github/workflows/latest.yaml | 15 +- CHANGELOG.md | 8 +- README.md | 4 +- ci/build-github-latest.sh | 14 +- core/testsuite-hibernate6/pom.xml | 122 +++ core/testsuite-jakarta-runner/pom.xml | 124 +++ core/testsuite-jakarta/pom.xml | 5 + core/testsuite/pom.xml | 343 ++++++++ .../treat/entity/TablePerClassSub1.java | 12 - .../treat/entity/TablePerClassSub2.java | 12 - .../testsuite/AbstractCoreTest.java | 9 + .../entity/EmbeddableTestEntitySub.java | 2 - dist/bom/pom.xml | 12 + dist/full/pom.xml | 8 + .../manual/en_US/getting_started.adoc | 2 +- .../entity-view/manual/en_US/spring_data.adoc | 2 +- .../view/impl/proxy/UnsafeHelper.java | 2 +- entity-view/testsuite-jakarta-runner/pom.xml | 124 +++ entity-view/testsuite/pom.xml | 239 ++++++ .../subview/treat/model/ContainerItem1.java | 2 - .../src/test/resources/logging.properties | 2 + integration/deltaspike-data/testsuite/pom.xml | 234 ++++++ integration/hibernate-6.2/pom.xml | 12 + integration/hibernate6-base/pom.xml | 12 + integration/quarkus-3/deployment/pom.xml | 220 +++++ integration/querydsl/testsuite/pom.xml | 239 ++++++ integration/spring-data/3.3/pom.xml | 167 ++++ .../query/CriteriaQueryParameterBinder.java | 49 ++ .../EntityViewAwareRepositoryInformation.java | 155 ++++ ...EntityViewAwareRepositoryMetadataImpl.java | 142 ++++ .../query/ParameterMetadataProviderImpl.java | 316 +++++++ .../query/PartTreeBlazePersistenceQuery.java | 256 ++++++ .../BlazePersistenceRepositoryFactory.java | 600 ++++++++++++++ ...BlazePersistenceRepositoryFactoryBean.java | 131 +++ .../DefaultRepositoryInformation.java | 320 ++++++++ .../EntityViewAwareRepositoryImpl.java | 111 +++ .../data/impl/repository/MethodLookups.java | 443 ++++++++++ .../QueryCollectingQueryCreationListener.java | 46 ++ .../QueryExecutionResultHandler.java | 296 +++++++ integration/spring-data/base-3.3/pom.xml | 126 +++ .../spring/data/annotation/OptionalParam.java | 43 + .../spring/data/base/EntityViewSortUtil.java | 115 +++ .../AbstractCriteriaQueryParameterBinder.java | 110 +++ ...AbstractPartTreeBlazePersistenceQuery.java | 553 +++++++++++++ .../query/EntityViewAwareJpaQueryMethod.java | 121 +++ .../EntityViewAwareRepositoryMetadata.java | 37 + .../spring/data/base/query/JpaParameters.java | 342 ++++++++ .../data/base/query/KeysetAwarePageImpl.java | 85 ++ .../data/base/query/KeysetAwareSliceImpl.java | 86 ++ .../data/base/query/ParameterBinder.java | 192 +++++ .../base/query/ParameterMetadataProvider.java | 96 +++ .../AbstractEntityViewAwareRepository.java | 768 ++++++++++++++++++ .../base/repository/EntityGraphFactory.java | 79 ++ .../EntityViewAwareCrudMethodMetadata.java | 43 + ...wAwareCrudMethodMetadataPostProcessor.java | 367 +++++++++ .../FetchableFluentQueryBySpecification.java | 245 ++++++ .../base/repository/FluentQuerySupport.java | 90 ++ .../data/repository/BlazeSpecification.java | 29 + .../EntityViewReplacingMethodInterceptor.java | 73 ++ .../data/repository/EntityViewRepository.java | 74 ++ .../EntityViewSettingProcessor.java | 35 + .../EntityViewSpecificationExecutor.java | 79 ++ .../data/repository/KeysetAwarePage.java | 45 + .../data/repository/KeysetAwareSlice.java | 46 ++ .../data/repository/KeysetPageRequest.java | 183 +++++ .../data/repository/KeysetPageable.java | 61 ++ .../config/BlazeRepositoriesRegistrar.java | 40 + .../BlazeRepositoryConfigExtension.java | 77 ++ .../config/EnableBlazeRepositories.java | 170 ++++ .../spring/data/repository/package-info.java | 24 + .../query/FixedJpaCountQueryCreator.java | 45 + .../query/FixedJpaQueryCreator.java | 300 +++++++ .../data/repository/config/BootstrapMode.java | 45 + integration/spring-data/pom.xml | 2 + .../testsuite/webflux-jakarta-runner/pom.xml | 129 ++- .../spring-data/testsuite/webflux/pom.xml | 335 ++++++++ .../testsuite/webmvc-jakarta-runner/pom.xml | 132 ++- .../spring-data/testsuite/webmvc/pom.xml | 332 ++++++++ integration/spring-data/webflux/pom.xml | 81 ++ integration/spring-data/webmvc/pom.xml | 96 +++ .../spring-hateoas/webmvc-jakarta/pom.xml | 145 +++- integration/spring-hateoas/webmvc/pom.xml | 89 +- jpa-criteria/testsuite-jakarta-runner/pom.xml | 124 +++ jpa-criteria/testsuite/pom.xml | 239 ++++++ parent/pom.xml | 25 +- testsuite-base/assertion/pom.xml | 54 ++ .../assertion/AbstractAssertStatement.java | 39 +- .../AbstractAssertStatementBuilder.java | 0 .../jpa/assertion/AssertDeleteStatement.java | 0 .../AssertDeleteStatementBuilder.java | 0 .../jpa/assertion/AssertInsertStatement.java | 0 .../AssertInsertStatementBuilder.java | 0 .../jpa/assertion/AssertMultiStatement.java | 0 .../AssertMultiStatementBuilder.java | 0 .../jpa/assertion/AssertSelectStatement.java | 0 .../AssertSelectStatementBuilder.java | 0 .../base/jpa/assertion/AssertStatement.java | 0 .../jpa/assertion/AssertStatementBuilder.java | 0 .../jpa/assertion/AssertUpdateStatement.java | 0 .../AssertUpdateStatementBuilder.java | 0 testsuite-base/jpa-jakarta/pom.xml | 5 - testsuite-base/jpa/pom.xml | 5 - .../base/jpa/AbstractJpaPersistenceTest.java | 13 +- testsuite-base/pom.xml | 2 + 105 files changed, 11451 insertions(+), 198 deletions(-) create mode 100644 integration/spring-data/3.3/pom.xml create mode 100644 integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/CriteriaQueryParameterBinder.java create mode 100644 integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/EntityViewAwareRepositoryInformation.java create mode 100644 integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/EntityViewAwareRepositoryMetadataImpl.java create mode 100644 integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/ParameterMetadataProviderImpl.java create mode 100644 integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/PartTreeBlazePersistenceQuery.java create mode 100644 integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/BlazePersistenceRepositoryFactory.java create mode 100644 integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/BlazePersistenceRepositoryFactoryBean.java create mode 100644 integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/DefaultRepositoryInformation.java create mode 100644 integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/EntityViewAwareRepositoryImpl.java create mode 100644 integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/MethodLookups.java create mode 100644 integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/QueryCollectingQueryCreationListener.java create mode 100644 integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/QueryExecutionResultHandler.java create mode 100644 integration/spring-data/base-3.3/pom.xml create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/annotation/OptionalParam.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/EntityViewSortUtil.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/AbstractCriteriaQueryParameterBinder.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/AbstractPartTreeBlazePersistenceQuery.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/EntityViewAwareJpaQueryMethod.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/EntityViewAwareRepositoryMetadata.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/JpaParameters.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/KeysetAwarePageImpl.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/KeysetAwareSliceImpl.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/ParameterBinder.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/ParameterMetadataProvider.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/AbstractEntityViewAwareRepository.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/EntityGraphFactory.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/EntityViewAwareCrudMethodMetadata.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/EntityViewAwareCrudMethodMetadataPostProcessor.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/FetchableFluentQueryBySpecification.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/FluentQuerySupport.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/BlazeSpecification.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewReplacingMethodInterceptor.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewRepository.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewSettingProcessor.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewSpecificationExecutor.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetAwarePage.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetAwareSlice.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetPageRequest.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetPageable.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/config/BlazeRepositoriesRegistrar.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/config/BlazeRepositoryConfigExtension.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/config/EnableBlazeRepositories.java create mode 100644 integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/package-info.java create mode 100644 integration/spring-data/base-3.3/src/main/java/org/springframework/data/jpa/repository/query/FixedJpaCountQueryCreator.java create mode 100644 integration/spring-data/base-3.3/src/main/java/org/springframework/data/jpa/repository/query/FixedJpaQueryCreator.java create mode 100644 integration/spring-data/base-3.3/src/main/java/org/springframework/data/repository/config/BootstrapMode.java create mode 100644 testsuite-base/assertion/pom.xml rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AbstractAssertStatement.java (87%) rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AbstractAssertStatementBuilder.java (100%) rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertDeleteStatement.java (100%) rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertDeleteStatementBuilder.java (100%) rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatement.java (100%) rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatementBuilder.java (100%) rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertMultiStatement.java (100%) rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertMultiStatementBuilder.java (100%) rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertSelectStatement.java (100%) rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertSelectStatementBuilder.java (100%) rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatement.java (100%) rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatementBuilder.java (100%) rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatement.java (100%) rename testsuite-base/{jpa => assertion}/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatementBuilder.java (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0b27eba919..d5c8dbdc40 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -273,16 +273,16 @@ jobs: jdk: 8 spring-data: spring-data-1.11.x deltaspike: deltaspike-1.7 - - rdbms: mysql8 - provider: hibernate-5.5 - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 - - rdbms: postgresql - provider: hibernate-5.5 - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 +# - rdbms: mysql8 +# provider: hibernate-5.5 +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 +# - rdbms: postgresql +# provider: hibernate-5.5 +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 # - rdbms: oracle # provider: hibernate-5.5 # jdk: 8 @@ -307,16 +307,16 @@ jobs: jdk: 8 spring-data: spring-data-1.11.x deltaspike: deltaspike-1.7 - - rdbms: mysql8 - provider: hibernate-5.4 - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 - - rdbms: postgresql - provider: hibernate-5.4 - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 +# - rdbms: mysql8 +# provider: hibernate-5.4 +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 +# - rdbms: postgresql +# provider: hibernate-5.4 +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 # - rdbms: oracle # provider: hibernate-5.4 # jdk: 8 @@ -341,16 +341,16 @@ jobs: jdk: 8 spring-data: spring-data-1.11.x deltaspike: deltaspike-1.7 - - rdbms: mysql - provider: hibernate-5.2 - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 - - rdbms: postgresql - provider: hibernate-5.2 - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 +# - rdbms: mysql +# provider: hibernate-5.2 +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 +# - rdbms: postgresql +# provider: hibernate-5.2 +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 # - rdbms: mysql8 # provider: hibernate-5.2 # jdk: 8 @@ -380,16 +380,16 @@ jobs: jdk: 8 spring-data: spring-data-1.11.x deltaspike: deltaspike-1.7 - - rdbms: mysql - provider: hibernate-5.1 - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 - - rdbms: postgresql - provider: hibernate-5.1 - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 +# - rdbms: mysql +# provider: hibernate-5.1 +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 +# - rdbms: postgresql +# provider: hibernate-5.1 +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 # - rdbms: mysql8 # provider: hibernate-5.1 # jdk: 8 @@ -419,16 +419,16 @@ jobs: jdk: 8 spring-data: spring-data-1.11.x deltaspike: deltaspike-1.7 - - rdbms: mysql - provider: hibernate-5.0 - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 - - rdbms: postgresql - provider: hibernate-5.0 - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 +# - rdbms: mysql +# provider: hibernate-5.0 +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 +# - rdbms: postgresql +# provider: hibernate-5.0 +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 # - rdbms: mysql8 # provider: hibernate-5.0 # jdk: 8 @@ -458,16 +458,16 @@ jobs: jdk: 8 spring-data: spring-data-1.11.x deltaspike: deltaspike-1.7 - - rdbms: mysql - provider: hibernate-4.3 - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 - - rdbms: postgresql - provider: hibernate-4.3 - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 +# - rdbms: mysql +# provider: hibernate-4.3 +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 +# - rdbms: postgresql +# provider: hibernate-4.3 +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 # - rdbms: mysql8 # provider: hibernate-4.3 # jdk: 8 @@ -497,16 +497,16 @@ jobs: jdk: 8 spring-data: spring-data-1.11.x deltaspike: deltaspike-1.7 - - rdbms: mysql - provider: hibernate - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 - - rdbms: postgresql - provider: hibernate - jdk: 8 - spring-data: spring-data-1.11.x - deltaspike: deltaspike-1.7 +# - rdbms: mysql +# provider: hibernate +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 +# - rdbms: postgresql +# provider: hibernate +# jdk: 8 +# spring-data: spring-data-1.11.x +# deltaspike: deltaspike-1.7 # - rdbms: mysql8 # provider: hibernate # jdk: 8 @@ -568,21 +568,29 @@ jobs: - rdbms: h2 provider: hibernate-6.2 jdk: 11 + spring-data: spring-data-3.1.x ################################################ -# hibernate-6.3 +# hibernate-6.4 ################################################ - rdbms: h2 - provider: hibernate-6.3 - jdk: 11 - + provider: hibernate-6.4 + jdk: 17 + spring-data: spring-data-3.2.x ################################################ -# hibernate-6.4 +# hibernate-6.5 ################################################ - rdbms: h2 - provider: hibernate-6.4 - jdk: 11 - + provider: hibernate-6.5 + jdk: 17 + spring-data: spring-data-3.3.x +################################################ +# hibernate-6.6 +################################################ + - rdbms: h2 + provider: hibernate-6.6 + jdk: 17 + spring-data: spring-data-3.3.x ################################################ # LTS JDKs ################################################ @@ -687,4 +695,4 @@ jobs: path: './**/*-reports/' - name: Omit produced artifacts from build cache - run: ./ci/before-cache.sh \ No newline at end of file + run: ./ci/before-cache.sh diff --git a/.github/workflows/latest.yaml b/.github/workflows/latest.yaml index ac4a5fe42a..256820a895 100644 --- a/.github/workflows/latest.yaml +++ b/.github/workflows/latest.yaml @@ -28,17 +28,24 @@ jobs: jdk: 17 ################################################ -# hibernate-6.3 +# hibernate-6.4 ################################################ - rdbms: h2 - component: hibernate-6.3 + component: hibernate-6.4 jdk: 17 ################################################ -# hibernate-6.4 +# hibernate-6.5 ################################################ - rdbms: h2 - component: hibernate-6.4 + component: hibernate-6.5 + jdk: 17 + +################################################ +# hibernate-6.6 +################################################ + - rdbms: h2 + component: hibernate-6.6 jdk: 17 steps: - uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index e432153b3b..66cddee910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,17 @@ Changes that happened in releases ### New features -None yet +* Add support for Spring Data/Boot 3.3 +* Add `date_iso`, `time_iso` and `timestamp_iso` internal functions ### Bug fixes * Fix bug in collection insert code triggered by Hibernate ORM 6.4.2 +* Fix concurrency issue leading to NPE when concurrently creating entity view updaters +* Fix `CAST_STRING` for SQL Server +* Ensure Spring Jackson integration copies `ObjectMapper` bean +* Fix entity view processor generated code for `@MappingSingular` leading to compilation error +* Fix `MULTISET` issues with temporal `BasicUserType` ### Backwards-incompatible changes diff --git a/README.md b/README.md index aad50f5e4d..36702fb49e 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ OpenJPA integration | N/A | (Curren Entity View CDI integration | CDI 1.0 | 1.0, 1.1, 1.2, 2.0, 3.0 Entity View Spring integration | Spring 4.3 | 4.3, 5.0, 5.1, 5.2, 5.3, 6.0 DeltaSpike Data integration | DeltaSpike 1.7 | 1.7, 1.8, 1.9 -Spring Data integration | Spring Data 1.11 | 1.11 - 2.7, 3.1 +Spring Data integration | Spring Data 1.11 | 1.11 - 2.7, 3.1 - 3.3 Spring Data WebMvc integration | Spring Data 1.11, Spring WebMvc 4.3 | Spring Data 1.11 - 2.7, Spring WebMvc 4.3 - 5.3 Spring Data WebFlux integration | Spring Data 2.0, Spring WebFlux 5.0 | Spring Data 2.0 - 2.7, Spring WebFlux 5.0 - 5.3 Spring HATEOAS WebMvc integration| Spring Data 2.2, Spring WebMvc 5.2 | Spring Data 2.3+, Spring WebMvc 5.2+, Spring HATEOAS 1.0+ @@ -468,6 +468,8 @@ By default, a Maven build `mvn clean install` will test against H2 and Hibernate To test a specific combination, you need to activate at least 4 profiles * One of the JPA provider profiles + * `hibernate-6.6` + the `jakarta` profile + * `hibernate-6.5` + the `jakarta` profile * `hibernate-6.4` + the `jakarta` profile * `hibernate-6.3` + the `jakarta` profile * `hibernate-6.2` + the `jakarta` profile diff --git a/ci/build-github-latest.sh b/ci/build-github-latest.sh index 67902ee2ae..f7eb31f458 100755 --- a/ci/build-github-latest.sh +++ b/ci/build-github-latest.sh @@ -6,15 +6,23 @@ java -version if [ "$COMPONENT" == 'hibernate-6.2' ]; then export JPAPROVIDER="hibernate-6.2" + export SPRING_DATA="spring-data-3.1.x" export PROPERTIES="-s $DIR/latest-settings.xml -Dversion.hibernate-6.2=[6.2,6.3.Alpha)" -elif [ "$COMPONENT" == 'hibernate-6.3' ]; then - export JPAPROVIDER="hibernate-6.3" - export PROPERTIES="-s $DIR/latest-settings.xml -Dversion.hibernate-6.3=[6.3,6.4.Alpha)" elif [ "$COMPONENT" == 'hibernate-6.4' ]; then export JPAPROVIDER="hibernate-6.4" + export SPRING_DATA="spring-data-3.2.x" export PROPERTIES="-s $DIR/latest-settings.xml -Dversion.hibernate-6.4=[6.4,6.5.Alpha)" +elif [ "$COMPONENT" == 'hibernate-6.5' ]; then + export JPAPROVIDER="hibernate-6.5" + export SPRING_DATA="spring-data-3.3.x" + export PROPERTIES="-s $DIR/latest-settings.xml -Dversion.hibernate-6.5=[6.5,6.6.Alpha)" +elif [ "$COMPONENT" == 'hibernate-6.6' ]; then + export JPAPROVIDER="hibernate-6.6" + export SPRING_DATA="spring-data-3.3.x" + export PROPERTIES="-s $DIR/latest-settings.xml -Dversion.hibernate-6.6=[6.6,6.6.Alpha)" else export JPAPROVIDER="hibernate-6.2" + export SPRING_DATA="spring-data-3.1.x" export PROPERTIES="-Dversion.spring-data-3.1=[3,4.Alpha) -Dversion.spring-data-3.1-spring=[6.0,6.1.Alpha) -Dversion.spring-data-3.1-spring-boot=[3,4.Alpha)" fi diff --git a/core/testsuite-hibernate6/pom.xml b/core/testsuite-hibernate6/pom.xml index b296593a44..96d15172c7 100644 --- a/core/testsuite-hibernate6/pom.xml +++ b/core/testsuite-hibernate6/pom.xml @@ -716,5 +716,127 @@ + + hibernate-6.5 + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate + hibernate-core + ${version.hibernate-6.5} + + + org.hibernate + hibernate-envers + ${version.hibernate-6.5} + + + org.hibernate + hibernate-testing + ${version.hibernate-6.5} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.persistence + jakarta.persistence-api + ${version.jakarta-jpa-3.1-api} + provided + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-6.5} + provided + + + + + hibernate-6.6 + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate + hibernate-core + ${version.hibernate-6.6} + + + org.hibernate + hibernate-envers + ${version.hibernate-6.6} + + + org.hibernate + hibernate-testing + ${version.hibernate-6.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.persistence + jakarta.persistence-api + ${version.jakarta-jpa-3.1-api} + provided + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-6.6} + provided + + + diff --git a/core/testsuite-jakarta-runner/pom.xml b/core/testsuite-jakarta-runner/pom.xml index 8c8c1302ef..9fb9dc43e0 100644 --- a/core/testsuite-jakarta-runner/pom.xml +++ b/core/testsuite-jakarta-runner/pom.xml @@ -662,6 +662,130 @@ + + hibernate-6.5 + + ${version.jakarta-jpa-3.1-api} + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate.orm + hibernate-core + ${version.hibernate-6.5} + + + org.hibernate.orm + hibernate-envers + ${version.hibernate-6.5} + + + org.hibernate.orm + hibernate-testing + ${version.hibernate-6.5} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate6 + ${project.version} + compile + + + + org.hibernate.orm + hibernate-jpamodelgen + ${version.hibernate-6.5} + provided + + + + + hibernate-6.6 + + ${version.jakarta-jpa-3.1-api} + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate.orm + hibernate-core + ${version.hibernate-6.6} + + + org.hibernate.orm + hibernate-envers + ${version.hibernate-6.6} + + + org.hibernate.orm + hibernate-testing + ${version.hibernate-6.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate6 + ${project.version} + compile + + + + org.hibernate.orm + hibernate-jpamodelgen + ${version.hibernate-6.6} + provided + + + diff --git a/core/testsuite-jakarta/pom.xml b/core/testsuite-jakarta/pom.xml index c3b701603e..062e074d7b 100644 --- a/core/testsuite-jakarta/pom.xml +++ b/core/testsuite-jakarta/pom.xml @@ -74,6 +74,11 @@ org.mockito mockito-core + + ${project.groupId} + blaze-persistence-testsuite-base-assertion + ${project.version} + diff --git a/core/testsuite/pom.xml b/core/testsuite/pom.xml index b8a3102618..94ab33edce 100644 --- a/core/testsuite/pom.xml +++ b/core/testsuite/pom.xml @@ -85,6 +85,11 @@ org.mockito mockito-core + + ${project.groupId} + blaze-persistence-testsuite-base-assertion + ${project.version} + @@ -2419,6 +2424,344 @@ + + hibernate-6.5 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + src/main/hibernate + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + org.hibernate + hibernate-envers + ${version.hibernate-5.6} + + + org.hibernate + hibernate-testing + ${version.hibernate-5.6} + + + com.vladmihalcea + hibernate-types-52 + ${version.hibernate-types} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + compile + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${additional.source.directory} + ${project.build.directory}/generated-sources/metamodel + + + + + add-test-source-hibernate + generate-test-sources + + add-test-source + + + + src/test/hibernate + + + + + + + org.bsc.maven + maven-processor-plugin + + + process + + process + + generate-sources + + + ${additional.source.directory} + + + ${project.build.directory}/generated-sources/metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + + + + + + hibernate-6.6 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + src/main/hibernate + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + org.hibernate + hibernate-envers + ${version.hibernate-5.6} + + + org.hibernate + hibernate-testing + ${version.hibernate-5.6} + + + com.vladmihalcea + hibernate-types-52 + ${version.hibernate-types} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + compile + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${additional.source.directory} + ${project.build.directory}/generated-sources/metamodel + + + + + add-test-source-hibernate + generate-test-sources + + add-test-source + + + + src/test/hibernate + + + + + + + org.bsc.maven + maven-processor-plugin + + + process + + process + + generate-sources + + + ${additional.source.directory} + + + ${project.build.directory}/generated-sources/metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + + + + eclipselink diff --git a/core/testsuite/src/main/hibernate/com/blazebit/persistence/testsuite/treat/entity/TablePerClassSub1.java b/core/testsuite/src/main/hibernate/com/blazebit/persistence/testsuite/treat/entity/TablePerClassSub1.java index bbe7f515bf..e02681bfdc 100644 --- a/core/testsuite/src/main/hibernate/com/blazebit/persistence/testsuite/treat/entity/TablePerClassSub1.java +++ b/core/testsuite/src/main/hibernate/com/blazebit/persistence/testsuite/treat/entity/TablePerClassSub1.java @@ -19,8 +19,6 @@ import com.blazebit.persistence.testsuite.entity.IntIdEntity; import org.hibernate.annotations.ForeignKey; -import javax.persistence.AssociationOverride; -import javax.persistence.AssociationOverrides; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -40,16 +38,6 @@ @Entity @Table(name = "table_per_class_sub_1") -@AssociationOverrides({ - @AssociationOverride( - name = "embeddable.list", - joinTable = @JoinTable(name = "tpces1_list")//, inverseForeignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) - ), - @AssociationOverride( - name = "embeddable.map", - joinTable = @JoinTable(name = "tpces1_map")//, inverseForeignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) - ) -}) public class TablePerClassSub1 extends TablePerClassBase implements Sub1 { private static final long serialVersionUID = 1L; diff --git a/core/testsuite/src/main/hibernate/com/blazebit/persistence/testsuite/treat/entity/TablePerClassSub2.java b/core/testsuite/src/main/hibernate/com/blazebit/persistence/testsuite/treat/entity/TablePerClassSub2.java index b39e7f69b8..0359ecd70e 100644 --- a/core/testsuite/src/main/hibernate/com/blazebit/persistence/testsuite/treat/entity/TablePerClassSub2.java +++ b/core/testsuite/src/main/hibernate/com/blazebit/persistence/testsuite/treat/entity/TablePerClassSub2.java @@ -19,8 +19,6 @@ import com.blazebit.persistence.testsuite.entity.IntIdEntity; import org.hibernate.annotations.ForeignKey; -import javax.persistence.AssociationOverride; -import javax.persistence.AssociationOverrides; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -40,16 +38,6 @@ @Entity @Table(name = "table_per_class_sub_2") -@AssociationOverrides({ - @AssociationOverride( - name = "embeddable.list", - joinTable = @JoinTable(name = "tpces2_list")//, inverseForeignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) - ), - @AssociationOverride( - name = "embeddable.map", - joinTable = @JoinTable(name = "tpces2_map")//, inverseForeignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) - ) -}) public class TablePerClassSub2 extends TablePerClassBase implements Sub2 { private static final long serialVersionUID = 1L; diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/AbstractCoreTest.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/AbstractCoreTest.java index 95a4634a20..e773aff2e1 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/AbstractCoreTest.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/AbstractCoreTest.java @@ -28,6 +28,7 @@ import com.blazebit.persistence.spi.JpaProvider; import com.blazebit.persistence.spi.JpqlFunctionGroup; import com.blazebit.persistence.testsuite.base.AbstractPersistenceTest; +import com.blazebit.persistence.testsuite.base.jpa.assertion.AssertStatementBuilder; import com.blazebit.persistence.testsuite.entity.Document; import com.blazebit.persistence.testsuite.entity.IntIdEntity; import com.blazebit.persistence.testsuite.entity.Person; @@ -631,4 +632,12 @@ protected V transactional(TxWork work) { } } + public AssertStatementBuilder assertOrderedQuerySequence() { + return new AssertStatementBuilder(getRelationalModelAccessor(), QueryInspectorListener.EXECUTED_QUERIES); + } + + public AssertStatementBuilder assertUnorderedQuerySequence() { + return assertOrderedQuerySequence().unordered(); + } + } diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/EmbeddableTestEntitySub.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/EmbeddableTestEntitySub.java index c15e577168..d89fed8a13 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/EmbeddableTestEntitySub.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/EmbeddableTestEntitySub.java @@ -18,10 +18,8 @@ import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; -import javax.persistence.Table; @Entity -@Table(name = "emb_tst_ent_sub") @DiscriminatorValue("sub") public class EmbeddableTestEntitySub extends EmbeddableTestEntity { diff --git a/dist/bom/pom.xml b/dist/bom/pom.xml index 386a0428fc..c1e0ea27e2 100644 --- a/dist/bom/pom.xml +++ b/dist/bom/pom.xml @@ -306,6 +306,12 @@ ${project.version} compile + + com.blazebit + blaze-persistence-integration-spring-data-base-3.3 + ${project.version} + compile + com.blazebit blaze-persistence-integration-spring-data-1.x @@ -366,6 +372,12 @@ ${project.version} compile + + com.blazebit + blaze-persistence-integration-spring-data-3.3 + ${project.version} + compile + com.blazebit blaze-persistence-integration-spring-data-webmvc diff --git a/dist/full/pom.xml b/dist/full/pom.xml index 923345bf5f..43f8a36d81 100644 --- a/dist/full/pom.xml +++ b/dist/full/pom.xml @@ -167,6 +167,10 @@ ${project.groupId} blaze-persistence-integration-spring-data-base-3.1 + + ${project.groupId} + blaze-persistence-integration-spring-data-base-3.3 + ${project.groupId} blaze-persistence-integration-spring-data-1.x @@ -207,6 +211,10 @@ ${project.groupId} blaze-persistence-integration-spring-data-3.1 + + ${project.groupId} + blaze-persistence-integration-spring-data-3.3 + ${project.groupId} blaze-persistence-integration-spring-data-webmvc diff --git a/documentation/src/main/asciidoc/entity-view/manual/en_US/getting_started.adoc b/documentation/src/main/asciidoc/entity-view/manual/en_US/getting_started.adoc index 7c56116048..cb96893f94 100644 --- a/documentation/src/main/asciidoc/entity-view/manual/en_US/getting_started.adoc +++ b/documentation/src/main/asciidoc/entity-view/manual/en_US/getting_started.adoc @@ -168,7 +168,7 @@ If you are using Jakarta APIs and Spring Framework 6+ / Spring Boot 3+, use this ---- com.blazebit - blaze-persistence-integration-spring-data-3.1 + blaze-persistence-integration-spring-data-3.3 ${blaze-persistence.version} compile diff --git a/documentation/src/main/asciidoc/entity-view/manual/en_US/spring_data.adoc b/documentation/src/main/asciidoc/entity-view/manual/en_US/spring_data.adoc index a2acf5febd..ffc133cc44 100644 --- a/documentation/src/main/asciidoc/entity-view/manual/en_US/spring_data.adoc +++ b/documentation/src/main/asciidoc/entity-view/manual/en_US/spring_data.adoc @@ -36,7 +36,7 @@ If you are using Jakarta APIs and Spring Framework 6+ / Spring Boot 3+, use this ---- com.blazebit - blaze-persistence-integration-spring-data-3.1 + blaze-persistence-integration-spring-data-3.3 ${blaze-persistence.version} compile diff --git a/entity-view/impl/src/main/java8/com/blazebit/persistence/view/impl/proxy/UnsafeHelper.java b/entity-view/impl/src/main/java8/com/blazebit/persistence/view/impl/proxy/UnsafeHelper.java index 560f5277e1..b5687fb8f9 100644 --- a/entity-view/impl/src/main/java8/com/blazebit/persistence/view/impl/proxy/UnsafeHelper.java +++ b/entity-view/impl/src/main/java8/com/blazebit/persistence/view/impl/proxy/UnsafeHelper.java @@ -64,7 +64,7 @@ private UnsafeHelper() { lookup = methodHandlesClass.getMethod("lookup"); defineClass = lookupClass.getMethod("defineClass", byte[].class); - } catch (Exception | NoSuchMethodError e) { + } catch (Exception e) { // ignore } GET_MODULE = getModule; diff --git a/entity-view/testsuite-jakarta-runner/pom.xml b/entity-view/testsuite-jakarta-runner/pom.xml index ffa491b8b2..662e00ed3f 100644 --- a/entity-view/testsuite-jakarta-runner/pom.xml +++ b/entity-view/testsuite-jakarta-runner/pom.xml @@ -674,6 +674,130 @@ + + hibernate-6.5 + + ${version.jakarta-jpa-3.1-api} + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate.orm + hibernate-core + ${version.hibernate-6.5} + + + org.hibernate.orm + hibernate-envers + ${version.hibernate-6.5} + + + org.hibernate.orm + hibernate-testing + ${version.hibernate-6.5} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate6 + ${project.version} + compile + + + + org.hibernate.orm + hibernate-jpamodelgen + ${version.hibernate-6.5} + provided + + + + + hibernate-6.6 + + ${version.jakarta-jpa-3.1-api} + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate.orm + hibernate-core + ${version.hibernate-6.6} + + + org.hibernate.orm + hibernate-envers + ${version.hibernate-6.6} + + + org.hibernate.orm + hibernate-testing + ${version.hibernate-6.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate6 + ${project.version} + compile + + + + org.hibernate.orm + hibernate-jpamodelgen + ${version.hibernate-6.6} + provided + + + diff --git a/entity-view/testsuite/pom.xml b/entity-view/testsuite/pom.xml index b5b71518f6..3643bb6f19 100644 --- a/entity-view/testsuite/pom.xml +++ b/entity-view/testsuite/pom.xml @@ -97,6 +97,11 @@ org.mockito mockito-core + + ${project.groupId} + blaze-persistence-testsuite-base-assertion + ${project.version} + @@ -1786,6 +1791,240 @@ + + hibernate-6.5 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + test + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.bsc.maven + maven-processor-plugin + + + process-test + + process-test + + generate-test-sources + + + ${project.build.directory}/test-metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jta} + + + jakarta.activation + jakarta.activation-api + ${version.activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source-metamodel + generate-test-sources + + add-test-source + + + + ${project.build.directory}/test-metamodel + + + + + + + + + + hibernate-6.6 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + test + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.bsc.maven + maven-processor-plugin + + + process-test + + process-test + + generate-test-sources + + + ${project.build.directory}/test-metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jta} + + + jakarta.activation + jakarta.activation-api + ${version.activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source-metamodel + generate-test-sources + + add-test-source + + + + ${project.build.directory}/test-metamodel + + + + + + + + eclipselink diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/subview/treat/model/ContainerItem1.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/subview/treat/model/ContainerItem1.java index 97213c6e0d..ae744af3c1 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/subview/treat/model/ContainerItem1.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/subview/treat/model/ContainerItem1.java @@ -17,14 +17,12 @@ package com.blazebit.persistence.view.testsuite.subview.treat.model; import javax.persistence.Entity; -import javax.persistence.Table; /** * @author Moritz Becker * @since 1.4.0 */ @Entity -@Table(name = "container_item_1") public class ContainerItem1 extends BaseContainerItem { public ContainerItem1() { } diff --git a/entity-view/testsuite/src/test/resources/logging.properties b/entity-view/testsuite/src/test/resources/logging.properties index 50b50baa31..e3d7064ea0 100644 --- a/entity-view/testsuite/src/test/resources/logging.properties +++ b/entity-view/testsuite/src/test/resources/logging.properties @@ -16,6 +16,8 @@ handlers = java.util.logging.ConsoleHandler +net.sf.jsqlparser.parser.level = SEVERE + # For debugging purposes use ALL # We turn it off for TravisCI build with SEVERE org.hibernate.level = SEVERE diff --git a/integration/deltaspike-data/testsuite/pom.xml b/integration/deltaspike-data/testsuite/pom.xml index 101108752f..38daa151cd 100644 --- a/integration/deltaspike-data/testsuite/pom.xml +++ b/integration/deltaspike-data/testsuite/pom.xml @@ -1766,6 +1766,240 @@ + + hibernate-6.5 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + compile + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.bsc.maven + maven-processor-plugin + + + process + + process + + generate-sources + + + ${project.build.directory}/generated-sources/metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jta} + + + jakarta.activation + jakarta.activation-api + ${version.activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/metamodel + + + + + + + + + + hibernate-6.6 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + compile + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.bsc.maven + maven-processor-plugin + + + process + + process + + generate-sources + + + ${project.build.directory}/generated-sources/metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jta} + + + jakarta.activation + jakarta.activation-api + ${version.activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/metamodel + + + + + + + + eclipselink diff --git a/integration/hibernate-6.2/pom.xml b/integration/hibernate-6.2/pom.xml index fee5a309ea..c0d5f2f80e 100644 --- a/integration/hibernate-6.2/pom.xml +++ b/integration/hibernate-6.2/pom.xml @@ -194,5 +194,17 @@ ${version.hibernate-6.4} + + hibernate-6.5 + + ${version.hibernate-6.5} + + + + hibernate-6.6 + + ${version.hibernate-6.6} + + diff --git a/integration/hibernate6-base/pom.xml b/integration/hibernate6-base/pom.xml index 771b8f5a30..527b838c9e 100644 --- a/integration/hibernate6-base/pom.xml +++ b/integration/hibernate6-base/pom.xml @@ -174,5 +174,17 @@ ${version.hibernate-6.4} + + hibernate-6.5 + + ${version.hibernate-6.5} + + + + hibernate-6.6 + + ${version.hibernate-6.6} + + diff --git a/integration/quarkus-3/deployment/pom.xml b/integration/quarkus-3/deployment/pom.xml index f1b13d66bf..31b3b3078c 100644 --- a/integration/quarkus-3/deployment/pom.xml +++ b/integration/quarkus-3/deployment/pom.xml @@ -441,6 +441,226 @@ ${project.build.directory}/test-metamodel + + + com.blazebit.persistence.view.processor.EntityViewAnnotationProcessor + + + + false + version + java.lang.Long + + + + + + + ${project.groupId} + blaze-persistence-entity-view-processor-jakarta + ${project.version} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source-metamodel + generate-test-sources + + add-test-source + + + + ${project.build.directory}/test-metamodel + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + org.jboss.logmanager.LogManager + + true + + + + + + + + hibernate-6.5 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate + + + + org.hibernate.orm + hibernate-core + ${version.hibernate-6.2} + + + + ${project.groupId} + blaze-persistence-entity-view-processor-jakarta + ${project.version} + provided + + + + + + org.bsc.maven + maven-processor-plugin + + + process-test + + process-test + + generate-test-sources + + + ${project.build.directory}/test-metamodel + + + + com.blazebit.persistence.view.processor.EntityViewAnnotationProcessor + + + + false + version + java.lang.Long + + + + + + + ${project.groupId} + blaze-persistence-entity-view-processor-jakarta + ${project.version} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source-metamodel + generate-test-sources + + add-test-source + + + + ${project.build.directory}/test-metamodel + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + org.jboss.logmanager.LogManager + + true + + + + + + + + hibernate-6.6 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate + + + + org.hibernate.orm + hibernate-core + ${version.hibernate-6.2} + + + + ${project.groupId} + blaze-persistence-entity-view-processor-jakarta + ${project.version} + provided + + + + + + org.bsc.maven + maven-processor-plugin + + + process-test + + process-test + + generate-test-sources + + + ${project.build.directory}/test-metamodel + com.blazebit.persistence.view.processor.EntityViewAnnotationProcessor diff --git a/integration/querydsl/testsuite/pom.xml b/integration/querydsl/testsuite/pom.xml index cbcf62a69f..efaa64a65e 100644 --- a/integration/querydsl/testsuite/pom.xml +++ b/integration/querydsl/testsuite/pom.xml @@ -41,6 +41,11 @@ + + ${project.groupId} + blaze-persistence-testsuite-base-assertion + ${project.version} + @@ -1648,6 +1653,240 @@ + + hibernate-6.5 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + compile + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.bsc.maven + maven-processor-plugin + + + process + + process + + generate-sources + + + ${project.build.directory}/generated-sources/metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jta} + + + jakarta.activation + jakarta.activation-api + ${version.activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/metamodel + + + + + + + + + + hibernate-6.6 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + compile + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.bsc.maven + maven-processor-plugin + + + process + + process + + generate-sources + + + ${project.build.directory}/generated-sources/metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jta} + + + jakarta.activation + jakarta.activation-api + ${version.activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/metamodel + + + + + + + + eclipselink diff --git a/integration/spring-data/3.3/pom.xml b/integration/spring-data/3.3/pom.xml new file mode 100644 index 0000000000..6cef0d02fd --- /dev/null +++ b/integration/spring-data/3.3/pom.xml @@ -0,0 +1,167 @@ + + + + blaze-persistence-integration-spring-data-parent + com.blazebit + 1.6.12-SNAPSHOT + ../pom.xml + + 4.0.0 + + Blazebit Persistence Integration Spring-Data 3.3 + blaze-persistence-integration-spring-data-3.3 + + + com.blazebit.persistence.integration.spring.data.impl + + + ${version.spring-data-3.3-spring} + 1.2.1 + 1.3.6 + 17 + + + + + + org.springframework + spring-framework-bom + ${version.spring} + import + pom + + + org.jetbrains.kotlinx + kotlinx-coroutines-bom + ${kotlin-coroutines} + pom + import + + + + + + + org.springframework.data + spring-data-jpa + ${version.spring-data-3.3} + provided + + + + io.reactivex + rxjava-reactive-streams + ${rxjava-reactive-streams} + true + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactive + true + + + + ${project.groupId} + blaze-persistence-entity-view-api-jakarta + + + ${project.groupId} + blaze-persistence-jpa-criteria-api-jakarta + + + ${project.groupId} + blaze-persistence-jpa-criteria-impl-jakarta + + + jakarta.persistence + jakarta.persistence-api + ${version.jakarta-jpa-api} + provided + + + + + ${project.groupId} + blaze-persistence-integration-spring-data-base-3.3 + compile + + + ${project.groupId} + blaze-persistence-integration-entity-view-spring-6.0 + compile + + + ${project.groupId} + blaze-persistence-core-impl-jakarta + runtime + + + ${project.groupId} + blaze-persistence-entity-view-impl-jakarta + runtime + + + + + + org.springframework + spring-test + test + + + junit + junit + test + + + + + + + org.moditect + moditect-maven-plugin + + + add-module-infos + package + + add-module-info + + + + + module ${module.name} { + requires transitive com.blazebit.persistence.integration.spring.data; + exports com.blazebit.persistence.spring.data.impl.query; + exports com.blazebit.persistence.spring.data.impl.repository; + } + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-resource + generate-resources + + add-resource + + + + + target/generated/resources + + + + + + + + + + diff --git a/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/CriteriaQueryParameterBinder.java b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/CriteriaQueryParameterBinder.java new file mode 100644 index 0000000000..4b3f7d2c96 --- /dev/null +++ b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/CriteriaQueryParameterBinder.java @@ -0,0 +1,49 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.impl.query; + +import com.blazebit.persistence.spring.data.base.query.AbstractCriteriaQueryParameterBinder; +import com.blazebit.persistence.spring.data.base.query.JpaParameters; +import com.blazebit.persistence.spring.data.base.query.ParameterMetadataProvider; +import com.blazebit.persistence.spring.data.repository.KeysetPageable; +import com.blazebit.persistence.view.EntityViewManager; +import org.springframework.data.domain.Pageable; + +import jakarta.persistence.EntityManager; + +/** + * Concrete version for Spring Data 2.x. + * + * @author Christian Beikov + * @since 1.3.0 + */ +public class CriteriaQueryParameterBinder extends AbstractCriteriaQueryParameterBinder { + + public CriteriaQueryParameterBinder(EntityManager em, EntityViewManager evm, JpaParameters parameters, Object[] values, Iterable> expressions) { + super(em, evm, parameters, values, expressions); + } + + @Override + protected int getOffset() { + Pageable pageable = getPageable(); + if (pageable instanceof KeysetPageable) { + return ((KeysetPageable) pageable).getIntOffset(); + } else { + return (int) pageable.getOffset(); + } + } +} diff --git a/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/EntityViewAwareRepositoryInformation.java b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/EntityViewAwareRepositoryInformation.java new file mode 100644 index 0000000000..4bdc8d6988 --- /dev/null +++ b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/EntityViewAwareRepositoryInformation.java @@ -0,0 +1,155 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.impl.query; + +import com.blazebit.persistence.spring.data.base.query.EntityViewAwareRepositoryMetadata; +import com.blazebit.persistence.view.EntityViewManager; +import org.springframework.data.repository.core.CrudMethods; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.support.RepositoryFragment; +import org.springframework.data.util.Streamable; +import org.springframework.data.util.TypeInformation; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Set; + +/** + * @author Christian Beikov + * @since 1.2.0 + */ +public class EntityViewAwareRepositoryInformation implements RepositoryInformation, EntityViewAwareRepositoryMetadata { + + private final EntityViewAwareRepositoryMetadata metadata; + private final RepositoryInformation repositoryInformation; + + public EntityViewAwareRepositoryInformation(EntityViewAwareRepositoryMetadata metadata, RepositoryInformation repositoryInformation) { + this.metadata = metadata; + this.repositoryInformation = repositoryInformation; + } + + @Override + public EntityViewManager getEntityViewManager() { + return metadata.getEntityViewManager(); + } + + @Override + public Class getRepositoryBaseClass() { + return repositoryInformation.getRepositoryBaseClass(); + } + + @Override + public boolean hasCustomMethod() { + return repositoryInformation.hasCustomMethod(); + } + + @Override + public boolean isCustomMethod(Method method) { + return repositoryInformation.isCustomMethod(method); + } + + @Override + public boolean isQueryMethod(Method method) { + return repositoryInformation.isQueryMethod(method); + } + + @Override + public boolean isBaseClassMethod(Method method) { + return repositoryInformation.isBaseClassMethod(method); + } + + @Override + public Streamable getQueryMethods() { + return repositoryInformation.getQueryMethods(); + } + + @Override + public Method getTargetClassMethod(Method method) { + return repositoryInformation.getTargetClassMethod(method); + } + + @Override + public Class getIdType() { + return (Class) repositoryInformation.getIdType(); + } + + @Override + public Class getDomainType() { + return repositoryInformation.getDomainType(); + } + + @Override + public Class getEntityViewType() { + return metadata.getEntityViewType(); + } + + @Override + public Class getRepositoryInterface() { + return repositoryInformation.getRepositoryInterface(); + } + + @Override + public Class getReturnedDomainClass(Method method) { + return repositoryInformation.getReturnedDomainClass(method); + } + + @Override + public Class getReturnedEntityViewClass(Method method) { + return metadata.getReturnedEntityViewClass(method); + } + + @Override + public CrudMethods getCrudMethods() { + return repositoryInformation.getCrudMethods(); + } + + @Override + public boolean isPagingRepository() { + return repositoryInformation.isPagingRepository(); + } + + @Override + public Set> getAlternativeDomainTypes() { + return repositoryInformation.getAlternativeDomainTypes(); + } + + public boolean isReactiveRepository() { + return metadata.isReactiveRepository(); + } + + + @Override + public TypeInformation getReturnType(Method method) { + return repositoryInformation.getReturnType(method); + } + + @Override + public TypeInformation getIdTypeInformation() { + return metadata.getIdTypeInformation(); + } + + @Override + public TypeInformation getDomainTypeInformation() { + return metadata.getDomainTypeInformation(); + } + + @Override + public Set> getFragments() { + // TODO: implement + return null; + } +} diff --git a/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/EntityViewAwareRepositoryMetadataImpl.java b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/EntityViewAwareRepositoryMetadataImpl.java new file mode 100644 index 0000000000..5b87351c9c --- /dev/null +++ b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/EntityViewAwareRepositoryMetadataImpl.java @@ -0,0 +1,142 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.impl.query; + +import com.blazebit.persistence.spring.data.base.query.EntityViewAwareRepositoryMetadata; +import com.blazebit.persistence.view.EntityViewManager; +import com.blazebit.persistence.view.metamodel.ManagedViewType; +import org.springframework.data.repository.core.CrudMethods; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.RepositoryFragment; +import org.springframework.data.util.TypeInformation; + +import java.lang.reflect.Method; +import java.util.Set; + +/** + * @author Christian Beikov + * @since 1.2.0 + */ +public class EntityViewAwareRepositoryMetadataImpl implements EntityViewAwareRepositoryMetadata { + + private final RepositoryMetadata metadata; + private final EntityViewManager evm; + private final Class domainType; + private final Class entityViewType; + + public EntityViewAwareRepositoryMetadataImpl(RepositoryMetadata metadata, EntityViewManager evm) { + this.metadata = metadata; + this.evm = evm; + Class domainType = metadata.getDomainType(); + ManagedViewType managedViewType = evm.getMetamodel().managedView(domainType); + if (managedViewType == null) { + this.domainType = domainType; + this.entityViewType = null; + } else { + this.domainType = managedViewType.getEntityClass(); + this.entityViewType = managedViewType.getJavaType(); + } + } + + @Override + public EntityViewManager getEntityViewManager() { + return evm; + } + + @Override + public Class getIdType() { + return metadata.getIdType(); + } + + @Override + public Class getDomainType() { + return domainType; + } + + @Override + public Class getEntityViewType() { + return entityViewType; + } + + @Override + public Class getRepositoryInterface() { + return metadata.getRepositoryInterface(); + } + + @Override + public Class getReturnedDomainClass(Method method) { + Class returnedDomainClass = metadata.getReturnedDomainClass(method); + ManagedViewType managedViewType = evm.getMetamodel().managedView(returnedDomainClass); + if (managedViewType == null) { + return returnedDomainClass; + } else { + return managedViewType.getEntityClass(); + } + } + + @Override + public TypeInformation getReturnType(Method method) { + return metadata.getReturnType(method); + } + + @Override + public Class getReturnedEntityViewClass(Method method) { + Class returnedDomainClass = metadata.getReturnedDomainClass(method); + ManagedViewType managedViewType = evm.getMetamodel().managedView(returnedDomainClass); + if (managedViewType == null) { + return null; + } else { + return managedViewType.getJavaType(); + } + } + + @Override + public CrudMethods getCrudMethods() { + return metadata.getCrudMethods(); + } + + @Override + public boolean isPagingRepository() { + return metadata.isPagingRepository(); + } + + @Override + public Set> getAlternativeDomainTypes() { + return metadata.getAlternativeDomainTypes(); + } + + @Override + public boolean isReactiveRepository() { + return metadata.isReactiveRepository(); + } + + @Override + public TypeInformation getIdTypeInformation() { + return metadata.getIdTypeInformation(); + } + + @Override + public TypeInformation getDomainTypeInformation() { + return metadata.getDomainTypeInformation(); + } + + @Override + public Set> getFragments() { + // TODO: implement + return null; + } +} diff --git a/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/ParameterMetadataProviderImpl.java b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/ParameterMetadataProviderImpl.java new file mode 100644 index 0000000000..e1aba03bcc --- /dev/null +++ b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/ParameterMetadataProviderImpl.java @@ -0,0 +1,316 @@ +/* + * Copyright 2011-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blazebit.persistence.spring.data.impl.query; + +import com.blazebit.persistence.spring.data.base.query.ParameterMetadataProvider; +import org.springframework.data.jpa.provider.PersistenceProvider; +import org.springframework.data.jpa.repository.query.EscapeCharacter; +import org.springframework.data.repository.query.Parameter; +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.parser.Part; +import org.springframework.data.repository.query.parser.Part.IgnoreCaseType; +import org.springframework.data.repository.query.parser.Part.Type; +import org.springframework.expression.Expression; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.ParameterExpression; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * Helper class to allow easy creation of {@link ParameterMetadata}s. + * + * Christian Beikov: Copied while implementing the shared interface to be able to share code between Spring Data integrations for 1.x and 2.x. + * + * @author Oliver Gierke + * @author Thomas Darimont + * @author Mark Paluch + * @author Christoph Strobl + * @author Jens Schauder + * @author Andrey Kovalev + */ +class ParameterMetadataProviderImpl implements ParameterMetadataProvider { + + private final CriteriaBuilder builder; + private final Iterator parameters; + private final List> expressions; + private final Iterator bindableParameterValues; + private final PersistenceProvider persistenceProvider; + private final EscapeCharacter escape; + + /** + * Creates a new {@link ParameterMetadataProviderImpl} from the given {@link CriteriaBuilder} and + * {@link ParametersParameterAccessor} with support for parameter value customizations via {@link PersistenceProvider} + * . + * + * @param builder must not be {@literal null}. + * @param accessor must not be {@literal null}. + * @param provider must not be {@literal null}. + * @param escape + */ + public ParameterMetadataProviderImpl(CriteriaBuilder builder, ParametersParameterAccessor accessor, + PersistenceProvider provider, EscapeCharacter escape) { + this(builder, accessor.iterator(), accessor.getParameters(), provider, escape); + } + + /** + * Creates a new {@link ParameterMetadataProviderImpl} from the given {@link CriteriaBuilder} and {@link Parameters} with + * support for parameter value customizations via {@link PersistenceProvider}. + * + * @param builder must not be {@literal null}. + * @param parameters must not be {@literal null}. + * @param provider must not be {@literal null}. + * @param escape + */ + public ParameterMetadataProviderImpl(CriteriaBuilder builder, Parameters parameters, PersistenceProvider provider, + EscapeCharacter escape) { + this(builder, null, parameters, provider, escape); + } + + /** + * Creates a new {@link ParameterMetadataProviderImpl} from the given {@link CriteriaBuilder} an {@link Iterable} of all + * bindable parameter values, and {@link Parameters} with support for parameter value customizations via + * {@link PersistenceProvider}. + * + * @param builder must not be {@literal null}. + * @param bindableParameterValues may be {@literal null}. + * @param parameters must not be {@literal null}. + * @param provider must not be {@literal null}. + * @param escape + */ + private ParameterMetadataProviderImpl(CriteriaBuilder builder, Iterator bindableParameterValues, + Parameters parameters, PersistenceProvider provider, EscapeCharacter escape) { + + Assert.notNull(builder, "CriteriaBuilder must not be null!"); + Assert.notNull(parameters, "Parameters must not be null!"); + Assert.notNull(provider, "PesistenceProvider must not be null!"); + + this.builder = builder; + this.parameters = parameters.getBindableParameters().iterator(); + this.expressions = new ArrayList<>(); + this.bindableParameterValues = bindableParameterValues; + this.persistenceProvider = provider; + this.escape = escape; + } + + /** + * Returns all {@link ParameterMetadata}s built. + * + * @return the expressions + */ + public List> getExpressions() { + return expressions; + } + + /** + * Builds a new {@link ParameterMetadata} for given {@link Part} and the next {@link Parameter}. + */ + @SuppressWarnings("unchecked") + public ParameterMetadata next(Part part) { + if (!parameters.hasNext()) { + throw new IllegalArgumentException(String.format("No parameter available for part %s.", part)); + } + + Parameter parameter = parameters.next(); + return (ParameterMetadata) next(part, parameter.getType(), parameter); + } + + /** + * Builds a new {@link ParameterMetadata} of the given {@link Part} and type. Forwards the underlying + * {@link Parameters} as well. + * + * @param is the type parameter of the returend {@link ParameterMetadata}. + * @param type must not be {@literal null}. + * @return ParameterMetadata for the next parameter. + */ + @SuppressWarnings("unchecked") + public ParameterMetadata next(Part part, Class type) { + + Parameter parameter = parameters.next(); + Class typeToUse = ClassUtils.isAssignable(type, parameter.getType()) ? parameter.getType() : type; + return (ParameterMetadata) next(part, typeToUse, parameter); + } + + /** + * Builds a new {@link ParameterMetadata} for the given type and name. + * + * @param type parameter for the returned {@link ParameterMetadata}. + * @param part must not be {@literal null}. + * @param type must not be {@literal null}. + * @param parameter providing the name for the returned {@link ParameterMetadata}. + * @return a new {@link ParameterMetadata} for the given type and name. + */ + private ParameterMetadata next(Part part, Class type, Parameter parameter) { + + Assert.notNull(type, "Type must not be null!"); + + /* + * We treat Expression types as Object vales since the real value to be bound as a parameter is determined at query time. + */ + @SuppressWarnings("unchecked") + Class reifiedType = Expression.class.equals(type) ? (Class) Object.class : type; + + Supplier name = () -> parameter.getName() + .orElseThrow(() -> new IllegalArgumentException("o_O Parameter needs to be named")); + + ParameterExpression expression = parameter.isExplicitlyNamed() // + ? builder.parameter(reifiedType, name.get()) // + : builder.parameter(reifiedType); + + Object value = bindableParameterValues == null ? ParameterMetadata.PLACEHOLDER : bindableParameterValues.next(); + + ParameterMetadata metadata = new ParameterMetadataImpl<>(expression, part, value, persistenceProvider, escape); + expressions.add(metadata); + + return metadata; + } + + EscapeCharacter getEscape() { + return escape; + } + + /** + * @author Oliver Gierke + * @author Thomas Darimont + * @author Andrey Kovalev + * @param + */ + static class ParameterMetadataImpl implements ParameterMetadata { + + private final Type type; + private final ParameterExpression expression; + private final PersistenceProvider persistenceProvider; + private final EscapeCharacter escape; + private final boolean ignoreCase; + private final boolean noWildcards; + + /** + * Creates a new {@link ParameterMetadata}. + */ + public ParameterMetadataImpl(ParameterExpression expression, Part part, Object value, + PersistenceProvider provider, EscapeCharacter escape) { + + this.expression = expression; + this.persistenceProvider = provider; + this.type = value == null && Type.SIMPLE_PROPERTY.equals(part.getType()) ? Type.IS_NULL : part.getType(); + this.ignoreCase = IgnoreCaseType.ALWAYS.equals(part.shouldIgnoreCase()); + this.noWildcards = part.getProperty().getLeafProperty().isCollection(); + this.escape = escape; + } + + /** + * Returns the {@link ParameterExpression}. + * + * @return the expression + */ + public ParameterExpression getExpression() { + return expression; + } + + /** + * Returns whether the parameter shall be considered an {@literal IS NULL} parameter. + */ + public boolean isIsNullParameter() { + return Type.IS_NULL.equals(type); + } + + /** + * Prepares the object before it's actually bound to the {@link javax.persistence.Query;}. + * + * @param value must not be {@literal null}. + */ + public Object prepare(Object value) { + + Assert.notNull(value, "Value must not be null!"); + + Object unwrapped = PersistenceProvider.unwrapTypedParameterValue(value); + Class expressionType; + + if (unwrapped == null || (expressionType = expression.getJavaType()) == null) { + return unwrapped; + } + + if (String.class.equals(expression.getJavaType()) && !noWildcards) { + + switch (type) { + case STARTING_WITH: + return String.format("%s%%", escape.escape(unwrapped.toString())); + case ENDING_WITH: + return String.format("%%%s", escape.escape(unwrapped.toString())); + case CONTAINING: + case NOT_CONTAINING: + return String.format("%%%s%%", escape.escape(unwrapped.toString())); + default: + return unwrapped; + } + } + + return Collection.class.isAssignableFrom(expressionType) // + ? upperIfIgnoreCase(ignoreCase, toCollection(value)) // + : value; + } + + /** + * Returns the given argument as {@link Collection} which means it will return it as is if it's a + * {@link Collections}, turn an array into an {@link ArrayList} or simply wrap any other value into a single element + * {@link Collections}. + * + * @param value the value to be converted to a {@link Collection}. + * @return the object itself as a {@link Collection} or a {@link Collection} constructed from the value. + */ + private static Collection toCollection(Object value) { + + if (value == null) { + return null; + } + + if (value instanceof Collection) { + return (Collection) value; + } + + if (ObjectUtils.isArray(value)) { + return Arrays.asList(ObjectUtils.toObjectArray(value)); + } + + return Collections.singleton(value); + } + + @SuppressWarnings("unchecked") + private static Collection upperIfIgnoreCase(boolean ignoreCase, Collection collection) { + + if (!ignoreCase || CollectionUtils.isEmpty(collection)) { + return collection; + } + + return ((Collection) collection).stream() // + .map(it -> it == null // + ? null // + : it.toUpperCase()) // + .collect(Collectors.toList()); + } + } +} diff --git a/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/PartTreeBlazePersistenceQuery.java b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/PartTreeBlazePersistenceQuery.java new file mode 100644 index 0000000000..ac1a5a955a --- /dev/null +++ b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/query/PartTreeBlazePersistenceQuery.java @@ -0,0 +1,256 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.impl.query; + +import com.blazebit.persistence.CriteriaBuilderFactory; +import com.blazebit.persistence.PagedList; +import com.blazebit.persistence.spring.data.base.query.AbstractPartTreeBlazePersistenceQuery; +import com.blazebit.persistence.spring.data.base.query.EntityViewAwareJpaQueryMethod; +import com.blazebit.persistence.spring.data.base.query.JpaParameters; +import com.blazebit.persistence.spring.data.base.query.KeysetAwarePageImpl; +import com.blazebit.persistence.spring.data.base.query.KeysetAwareSliceImpl; +import com.blazebit.persistence.spring.data.base.query.ParameterBinder; +import com.blazebit.persistence.spring.data.base.query.ParameterMetadataProvider; +import com.blazebit.persistence.spring.data.repository.KeysetPageable; +import com.blazebit.persistence.view.EntityViewManager; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.provider.PersistenceProvider; +import org.springframework.data.jpa.repository.query.AbstractJpaQuery; +import org.springframework.data.jpa.repository.query.EscapeCharacter; +import org.springframework.data.jpa.repository.query.Jpa21Utils; +import org.springframework.data.jpa.repository.query.JpaEntityGraph; +import org.springframework.data.jpa.repository.query.JpaParametersParameterAccessor; +import org.springframework.data.jpa.repository.query.JpaQueryExecution; +import org.springframework.data.jpa.repository.support.QueryHints; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.parser.PartTree; +import jakarta.persistence.EntityManager; + +import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + * @author Moritz Becker + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public class PartTreeBlazePersistenceQuery extends AbstractPartTreeBlazePersistenceQuery { + + public PartTreeBlazePersistenceQuery(EntityViewAwareJpaQueryMethod method, EntityManager em, PersistenceProvider persistenceProvider, EscapeCharacter escape, CriteriaBuilderFactory cbf, EntityViewManager evm) { + super(method, em, persistenceProvider, escape, cbf, evm); + } + + @Override + protected ParameterMetadataProvider createParameterMetadataProvider(CriteriaBuilder builder, ParametersParameterAccessor accessor, PersistenceProvider provider, Object escape) { + return new ParameterMetadataProviderImpl(builder, accessor, provider, (EscapeCharacter) escape); + } + + @Override + protected ParameterMetadataProvider createParameterMetadataProvider(CriteriaBuilder builder, JpaParameters parameters, PersistenceProvider provider, Object escape) { + return new ParameterMetadataProviderImpl(builder, parameters, provider, (EscapeCharacter) escape); + } + + @Override + protected JpaQueryExecution getExecution() { + if (getQueryMethod().isSliceQuery()) { + return new PartTreeBlazePersistenceQuery.SlicedExecution(getQueryMethod().getParameters()); + } else if (getQueryMethod().isPageQuery()) { + return new PartTreeBlazePersistenceQuery.PagedExecution(getQueryMethod().getParameters()); + } else if (isDelete()) { + return new PartTreeBlazePersistenceQuery.DeleteExecution(getEntityManager()); + } else if (isExists()) { + return new PartTreeBlazePersistenceQuery.ExistsExecution(); + } else { + return super.getExecution(); + } + } + + /** + * {@link JpaQueryExecution} performing an exists check on the query. + * + * @author Christian Beikov + * @since 1.3.0 + */ + private static class ExistsExecution extends JpaQueryExecution { + + @Override + protected Object doExecute(AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor jpaParametersParameterAccessor) { + return !((PartTreeBlazePersistenceQuery) repositoryQuery).createQuery(jpaParametersParameterAccessor).getResultList().isEmpty(); + } + } + + /** + * Uses the {@link com.blazebit.persistence.PaginatedCriteriaBuilder} API for executing the query. + * + * @author Christian Beikov + * @since 1.2.0 + */ + private static class SlicedExecution extends JpaQueryExecution { + + private final Parameters parameters; + + public SlicedExecution(Parameters parameters) { + this.parameters = parameters; + } + + @Override + @SuppressWarnings("unchecked") + protected Object doExecute(AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor jpaParametersParameterAccessor) { + Query paginatedCriteriaBuilder = ((PartTreeBlazePersistenceQuery) repositoryQuery).createPaginatedQuery(jpaParametersParameterAccessor.getValues(), false); + PagedList resultList = (PagedList) paginatedCriteriaBuilder.getResultList(); + ParameterAccessor accessor = new ParametersParameterAccessor(parameters, jpaParametersParameterAccessor.getValues()); + Pageable pageable = accessor.getPageable(); + + return new KeysetAwareSliceImpl<>(resultList, pageable); + } + } + + /** + * Uses the {@link com.blazebit.persistence.PaginatedCriteriaBuilder} API for executing the query. + * + * @author Christian Beikov + * @since 1.2.0 + */ + private static class PagedExecution extends JpaQueryExecution { + + private final Parameters parameters; + + public PagedExecution(Parameters parameters) { + this.parameters = parameters; + } + + @Override + @SuppressWarnings("unchecked") + protected Object doExecute(AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor jpaParametersParameterAccessor) { + Query paginatedCriteriaBuilder = ((PartTreeBlazePersistenceQuery) repositoryQuery).createPaginatedQuery(jpaParametersParameterAccessor.getValues(), true); + PagedList resultList = (PagedList) paginatedCriteriaBuilder.getResultList(); + Long total = resultList.getTotalSize(); + ParameterAccessor accessor = new ParametersParameterAccessor(parameters, jpaParametersParameterAccessor.getValues()); + Pageable pageable = accessor.getPageable(); + + if (total.equals(0L)) { + return new KeysetAwarePageImpl<>(Collections.emptyList(), total, null, pageable); + } + + return new KeysetAwarePageImpl<>(resultList, pageable); + } + } + + /** + * {@link JpaQueryExecution} removing entities matching the query. + * + * @author Thomas Darimont + * @author Oliver Gierke + * @since 1.6 + */ + static class DeleteExecution extends JpaQueryExecution { + + private final EntityManager em; + + public DeleteExecution(EntityManager em) { + this.em = em; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.query.JpaQueryExecution#doExecute(org.springframework.data.jpa.repository.query.AbstractJpaQuery, java.lang.Object[]) + */ + @Override + protected Object doExecute(AbstractJpaQuery jpaQuery, JpaParametersParameterAccessor jpaParametersParameterAccessor) { + Query query = ((PartTreeBlazePersistenceQuery) jpaQuery).createQuery(jpaParametersParameterAccessor); + List resultList = query.getResultList(); + + for (Object o : resultList) { + em.remove(o); + } + + return jpaQuery.getQueryMethod().isCollectionQuery() ? resultList : resultList.size(); + } + } + + @Override + protected Query doCreateQuery(JpaParametersParameterAccessor jpaParametersParameterAccessor) { + return super.doCreateQuery(jpaParametersParameterAccessor.getValues()); + } + + @Override + protected Query doCreateCountQuery(JpaParametersParameterAccessor jpaParametersParameterAccessor) { + return super.doCreateCountQuery(jpaParametersParameterAccessor.getValues()); + } + + @Override + protected boolean isCountProjection(PartTree tree) { + return tree.isCountProjection(); + } + + @Override + protected boolean isDelete(PartTree tree) { + return tree.isDelete(); + } + + @Override + protected boolean isExists(PartTree tree) { + return tree.isExistsProjection(); + } + + @Override + protected int getOffset(Pageable pageable) { + if (pageable.isPaged()) { + if (pageable instanceof KeysetPageable) { + return ((KeysetPageable) pageable).getIntOffset(); + } else { + return (int) pageable.getOffset(); + } + } + return 0; + } + + @Override + protected int getLimit(Pageable pageable) { + if (pageable.isPaged()) { + return pageable.getPageSize(); + } + return Integer.MAX_VALUE; + } + + @Override + protected ParameterBinder createCriteriaQueryParameterBinder(JpaParameters parameters, Object[] values, List> expressions) { + return new CriteriaQueryParameterBinder(getEntityManager(), evm, parameters, values, expressions); + } + + @Override + protected Map tryGetFetchGraphHints(JpaEntityGraph entityGraph, Class entityType) { + QueryHints fetchGraphHint = Jpa21Utils.getFetchGraphHint( + this.getEntityManager(), + entityGraph, + this.getQueryMethod() + .getEntityInformation() + .getJavaType() + ); + Map map = new HashMap<>(); + fetchGraphHint.forEach(map::put); + return map; + } +} diff --git a/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/BlazePersistenceRepositoryFactory.java b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/BlazePersistenceRepositoryFactory.java new file mode 100644 index 0000000000..cc3c9ec287 --- /dev/null +++ b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/BlazePersistenceRepositoryFactory.java @@ -0,0 +1,600 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.impl.repository; + +import com.blazebit.persistence.CriteriaBuilderFactory; +import com.blazebit.persistence.parser.EntityMetamodel; +import com.blazebit.persistence.spi.ExtendedManagedType; +import com.blazebit.persistence.spring.data.base.query.EntityViewAwareJpaQueryMethod; +import com.blazebit.persistence.spring.data.base.query.EntityViewAwareRepositoryMetadata; +import com.blazebit.persistence.spring.data.base.repository.AbstractEntityViewAwareRepository; +import com.blazebit.persistence.spring.data.base.repository.EntityViewAwareCrudMethodMetadata; +import com.blazebit.persistence.spring.data.base.repository.EntityViewAwareCrudMethodMetadataPostProcessor; +import com.blazebit.persistence.spring.data.impl.query.EntityViewAwareRepositoryInformation; +import com.blazebit.persistence.spring.data.impl.query.EntityViewAwareRepositoryMetadataImpl; +import com.blazebit.persistence.spring.data.impl.query.PartTreeBlazePersistenceQuery; +import com.blazebit.persistence.spring.data.repository.EntityViewReplacingMethodInterceptor; +import com.blazebit.persistence.view.EntityViewManager; +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.interceptor.ExposeInvocationInterceptor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.jpa.provider.PersistenceProvider; +import org.springframework.data.jpa.provider.QueryExtractor; +import org.springframework.data.jpa.repository.query.AbstractJpaQuery; +import org.springframework.data.jpa.repository.query.EscapeCharacter; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.jpa.repository.support.JpaRepositoryFactory; +import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation; +import org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.NamedQueries; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.MethodInvocationValidator; +import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries; +import org.springframework.data.repository.core.support.QueryCreationListener; +import org.springframework.data.repository.core.support.RepositoryComposition; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.data.repository.core.support.RepositoryFragment; +import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener; +import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor; +import org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor; +import org.springframework.data.repository.query.QueryLookupStrategy; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.util.ClassUtils; +import org.springframework.data.repository.util.ReactiveWrapperConverters; +import org.springframework.data.repository.util.ReactiveWrappers; +import org.springframework.lang.Nullable; +import org.springframework.transaction.interceptor.TransactionalProxy; +import org.springframework.util.Assert; +import org.springframework.util.ConcurrentReferenceHashMap; + +import jakarta.persistence.EntityManager; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; + +/** + * Partly copied from {@link JpaRepositoryFactory} to retain functionality but mostly original. + * + * @author Moritz Becker + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public class BlazePersistenceRepositoryFactory extends JpaRepositoryFactory { + + private static final Constructor IMPLEMENTATION_METHOD_EXECUTION_INTERCEPTOR; + private static final Constructor QUERY_EXECUTOR_METHOD_INTERCEPTOR; + + static { + Constructor implementationMethodExecutionInterceptor; + Constructor queryExecutorMethodInterceptor; + try { + implementationMethodExecutionInterceptor = (Constructor) Class.forName("org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor") + .getConstructor( + RepositoryInformation.class, + RepositoryComposition.class, + List.class + ); + implementationMethodExecutionInterceptor.setAccessible(true); + queryExecutorMethodInterceptor = (Constructor) Class.forName("org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor") + .getConstructor( + RepositoryInformation.class, + ProjectionFactory.class, + Optional.class, + NamedQueries.class, + List.class, + List.class + ); + queryExecutorMethodInterceptor.setAccessible(true); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + IMPLEMENTATION_METHOD_EXECUTION_INTERCEPTOR = implementationMethodExecutionInterceptor; + QUERY_EXECUTOR_METHOD_INTERCEPTOR = queryExecutorMethodInterceptor; + } + + private final EntityManager entityManager; + private final CriteriaBuilderFactory cbf; + private final EntityViewManager evm; + private final QueryExtractor extractor; + private final Map repositoryInformationCache; + private final EntityViewReplacingMethodInterceptor entityViewReplacingMethodInterceptor; + private List postProcessors; + private EntityViewAwareCrudMethodMetadataPostProcessor crudMethodMetadataPostProcessor; + private Optional> repositoryBaseClass; + private QueryLookupStrategy.Key queryLookupStrategyKey; + private List> queryPostProcessors; + private List methodInvocationListeners; + private NamedQueries namedQueries; + private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT; + private ClassLoader classLoader; + private QueryMethodEvaluationContextProvider evaluationContextProvider; + private BeanFactory beanFactory; + + private final QueryCollectingQueryCreationListener collectingListener = new QueryCollectingQueryCreationListener(); + + /** + * Creates a new {@link JpaRepositoryFactory}. + * + * @param entityManager must not be {@literal null} + * @param cbf + * @param evm + */ + public BlazePersistenceRepositoryFactory(EntityManager entityManager, CriteriaBuilderFactory cbf, EntityViewManager evm) { + super(entityManager); + this.entityManager = entityManager; + this.extractor = PersistenceProvider.fromEntityManager(entityManager); + this.repositoryInformationCache = new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK); + this.cbf = cbf; + this.evm = evm; + this.namedQueries = PropertiesBasedNamedQueries.EMPTY; + this.evaluationContextProvider = QueryMethodEvaluationContextProvider.DEFAULT; + this.queryPostProcessors = new ArrayList<>(); + this.queryPostProcessors.add(collectingListener); + this.methodInvocationListeners = new ArrayList<>(); + addRepositoryProxyPostProcessor(this.crudMethodMetadataPostProcessor = new EntityViewAwareCrudMethodMetadataPostProcessor()); + this.repositoryBaseClass = Optional.empty(); + this.entityViewReplacingMethodInterceptor = new EntityViewReplacingMethodInterceptor(entityManager, evm); + } + + @Override + public void setQueryLookupStrategyKey(QueryLookupStrategy.Key key) { + this.queryLookupStrategyKey = key; + } + + @Override + public void setNamedQueries(NamedQueries namedQueries) { + this.namedQueries = namedQueries == null ? PropertiesBasedNamedQueries.EMPTY : namedQueries; + } + + @Override + public void addQueryCreationListener(QueryCreationListener listener) { + Assert.notNull(listener, "Listener must not be null!"); + this.queryPostProcessors.add(listener); + } + + @Override + public void addInvocationListener(RepositoryMethodInvocationListener listener) { + Assert.notNull(listener, "Listener must not be null!"); + this.methodInvocationListeners.add(listener); + } + + @Override + public void addRepositoryProxyPostProcessor(RepositoryProxyPostProcessor processor) { + if (crudMethodMetadataPostProcessor != null) { + Assert.notNull(processor, "RepositoryProxyPostProcessor must not be null!"); + super.addRepositoryProxyPostProcessor(processor); + if (postProcessors == null) { + this.postProcessors = new ArrayList<>(); + } + this.postProcessors.add(processor); + } + } + + @Override + protected List getQueryMethods() { + return collectingListener.getQueryMethods(); + } + + @Override + public void setEscapeCharacter(EscapeCharacter escapeCharacter) { + this.escapeCharacter = escapeCharacter; + } + + protected EntityViewAwareCrudMethodMetadata getCrudMethodMetadata() { + return crudMethodMetadataPostProcessor == null ? null : crudMethodMetadataPostProcessor.getCrudMethodMetadata(); + } + + @Override + protected RepositoryMetadata getRepositoryMetadata(Class repositoryInterface) { + return new EntityViewAwareRepositoryMetadataImpl(super.getRepositoryMetadata(repositoryInterface), evm); + } + + @Override + protected RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata, RepositoryComposition.RepositoryFragments fragments) { + return getRepositoryInformation(metadata, super.getRepositoryInformation(metadata, fragments)); + } + + protected RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata, RepositoryInformation repositoryInformation) { + return new EntityViewAwareRepositoryInformation((EntityViewAwareRepositoryMetadata) metadata, repositoryInformation); + } + + @Override + protected void validate(RepositoryMetadata repositoryMetadata) { + super.validate(repositoryMetadata); + + if (cbf.getService(EntityMetamodel.class).getEntity(repositoryMetadata.getDomainType()) == null) { + throw new InvalidDataAccessApiUsageException( + String.format("Cannot implement repository %s when using a non-entity domain type %s. Only types annotated with @Entity are supported!", + repositoryMetadata.getRepositoryInterface().getName(), repositoryMetadata.getDomainType().getName())); + } + } + + @Override + protected JpaRepositoryImplementation getTargetRepository(RepositoryInformation information, EntityManager entityManager) { + if (information instanceof EntityViewAwareRepositoryInformation && information.getRepositoryBaseClass() == EntityViewAwareRepositoryImpl.class) { + // TODO: at some point, we might want to switch to the default if the repository doesn't contain entity views or keyset pagination + JpaEntityInformation entityInformation = getEntityInformation(information.getDomainType()); + AbstractEntityViewAwareRepository entityViewAwareRepository = getTargetRepositoryViaReflection(information, entityInformation, entityManager, cbf, evm, ((EntityViewAwareRepositoryInformation) information).getEntityViewType()); + entityViewAwareRepository.setRepositoryMethodMetadata(getCrudMethodMetadata()); + return (JpaRepositoryImplementation) entityViewAwareRepository; + } + return super.getTargetRepository(information, entityManager); + } + + @Override + protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { + ExtendedManagedType managedType = cbf.getService(EntityMetamodel.class).getManagedType(ExtendedManagedType.class, metadata.getDomainType()); + // Only use the entity view aware repository if the domain type has a single id attribute + if (managedType.getIdAttributes().size() == 1) { + // TODO: at some point, we might want to switch to the default if the repository doesn't contain entity views or keyset pagination + return EntityViewAwareRepositoryImpl.class; + } + return super.getRepositoryBaseClass(metadata); + } + + @Override + protected Optional getQueryLookupStrategy(QueryLookupStrategy.Key key, QueryMethodEvaluationContextProvider evaluationContextProvider) { + switch (key != null ? key : QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND) { + case CREATE: + return Optional.of(new CreateQueryLookupStrategy(entityManager, extractor, escapeCharacter, cbf, evm)); + case USE_DECLARED_QUERY: + return Optional.of(new DelegateQueryLookupStrategy(super.getQueryLookupStrategy(key, evaluationContextProvider).get())); + case CREATE_IF_NOT_FOUND: + return Optional.of(new CreateIfNotFoundQueryLookupStrategy(entityManager, extractor, new CreateQueryLookupStrategy(entityManager, extractor, escapeCharacter, cbf, evm), + new DelegateQueryLookupStrategy(super.getQueryLookupStrategy(QueryLookupStrategy.Key.USE_DECLARED_QUERY, evaluationContextProvider).get()))); + default: + throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key)); + } + } + + private static class CreateQueryLookupStrategy implements QueryLookupStrategy { + + private final EntityManager em; + private final QueryExtractor provider; + private final PersistenceProvider persistenceProvider; + private final EscapeCharacter escapeCharacter; + private final CriteriaBuilderFactory cbf; + private final EntityViewManager evm; + + public CreateQueryLookupStrategy(EntityManager em, QueryExtractor extractor, EscapeCharacter escapeCharacter, CriteriaBuilderFactory cbf, EntityViewManager evm) { + this.em = em; + this.provider = extractor; + this.persistenceProvider = PersistenceProvider.fromEntityManager(em); + this.escapeCharacter = escapeCharacter; + this.cbf = cbf; + this.evm = evm; + } + + @Override + public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, NamedQueries namedQueries) { + try { + // TODO: at some point, we might want to switch to the default if the repository doesn't contain entity views or keyset pagination + return new PartTreeBlazePersistenceQuery(new EntityViewAwareJpaQueryMethod(method, (EntityViewAwareRepositoryMetadata) metadata, factory, provider), em, persistenceProvider, escapeCharacter, cbf, evm); + } catch (RuntimeException e) { + throw new IllegalArgumentException( + String.format("Could not create query metamodel for method %s!", method.toString()), e); + } + } + } + + private static class DelegateQueryLookupStrategy implements QueryLookupStrategy { + + private final QueryLookupStrategy delegate; + + public DelegateQueryLookupStrategy(QueryLookupStrategy delegate) { + this.delegate = delegate; + } + + @Override + public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, NamedQueries namedQueries) { + return delegate.resolveQuery(method, metadata, factory, namedQueries); + } + } + + private static class CreateIfNotFoundQueryLookupStrategy implements QueryLookupStrategy { + + private final EntityManager em; + private final QueryExtractor provider; + private final DelegateQueryLookupStrategy lookupStrategy; + private final CreateQueryLookupStrategy createStrategy; + + public CreateIfNotFoundQueryLookupStrategy(EntityManager em, QueryExtractor extractor, + CreateQueryLookupStrategy createStrategy, DelegateQueryLookupStrategy lookupStrategy) { + this.em = em; + this.provider = extractor; + this.createStrategy = createStrategy; + this.lookupStrategy = lookupStrategy; + } + + @Override + public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, NamedQueries namedQueries) { + try { + RepositoryQuery repositoryQuery = lookupStrategy.resolveQuery(method, metadata, factory, namedQueries); + // Only return something if the RepositoryQuery is not an instance of org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy.NoQuery + // Since we can't refer to the class though, we instead check if the returned class is an instance of AbstractJpaQuery, + // because we know that NoQuery is not matching that + if (repositoryQuery instanceof AbstractJpaQuery) { + return repositoryQuery; + } + } catch (IllegalStateException e) { + // Ignore + } + return createStrategy.resolveQuery(method, metadata, factory, namedQueries); + } + } + + // Mostly copied from RepositoryFactorySupport to be able to use a custom RepositoryInformation implementation + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + super.setBeanClassLoader(classLoader); + this.classLoader = classLoader; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + super.setBeanFactory(beanFactory); + this.beanFactory = beanFactory; + } + + @Override + public void setRepositoryBaseClass(Class repositoryBaseClass) { + super.setRepositoryBaseClass(repositoryBaseClass); + this.repositoryBaseClass = Optional.ofNullable(repositoryBaseClass); + } + + private static final BiFunction REACTIVE_ARGS_CONVERTER = (method, o) -> { + + if (ReactiveWrappers.isAvailable()) { + + Class[] parameterTypes = method.getParameterTypes(); + + Object[] converted = new Object[o.length]; + for (int i = 0; i < parameterTypes.length; i++) { + + Class parameterType = parameterTypes[i]; + Object value = o[i]; + + if (value == null) { + continue; + } + + if (!parameterType.isAssignableFrom(value.getClass()) + && ReactiveWrapperConverters.canConvert(value.getClass(), parameterType)) { + + converted[i] = ReactiveWrapperConverters.toWrapper(value, parameterType); + } else { + converted[i] = value; + } + } + + return converted; + } + + return o; + }; + + /** + * Returns a repository instance for the given interface backed by an instance providing implementation logic for + * custom logic. + * + * @param repositoryInterface must not be {@literal null}. + * @param fragments must not be {@literal null}. + * @return + * @since 2.0 + */ + @SuppressWarnings({ "unchecked" }) + public T getRepository(Class repositoryInterface, RepositoryComposition.RepositoryFragments fragments) { + + Assert.notNull(repositoryInterface, "Repository interface must not be null!"); + Assert.notNull(fragments, "RepositoryFragments must not be null!"); + + RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface); + RepositoryComposition composition = getRepositoryComposition(metadata, fragments); + RepositoryInformation information = getRepositoryInformation(metadata, composition); + + validate(information, composition); + + Object target = getTargetRepository(information); + + // Create proxy + ProxyFactory result = new ProxyFactory(); + result.setTarget(target); + result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class); + + if (MethodInvocationValidator.supports(repositoryInterface)) { + result.addAdvice(new MethodInvocationValidator()); + } + + result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE); + result.addAdvisor(ExposeInvocationInterceptor.ADVISOR); + + postProcessors.forEach(processor -> processor.postProcess(result, information)); + + result.addAdvice(entityViewReplacingMethodInterceptor); + result.addAdvice(new DefaultMethodInvokingMethodInterceptor()); + + ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory); + Optional queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey, + evaluationContextProvider); + result.addAdvice(queryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy, + namedQueries, queryPostProcessors, methodInvocationListeners)); + + composition = composition.append(RepositoryFragment.implemented(target)); + result.addAdvice(implementationMethodExecutionInterceptor(information, composition, methodInvocationListeners)); + + return (T) result.getProxy(classLoader); + } + + private Advice implementationMethodExecutionInterceptor( + RepositoryInformation information, + RepositoryComposition composition, + List methodInvocationListeners) { + try { + return IMPLEMENTATION_METHOD_EXECUTION_INTERCEPTOR.newInstance( + information, + composition, + methodInvocationListeners + ); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private Advice queryExecutorMethodInterceptor(RepositoryInformation repositoryInformation, + ProjectionFactory projectionFactory, Optional queryLookupStrategy, NamedQueries namedQueries, + List> queryPostProcessors, + List methodInvocationListeners) { + try { + return QUERY_EXECUTOR_METHOD_INTERCEPTOR.newInstance( + repositoryInformation, + projectionFactory, + queryLookupStrategy, + namedQueries, + queryPostProcessors, + methodInvocationListeners + ); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + /** + * Validates the given repository interface as well as the given custom implementation. + * + * @param repositoryInformation + * @param composition + */ + private void validate(RepositoryInformation repositoryInformation, RepositoryComposition composition) { + + if (repositoryInformation.hasCustomMethod()) { + + if (composition.isEmpty()) { + + throw new IllegalArgumentException( + String.format("You have custom methods in %s but not provided a custom implementation!", + repositoryInformation.getRepositoryInterface())); + } + + composition.validateImplementation(); + } + + validate(repositoryInformation); + } + + private RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata, + RepositoryComposition composition) { + + RepositoryInformationCacheKey cacheKey = new RepositoryInformationCacheKey(metadata, composition); + + return repositoryInformationCache.computeIfAbsent(cacheKey, key -> { + + Class baseClass = repositoryBaseClass.orElse(getRepositoryBaseClass(metadata)); + + return getRepositoryInformation(metadata, new DefaultRepositoryInformation(metadata, baseClass, composition)); + }); + } + + private RepositoryComposition getRepositoryComposition(RepositoryMetadata metadata, RepositoryComposition.RepositoryFragments fragments) { + + Assert.notNull(metadata, "RepositoryMetadata must not be null!"); + Assert.notNull(fragments, "RepositoryFragments must not be null!"); + + RepositoryComposition composition = getRepositoryComposition(metadata); + RepositoryComposition.RepositoryFragments repositoryAspects = getRepositoryFragments(metadata); + + return composition.append(fragments).append(repositoryAspects); + } + + /** + * Creates {@link RepositoryComposition} based on {@link RepositoryMetadata} for repository-specific method handling. + * + * @param metadata + * @return + */ + private RepositoryComposition getRepositoryComposition(RepositoryMetadata metadata) { + + RepositoryComposition composition = RepositoryComposition.empty(); + + if (metadata.isReactiveRepository()) { + return composition.withMethodLookup(MethodLookups.forReactiveTypes(metadata)) + .withArgumentConverter(REACTIVE_ARGS_CONVERTER); + } + + return composition.withMethodLookup(MethodLookups.forRepositoryTypes(metadata)); + } + + private static class RepositoryInformationCacheKey { + + String repositoryInterfaceName; + final long compositionHash; + + /** + * Creates a new {@link RepositoryInformationCacheKey} for the given {@link RepositoryMetadata} and composition. + * + * @param metadata must not be {@literal null}. + * @param composition must not be {@literal null}. + */ + public RepositoryInformationCacheKey(RepositoryMetadata metadata, RepositoryComposition composition) { + + this.repositoryInterfaceName = metadata.getRepositoryInterface().getName(); + this.compositionHash = composition.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof RepositoryInformationCacheKey)) { + return false; + } + + RepositoryInformationCacheKey that = (RepositoryInformationCacheKey) o; + + if (compositionHash != that.compositionHash) { + return false; + } + return repositoryInterfaceName != null ? repositoryInterfaceName.equals(that.repositoryInterfaceName) : that.repositoryInterfaceName == null; + } + + @Override + public int hashCode() { + int result = repositoryInterfaceName != null ? repositoryInterfaceName.hashCode() : 0; + result = 31 * result + (int) (compositionHash ^ (compositionHash >>> 32)); + return result; + } + } + +} diff --git a/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/BlazePersistenceRepositoryFactoryBean.java b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/BlazePersistenceRepositoryFactoryBean.java new file mode 100644 index 0000000000..92cfce3149 --- /dev/null +++ b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/BlazePersistenceRepositoryFactoryBean.java @@ -0,0 +1,131 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.impl.repository; + +import com.blazebit.persistence.CriteriaBuilderFactory; +import com.blazebit.persistence.view.EntityViewManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport; +import org.springframework.util.Assert; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import java.io.Serializable; + +/** + * @author Moritz Becker + * @author Eugen Mayer + * @since 1.6.9 + */ +public class BlazePersistenceRepositoryFactoryBean, S, ID extends Serializable> extends + TransactionalRepositoryFactoryBeanSupport { + + private EntityManager entityManager; + + @Autowired + private CriteriaBuilderFactory cbf; + + @Autowired + private EntityViewManager evm; + + /** + * Creates a new {@link BlazePersistenceRepositoryFactoryBean}. + */ + protected BlazePersistenceRepositoryFactoryBean() { + super(null); + } + + /** + * Creates a new {@link BlazePersistenceRepositoryFactoryBean} for the given repository interface. + * + * @param repositoryInterface must not be {@literal null}. + */ + protected BlazePersistenceRepositoryFactoryBean(Class repositoryInterface) { + super(repositoryInterface); + } + + public boolean isSingleton() { + return true; + } + + /** + * The {@link EntityManager} to be used. + * + * @param entityManager the entityManager to set + */ + @PersistenceContext + public void setEntityManager(EntityManager entityManager) { + if (this.entityManager == null) { + this.entityManager = entityManager; + } + } + + /* + * (non-Javadoc) + * @see com.blazebit.persistence.spring.data.impl.repository.BlazeRepositoryFactoryBeanSupport#setMappingContext(org.springframework.data.mapping.context.MappingContext) + */ + @Override + public void setMappingContext(MappingContext mappingContext) { + super.setMappingContext(mappingContext); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.support. + * BlazeTransactionalRepositoryFactoryBeanSupport#doCreateRepositoryFactory() + */ + @Override + protected BlazePersistenceRepositoryFactory doCreateRepositoryFactory() { + return createRepositoryFactory(entityManager); + } + + /** + * Returns a {@link RepositoryFactorySupport}. + * + * @param entityManager + * @return + */ + protected BlazePersistenceRepositoryFactory createRepositoryFactory(EntityManager entityManager) { + return new BlazePersistenceRepositoryFactory(entityManager, cbf, evm); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + @Override + public void afterPropertiesSet() { + Assert.notNull(entityManager, "EntityManager must not be null!"); + super.afterPropertiesSet(); + } + + public void setEscapeCharacter(char escapeCharacter) { + // Needed to work with Spring Boot 2.1.4 + } + + public char getEscapeCharacter() { + // Needed to work with Spring Boot 2.1.4 + return '\\'; + } + +} diff --git a/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/DefaultRepositoryInformation.java b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/DefaultRepositoryInformation.java new file mode 100644 index 0000000000..6b1f54b5ae --- /dev/null +++ b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/DefaultRepositoryInformation.java @@ -0,0 +1,320 @@ +/* + * Copyright 2011-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blazebit.persistence.spring.data.impl.repository; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.annotation.QueryAnnotation; +import org.springframework.data.repository.core.CrudMethods; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.data.repository.core.support.RepositoryComposition; +import org.springframework.data.repository.core.support.RepositoryFragment; +import org.springframework.data.util.Streamable; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static org.springframework.data.repository.util.ClassUtils.isGenericRepositoryInterface; +import static org.springframework.util.ReflectionUtils.makeAccessible; + +/** + * Default implementation of {@link RepositoryInformation}. + * + * @author Oliver Gierke + * @author Thomas Darimont + * @author Mark Paluch + * @author Christoph Strobl + * @author Eugen Mayer + * @since 1.6.9 + */ +class DefaultRepositoryInformation implements RepositoryInformation { + + private final Map methodCache = new ConcurrentHashMap<>(); + + private final RepositoryMetadata metadata; + private final Class repositoryBaseClass; + private final RepositoryComposition composition; + private final RepositoryComposition baseComposition; + + /** + * Creates a new {@link DefaultRepositoryMetadata} for the given repository interface and repository base class. + * + * @param metadata must not be {@literal null}. + * @param repositoryBaseClass must not be {@literal null}. + * @param composition must not be {@literal null}. + */ + public DefaultRepositoryInformation(RepositoryMetadata metadata, Class repositoryBaseClass, + RepositoryComposition composition) { + + Assert.notNull(metadata, "Repository metadata must not be null!"); + Assert.notNull(repositoryBaseClass, "Repository base class must not be null!"); + Assert.notNull(composition, "Repository composition must not be null!"); + + this.metadata = metadata; + this.repositoryBaseClass = repositoryBaseClass; + this.composition = composition; + this.baseComposition = RepositoryComposition.of(RepositoryFragment.structural(repositoryBaseClass)) // + .withArgumentConverter(composition.getArgumentConverter()) // + .withMethodLookup(composition.getMethodLookup()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.support.RepositoryMetadata#getDomainClass() + */ + @Override + public Class getDomainType() { + return metadata.getDomainType(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.support.RepositoryMetadata#getIdClass() + */ + @Override + public Class getIdType() { + return metadata.getIdType(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.support.RepositoryInformation#getRepositoryBaseClass() + */ + @Override + public Class getRepositoryBaseClass() { + return this.repositoryBaseClass; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.support.RepositoryInformation#getTargetClassMethod(java.lang.reflect.Method) + */ + @Override + public Method getTargetClassMethod(Method method) { + + if (methodCache.containsKey(method)) { + return methodCache.get(method); + } + + Method result = composition.findMethod(method).orElse(method); + + if (!result.equals(method)) { + return cacheAndReturn(method, result); + } + + return cacheAndReturn(method, baseComposition.findMethod(method).orElse(method)); + } + + private Method cacheAndReturn(Method key, Method value) { + + if (value != null) { + makeAccessible(value); + } + + methodCache.put(key, value); + return value; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.support.RepositoryInformation#getQueryMethods() + */ + @Override + public Streamable getQueryMethods() { + + Set result = new HashSet<>(); + + for (Method method : getRepositoryInterface().getMethods()) { + method = ClassUtils.getMostSpecificMethod(method, getRepositoryInterface()); + if (isQueryMethodCandidate(method)) { + result.add(method); + } + } + + return Streamable.of(Collections.unmodifiableSet(result)); + } + + /** + * Checks whether the given method is a query method candidate. + * + * @param method + * @return + */ + private boolean isQueryMethodCandidate(Method method) { + return !method.isBridge() && !method.isDefault() // + && !Modifier.isStatic(method.getModifiers()) // + && (isQueryAnnotationPresentOn(method) || !isCustomMethod(method) && !isBaseClassMethod(method)); + } + + /** + * Checks whether the given method contains a custom store specific query annotation annotated with + * {@link QueryAnnotation}. The method-hierarchy is also considered in the search for the annotation. + * + * @param method + * @return + */ + private boolean isQueryAnnotationPresentOn(Method method) { + + return AnnotationUtils.findAnnotation(method, QueryAnnotation.class) != null; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.support.RepositoryInformation#isCustomMethod(java.lang.reflect.Method) + */ + @Override + public boolean isCustomMethod(Method method) { + return composition.findMethod(method).isPresent(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.RepositoryInformation#isQueryMethod(java.lang.reflect.Method) + */ + @Override + public boolean isQueryMethod(Method method) { + return getQueryMethods().stream().anyMatch(it -> it.equals(method)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.RepositoryInformation#isBaseClassMethod(java.lang.reflect.Method) + */ + @Override + public boolean isBaseClassMethod(Method method) { + + Assert.notNull(method, "Method must not be null!"); + return baseComposition.findMethod(method).isPresent(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.support.RepositoryInformation#hasCustomMethod() + */ + @Override + public boolean hasCustomMethod() { + + Class repositoryInterface = getRepositoryInterface(); + + // No detection required if no typing interface was configured + if (isGenericRepositoryInterface(repositoryInterface)) { + return false; + } + + for (Method method : repositoryInterface.getMethods()) { + if (isCustomMethod(method) && !isBaseClassMethod(method)) { + return true; + } + } + + return false; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.RepositoryMetadata#getRepositoryInterface() + */ + @Override + public Class getRepositoryInterface() { + return metadata.getRepositoryInterface(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.RepositoryMetadata#getReturnedDomainClass(java.lang.reflect.Method) + */ + @Override + public Class getReturnedDomainClass(Method method) { + return metadata.getReturnedDomainClass(method); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.RepositoryMetadata#getReturnType(java.lang.reflect.Method) + */ + @Override + public TypeInformation getReturnType(Method method) { + return this.metadata.getReturnType(method); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.RepositoryMetadata#getIdTypeInformation() + */ + @Override + public TypeInformation getIdTypeInformation() { + return metadata.getIdTypeInformation(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.RepositoryMetadata#getDomainTypeInformation() + */ + @Override + public TypeInformation getDomainTypeInformation() { + return metadata.getDomainTypeInformation(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.RepositoryMetadata#getCrudMethods() + */ + @Override + public CrudMethods getCrudMethods() { + return metadata.getCrudMethods(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.RepositoryMetadata#isPagingRepository() + */ + @Override + public boolean isPagingRepository() { + return metadata.isPagingRepository(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.RepositoryMetadata#getAlternativeDomainTypes() + */ + @Override + public Set> getAlternativeDomainTypes() { + return metadata.getAlternativeDomainTypes(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.RepositoryMetadata#isReactiveRepository() + */ + @Override + public boolean isReactiveRepository() { + return metadata.isReactiveRepository(); + } + + @Override + public Set> getFragments() { + return null; + } +} diff --git a/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/EntityViewAwareRepositoryImpl.java b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/EntityViewAwareRepositoryImpl.java new file mode 100644 index 0000000000..dca8727240 --- /dev/null +++ b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/EntityViewAwareRepositoryImpl.java @@ -0,0 +1,111 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.impl.repository; + +import com.blazebit.persistence.CriteriaBuilderFactory; +import com.blazebit.persistence.spring.data.base.repository.AbstractEntityViewAwareRepository; +import com.blazebit.persistence.spring.data.repository.EntityViewRepository; +import com.blazebit.persistence.spring.data.repository.KeysetPageable; +import com.blazebit.persistence.view.EntityViewManager; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.query.Jpa21Utils; +import org.springframework.data.jpa.repository.query.JpaEntityGraph; +import org.springframework.data.jpa.repository.support.CrudMethodMetadata; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.NoResultException; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +@Transactional(readOnly = true) +public class EntityViewAwareRepositoryImpl extends AbstractEntityViewAwareRepository implements JpaRepositoryImplementation, EntityViewRepository/*, EntityViewSpecificationExecutor*/ { // Can't implement that interface because of clashing methods + + public EntityViewAwareRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager, CriteriaBuilderFactory cbf, EntityViewManager evm, Class entityViewClass) { + super(entityInformation, entityManager, cbf, evm, (Class) entityViewClass); + } + + @Override + protected Map tryGetFetchGraphHints(EntityManager entityManager, JpaEntityGraph entityGraph, Class entityType) { + Map map = new HashMap<>(); + Jpa21Utils.getFetchGraphHint(entityManager, entityGraph, entityType).forEach( map::put ); + return map; + } + + @Override + public Optional findOne(Example example) { + try { + return Optional.of(getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), (Sort) null).getSingleResult()); + } catch (NoResultException e) { + return Optional.empty(); + } + } + + @Override + public Optional findOne(Specification spec) { + try { + return Optional.of((E) getQuery(spec, (Sort) null).getSingleResult()); + } catch (NoResultException e) { + return Optional.empty(); + } + } + + @Override + public Optional findById(ID id) { + return Optional.ofNullable((E) findOne(id)); + } + + public E getById(ID id) { + return (E) getReference(id); + } + + @Override + public void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) { + // Ignore the Spring data version of the CrudMethodMetadata + } + + @Override + protected int getOffset(Pageable pageable) { + if (pageable instanceof KeysetPageable) { + return ((KeysetPageable) pageable).getIntOffset(); + } else { + return (int) pageable.getOffset(); + } + } + + @Override + public long count(Example example) { + return super.count(example); + } + + @Override + public boolean exists(Example example) { + return super.exists(example); + } +} diff --git a/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/MethodLookups.java b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/MethodLookups.java new file mode 100644 index 0000000000..b92f2d24ea --- /dev/null +++ b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/MethodLookups.java @@ -0,0 +1,443 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blazebit.persistence.spring.data.impl.repository; + +import org.springframework.core.MethodParameter; +import org.springframework.core.ResolvableType; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.MethodLookup; +import org.springframework.data.repository.core.support.MethodLookup.MethodPredicate; +import org.springframework.data.repository.util.QueryExecutionConverters; +import org.springframework.data.repository.util.ReactiveWrapperConverters; +import org.springframework.util.Assert; + +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.springframework.core.GenericTypeResolver.resolveParameterType; + +/** + * Implementations of method lookup functions. + * + * @author Mark Paluch + * @author Oliver Gierke + * @since 2.0 + */ +interface MethodLookups { + + /** + * Direct method lookup filtering on exact method name, parameter count and parameter types. + * + * @return direct method lookup. + */ + public static MethodLookup direct() { + + MethodPredicate direct = (invoked, candidate) -> candidate.getName().equals(invoked.getName()) + && candidate.getParameterCount() == invoked.getParameterCount() + && Arrays.equals(candidate.getParameterTypes(), invoked.getParameterTypes()) + && candidate.getReturnType().equals(invoked.getMethod().getReturnType()); + + return () -> Collections.singletonList(direct); + } + + /** + * Repository type-aware method lookup composed of {@link #direct()} and {@link RepositoryAwareMethodLookup}. + *

+ * Repository-aware lookups resolve generic types from the repository declaration to verify assignability to Id/domain + * types. This lookup also permits assignable method signatures but prefers {@link #direct()} matches. + * + * @param repositoryMetadata must not be {@literal null}. + * @return the composed, repository-aware method lookup. + * @see #direct() + */ + public static MethodLookup forRepositoryTypes(RepositoryMetadata repositoryMetadata) { + return direct().and(new RepositoryAwareMethodLookup(repositoryMetadata)); + } + + /** + * Repository type-aware method lookup composed of {@link #direct()} and {@link ReactiveTypeInteropMethodLookup}. + *

+ * This method lookup considers adaptability of reactive types in method signatures. Repository methods accepting a + * reactive type can be possibly called with a different reactive type if the reactive type can be adopted to the + * target type. This lookup also permits assignable method signatures and resolves repository id/entity types but + * prefers {@link #direct()} matches. + * + * @param repositoryMetadata must not be {@literal null}. + * @return the composed, repository-aware method lookup. + * @see #direct() + * @see #forRepositoryTypes(RepositoryMetadata) + */ + public static MethodLookup forReactiveTypes(RepositoryMetadata repositoryMetadata) { + return direct().and(new ReactiveTypeInteropMethodLookup(repositoryMetadata)); + } + + /** + * Default {@link MethodLookup} considering repository Id and entity types permitting calls to methods with assignable + * arguments. + * + * @author Mark Paluch + */ + static class RepositoryAwareMethodLookup implements MethodLookup { + + @SuppressWarnings("rawtypes") private static final TypeVariable>[] PARAMETERS = Repository.class + .getTypeParameters(); + private static final String DOMAIN_TYPE_NAME = PARAMETERS[0].getName(); + private static final String ID_TYPE_NAME = PARAMETERS[1].getName(); + + private final ResolvableType entityType, idType; + private final Class repositoryInterface; + + /** + * Creates a new {@link RepositoryAwareMethodLookup} for the given {@link RepositoryMetadata}. + * + * @param repositoryMetadata must not be {@literal null}. + */ + public RepositoryAwareMethodLookup(RepositoryMetadata repositoryMetadata) { + + Assert.notNull(repositoryMetadata, "Repository metadata must not be null!"); + + this.entityType = ResolvableType.forClass(repositoryMetadata.getDomainType()); + this.idType = ResolvableType.forClass(repositoryMetadata.getIdType()); + this.repositoryInterface = repositoryMetadata.getRepositoryInterface(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.MethodLookup#getLookups() + */ + @Override + public List getLookups() { + + MethodPredicate detailedComparison = (invoked, candidate) -> Optional.of(candidate) + .filter(baseClassMethod -> baseClassMethod.getName().equals(invoked.getName()))// Right name + .filter(baseClassMethod -> baseClassMethod.getParameterCount() == invoked.getParameterCount()) + .filter(baseClassMethod -> parametersMatch(invoked.getMethod(), baseClassMethod))// All parameters match + .isPresent(); + + return Collections.singletonList(detailedComparison); + } + + /** + * Checks whether the given parameter type matches the generic type of the given parameter. Thus when {@literal PK} + * is declared, the method ensures that given method parameter is the primary key type declared in the given + * repository interface e.g. + * + * @param variable must not be {@literal null}. + * @param parameterType must not be {@literal null}. + * @return + */ + protected boolean matchesGenericType(TypeVariable variable, ResolvableType parameterType) { + + GenericDeclaration declaration = variable.getGenericDeclaration(); + + if (declaration instanceof Class) { + + if (ID_TYPE_NAME.equals(variable.getName()) && parameterType.isAssignableFrom(idType)) { + return true; + } + + Type boundType = variable.getBounds()[0]; + String referenceName = boundType instanceof TypeVariable ? boundType.toString() : variable.toString(); + + return DOMAIN_TYPE_NAME.equals(referenceName) && parameterType.isAssignableFrom(entityType); + } + + for (Type type : variable.getBounds()) { + if (ResolvableType.forType(type).isAssignableFrom(parameterType)) { + return true; + } + } + + return false; + } + + /** + * Checks the given method's parameters to match the ones of the given base class method. Matches generic arguments + * against the ones bound in the given repository interface. + * + * @param invokedMethod + * @param candidate + * @return + */ + private boolean parametersMatch(Method invokedMethod, Method candidate) { + + Class[] methodParameterTypes = invokedMethod.getParameterTypes(); + Type[] genericTypes = candidate.getGenericParameterTypes(); + Class[] types = candidate.getParameterTypes(); + + for (int i = 0; i < genericTypes.length; i++) { + + Type genericType = genericTypes[i]; + Class type = types[i]; + MethodParameter parameter = new MethodParameter(invokedMethod, i); + Class parameterType = resolveParameterType(parameter, repositoryInterface); + + if (genericType instanceof TypeVariable) { + + if (!matchesGenericType((TypeVariable) genericType, ResolvableType.forMethodParameter(parameter))) { + return false; + } + + continue; + } + + if (types[i].equals(parameterType)) { + continue; + } + + if (!type.isAssignableFrom(parameterType) || !type.equals(methodParameterTypes[i])) { + return false; + } + } + + return true; + } + } + + /** + * Extension to {@link RepositoryAwareMethodLookup} considering reactive type adoption and entity types permitting + * calls to methods with assignable arguments. + * + * @author Mark Paluch + */ + static class ReactiveTypeInteropMethodLookup extends RepositoryAwareMethodLookup { + + private final RepositoryMetadata repositoryMetadata; + + public ReactiveTypeInteropMethodLookup(RepositoryMetadata repositoryMetadata) { + + super(repositoryMetadata); + this.repositoryMetadata = repositoryMetadata; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.MethodLookups.RepositoryAwareMethodLookup#getLookups() + */ + @Override + public List getLookups() { + + MethodPredicate convertibleComparison = (invokedMethod, candidate) -> { + + List>> suppliers = new ArrayList<>(); + + if (usesParametersWithReactiveWrappers(invokedMethod.getMethod())) { + suppliers.add(() -> getMethodCandidate(invokedMethod, candidate, assignableWrapperMatch())); // + suppliers.add(() -> getMethodCandidate(invokedMethod, candidate, wrapperConversionMatch())); + } + + return suppliers.stream().anyMatch(supplier -> supplier.get().isPresent()); + }; + + MethodPredicate detailedComparison = (invokedMethod, candidate) -> getMethodCandidate(invokedMethod, candidate, + matchParameterOrComponentType(repositoryMetadata.getRepositoryInterface())).isPresent(); + + return Arrays.asList(convertibleComparison, detailedComparison); + } + + /** + * {@link Predicate} to check parameter assignability between a parameters in which the declared parameter may be + * wrapped but supports unwrapping. Usually types like {@link Optional} or {@link Stream}. + * + * @param repositoryInterface + * @return + * @see QueryExecutionConverters + * @see #matchesGenericType + */ + private Predicate matchParameterOrComponentType(Class repositoryInterface) { + + return (parameterCriteria) -> { + + Class parameterType = resolveParameterType(parameterCriteria.getDeclared(), repositoryInterface); + Type genericType = parameterCriteria.getGenericBaseType(); + + if (genericType instanceof TypeVariable) { + + if (!matchesGenericType((TypeVariable) genericType, + ResolvableType.forMethodParameter(parameterCriteria.getDeclared()))) { + return false; + } + } + + return parameterCriteria.getBaseType().isAssignableFrom(parameterType) + && parameterCriteria.isAssignableFromDeclared(); + }; + } + + /** + * Checks whether the type is a wrapper without unwrapping support. Reactive wrappers don't like to be unwrapped. + * + * @param parameterType must not be {@literal null}. + * @return + */ + private static boolean isNonUnwrappingWrapper(Class parameterType) { + + Assert.notNull(parameterType, "Parameter type must not be null!"); + + return QueryExecutionConverters.supports(parameterType) + && !QueryExecutionConverters.supportsUnwrapping(parameterType); + } + + /** + * Returns whether the given {@link Method} uses a reactive wrapper type as parameter. + * + * @param method must not be {@literal null}. + * @return + */ + private static boolean usesParametersWithReactiveWrappers(Method method) { + + Assert.notNull(method, "Method must not be null!"); + + return Arrays.stream(method.getParameterTypes())// + .anyMatch(ReactiveTypeInteropMethodLookup::isNonUnwrappingWrapper); + } + + /** + * Returns a candidate method from the base class for the given one or the method given in the first place if none + * one the base class matches. + * + * @param invokedMethod must not be {@literal null}. + * @param candidate must not be {@literal null}. + * @param predicate must not be {@literal null}. + * @return + */ + private static Optional getMethodCandidate(InvokedMethod invokedMethod, Method candidate, + Predicate predicate) { + + return Optional.of(candidate)// + .filter(it -> invokedMethod.getName().equals(it.getName()))// + .filter(it -> invokedMethod.getParameterCount() == it.getParameterCount())// + .filter(it -> parametersMatch(it, invokedMethod.getMethod(), predicate)); + } + + /** + * Checks the given method's parameters to match the ones of the given base class method. Matches generic arguments + * against the ones bound in the given repository interface. + * + * @param baseClassMethod must not be {@literal null}. + * @param declaredMethod must not be {@literal null}. + * @param predicate must not be {@literal null}. + * @return + */ + private static boolean parametersMatch(Method baseClassMethod, Method declaredMethod, + Predicate predicate) { + + return methodParameters(baseClassMethod, declaredMethod).allMatch(predicate); + } + + /** + * {@link Predicate} to check whether a method parameter is a {@link #isNonUnwrappingWrapper(Class)} and can be + * converted into a different wrapper. Usually rx.Observable to {@link org.reactivestreams.Publisher} + * conversion. + * + * @return + */ + private static Predicate wrapperConversionMatch() { + + return (parameterCriteria) -> isNonUnwrappingWrapper(parameterCriteria.getBaseType()) // + && isNonUnwrappingWrapper(parameterCriteria.getDeclaredType()) // + && ReactiveWrapperConverters.canConvert(parameterCriteria.getDeclaredType(), parameterCriteria.getBaseType()); + } + + /** + * {@link Predicate} to check parameter assignability between a {@link #isNonUnwrappingWrapper(Class)} parameter and + * a declared parameter. Usually reactor.core.publisher.Flux vs. {@link org.reactivestreams.Publisher} + * conversion. + * + * @return + */ + private static Predicate assignableWrapperMatch() { + + return (parameterCriteria) -> isNonUnwrappingWrapper(parameterCriteria.getBaseType()) // + && isNonUnwrappingWrapper(parameterCriteria.getDeclaredType()) // + && parameterCriteria.getBaseType().isAssignableFrom(parameterCriteria.getDeclaredType()); + } + + private static Stream methodParameters(Method first, Method second) { + + Assert.isTrue(first.getParameterCount() == second.getParameterCount(), "Method parameter count must be equal!"); + + return IntStream.range(0, first.getParameterCount()) // + .mapToObj(index -> ParameterOverrideCriteria.of(new MethodParameter(first, index), + new MethodParameter(second, index))); + } + + /** + * Criterion to represent {@link MethodParameter}s from a base method and its declared (overridden) method. Method + * parameters indexes are correlated so {@link ParameterOverrideCriteria} applies only to methods with same + * parameter count. + */ + static class ParameterOverrideCriteria { + + private final MethodParameter base; + private final MethodParameter declared; + + private ParameterOverrideCriteria(MethodParameter base, MethodParameter declared) { + this.base = base; + this.declared = declared; + } + + public static ParameterOverrideCriteria of(MethodParameter base, MethodParameter declared) { + return new ParameterOverrideCriteria(base, declared); + } + + public MethodParameter getBase() { + return base; + } + + public MethodParameter getDeclared() { + return declared; + } + + /** + * @return base method parameter type. + */ + public Class getBaseType() { + return base.getParameterType(); + } + + /** + * @return generic base method parameter type. + */ + public Type getGenericBaseType() { + return base.getGenericParameterType(); + } + + /** + * @return declared method parameter type. + */ + public Class getDeclaredType() { + return declared.getParameterType(); + } + + public boolean isAssignableFromDeclared() { + return getBaseType().isAssignableFrom(getDeclaredType()); + } + } + } +} diff --git a/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/QueryCollectingQueryCreationListener.java b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/QueryCollectingQueryCreationListener.java new file mode 100644 index 0000000000..087902739c --- /dev/null +++ b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/QueryCollectingQueryCreationListener.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.impl.repository; + + +import org.springframework.data.repository.core.support.QueryCreationListener; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.RepositoryQuery; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Moritz Becker + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public class QueryCollectingQueryCreationListener implements QueryCreationListener { + + private final List queryMethods = new ArrayList<>(); + + @Override + public void onCreation(RepositoryQuery query) { + this.queryMethods.add(query.getQueryMethod()); + } + + public List getQueryMethods() { + return queryMethods; + } +} diff --git a/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/QueryExecutionResultHandler.java b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/QueryExecutionResultHandler.java new file mode 100644 index 0000000000..d20d6d58e8 --- /dev/null +++ b/integration/spring-data/3.3/src/main/java/com/blazebit/persistence/spring/data/impl/repository/QueryExecutionResultHandler.java @@ -0,0 +1,296 @@ +/* + * Copyright 2014-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blazebit.persistence.spring.data.impl.repository; + +import org.springframework.core.CollectionFactory; +import org.springframework.core.MethodParameter; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.repository.util.QueryExecutionConverters; +import org.springframework.data.repository.util.ReactiveWrapperConverters; +import org.springframework.data.util.NullableWrapper; +import org.springframework.data.util.Streamable; +import org.springframework.lang.Nullable; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * Simple domain service to convert query results into a dedicated type. + * + * @author Oliver Gierke + * @author Mark Paluch + * @author Jens Schauder + * @author Eugen Mayer + * @since 1.6.9 + */ +class QueryExecutionResultHandler { + + private static final TypeDescriptor WRAPPER_TYPE = TypeDescriptor.valueOf(NullableWrapper.class); + + private final GenericConversionService conversionService; + + private final Object mutex = new Object(); + + // concurrent access guarded by mutex. + private Map descriptorCache = Collections.emptyMap(); + + /** + * Creates a new {@link QueryExecutionResultHandler}. + */ + public QueryExecutionResultHandler(GenericConversionService conversionService) { + this.conversionService = conversionService; + } + + /** + * Post-processes the given result of a query invocation to match the return type of the given method. + * + * @param result can be {@literal null}. + * @param method must not be {@literal null}. + * @return + */ + @Nullable + public Object postProcessInvocationResult(@Nullable Object result, Method method) { + + if (!processingRequired(result, method.getReturnType())) { + return result; + } + + ReturnTypeDescriptor descriptor = getOrCreateReturnTypeDescriptor(method); + + return postProcessInvocationResult(result, 0, descriptor); + } + + private ReturnTypeDescriptor getOrCreateReturnTypeDescriptor(Method method) { + + Map descriptorCache = this.descriptorCache; + ReturnTypeDescriptor descriptor = descriptorCache.get(method); + + if (descriptor == null) { + + descriptor = ReturnTypeDescriptor.of(method); + + Map updatedDescriptorCache; + + if (descriptorCache.isEmpty()) { + updatedDescriptorCache = Collections.singletonMap(method, descriptor); + } else { + updatedDescriptorCache = new HashMap<>(descriptorCache.size() + 1, 1); + updatedDescriptorCache.putAll(descriptorCache); + updatedDescriptorCache.put(method, descriptor); + + } + + synchronized (mutex) { + this.descriptorCache = updatedDescriptorCache; + } + } + + return descriptor; + } + + /** + * Post-processes the given result of a query invocation to the given type. + * + * @param result can be {@literal null}. + * @param nestingLevel + * @param descriptor must not be {@literal null}. + * @return + */ + @Nullable + Object postProcessInvocationResult(@Nullable Object result, int nestingLevel, ReturnTypeDescriptor descriptor) { + + TypeDescriptor returnTypeDescriptor = descriptor.getReturnTypeDescriptor(nestingLevel); + + if (returnTypeDescriptor == null) { + return result; + } + + Class expectedReturnType = returnTypeDescriptor.getType(); + + result = unwrapOptional(result); + + if (QueryExecutionConverters.supports(expectedReturnType)) { + + // For a wrapper type, try nested resolution first + result = postProcessInvocationResult(result, nestingLevel + 1, descriptor); + + if (conversionRequired(WRAPPER_TYPE, returnTypeDescriptor)) { + return conversionService.convert(new NullableWrapper(result), returnTypeDescriptor); + } + + if (result != null) { + + TypeDescriptor source = TypeDescriptor.valueOf(result.getClass()); + + if (conversionRequired(source, returnTypeDescriptor)) { + return conversionService.convert(result, returnTypeDescriptor); + } + } + } + + if (result != null) { + + if (ReactiveWrapperConverters.supports(expectedReturnType)) { + return ReactiveWrapperConverters.toWrapper(result, expectedReturnType); + } + + if (result instanceof Collection) { + + TypeDescriptor elementDescriptor = descriptor.getReturnTypeDescriptor(nestingLevel + 1); + boolean requiresConversion = requiresConversion((Collection) result, expectedReturnType, elementDescriptor); + + if (!requiresConversion) { + return result; + } + } + + TypeDescriptor resultDescriptor = TypeDescriptor.forObject(result); + return conversionService.canConvert(resultDescriptor, returnTypeDescriptor) + ? conversionService.convert(result, returnTypeDescriptor) + : result; + } + + return Map.class.equals(expectedReturnType) // + ? CollectionFactory.createMap(expectedReturnType, 0) // + : null; + + } + private boolean requiresConversion(Collection collection, Class expectedReturnType, + @Nullable TypeDescriptor elementDescriptor) { + + if (Streamable.class.isAssignableFrom(expectedReturnType) || !expectedReturnType.isInstance(collection)) { + return true; + } + + if (elementDescriptor == null || !Iterable.class.isAssignableFrom(expectedReturnType)) { + return false; + } + + Class type = elementDescriptor.getType(); + + for (Object o : collection) { + + if (!type.isInstance(o)) { + return true; + } + } + + return false; + } + + /** + * Returns whether the configured {@link ConversionService} can convert between the given {@link TypeDescriptor}s and + * the conversion will not be a no-op. + * + * @param source + * @param target + * @return + */ + private boolean conversionRequired(TypeDescriptor source, TypeDescriptor target) { + + return conversionService.canConvert(source, target) // + && !conversionService.canBypassConvert(source, target); + } + + /** + * Unwraps the given value if it's a JDK 8 {@link Optional}. + * + * @param source can be {@literal null}. + * @return + */ + @Nullable + @SuppressWarnings("unchecked") + private static Object unwrapOptional(@Nullable Object source) { + + if (source == null) { + return null; + } + + return Optional.class.isInstance(source) // + ? Optional.class.cast(source).orElse(null) // + : source; + } + + /** + * Returns whether we have to process the given source object in the first place. + * + * @param source can be {@literal null}. + * @param targetType must not be {@literal null}. + * @return + */ + private static boolean processingRequired(@Nullable Object source, Class targetType) { + + return !targetType.isInstance(source) // + || source == null // + || Collection.class.isInstance(source); + } + + /** + * Value object capturing {@link MethodParameter} and {@link TypeDescriptor}s for top and nested levels. + */ + static class ReturnTypeDescriptor { + + private final MethodParameter methodParameter; + private final TypeDescriptor typeDescriptor; + private final @Nullable TypeDescriptor nestedTypeDescriptor; + + private ReturnTypeDescriptor(Method method) { + this.methodParameter = new MethodParameter(method, -1); + this.typeDescriptor = TypeDescriptor.nested(this.methodParameter, 0); + this.nestedTypeDescriptor = TypeDescriptor.nested(this.methodParameter, 1); + } + + /** + * Create a {@link ReturnTypeDescriptor} from a {@link Method}. + * + * @param method + * @return + */ + public static ReturnTypeDescriptor of(Method method) { + return new ReturnTypeDescriptor(method); + } + + /** + * Return the {@link TypeDescriptor} for a nested type declared within the method parameter described by + * {@code nestingLevel} . + * + * @param nestingLevel the nesting level. {@code 0} is the first level, {@code 1} the next inner one. + * @return the {@link TypeDescriptor} or {@literal null} if it could not be obtained. + * @see TypeDescriptor#nested(MethodParameter, int) + */ + @Nullable + public TypeDescriptor getReturnTypeDescriptor(int nestingLevel) { + + // optimizing for nesting level 0 and 1 (Optional, List) + // nesting level 2 (Optional>) uses the slow path. + + switch (nestingLevel) { + case 0: + return typeDescriptor; + case 1: + return nestedTypeDescriptor; + default: + return TypeDescriptor.nested(this.methodParameter, nestingLevel); + } + } + } +} diff --git a/integration/spring-data/base-3.3/pom.xml b/integration/spring-data/base-3.3/pom.xml new file mode 100644 index 0000000000..45b948b349 --- /dev/null +++ b/integration/spring-data/base-3.3/pom.xml @@ -0,0 +1,126 @@ + + + + blaze-persistence-integration-spring-data-parent + com.blazebit + 1.6.12-SNAPSHOT + ../pom.xml + + 4.0.0 + + Blazebit Persistence Integration Spring-Data Base 3.3 + blaze-persistence-integration-spring-data-base-3.3 + + + com.blazebit.persistence.integration.spring.data + ${version.spring-data-3.3-spring} + + + 17 + + + + + + org.springframework + spring-framework-bom + ${version.spring} + import + pom + + + + + + + ${project.groupId} + blaze-persistence-entity-view-api-jakarta + + + ${project.groupId} + blaze-persistence-jpa-criteria-api-jakarta + + + ${project.groupId} + blaze-persistence-jpa-criteria-impl-jakarta + + + jakarta.persistence + jakarta.persistence-api + ${version.jakarta-jpa-3.1-api} + provided + + + + + ${project.groupId} + blaze-persistence-integration-entity-view-spring-6.0 + compile + + + ${project.groupId} + blaze-persistence-core-impl-jakarta + runtime + + + ${project.groupId} + blaze-persistence-entity-view-impl-jakarta + runtime + + + + org.springframework.data + spring-data-jpa + ${version.spring-data-3.3} + provided + + + + + + org.springframework + spring-test + test + + + junit + junit + test + + + + + + + org.moditect + moditect-maven-plugin + + + add-module-infos + package + + add-module-info + + + + + module ${module.name} { + requires transitive spring.context; + requires transitive spring.data.jpa; + requires transitive com.blazebit.persistence.criteria; + exports com.blazebit.persistence.spring.data.annotation; + exports com.blazebit.persistence.spring.data.base; + exports com.blazebit.persistence.spring.data.base.query; + exports com.blazebit.persistence.spring.data.base.repository; + exports com.blazebit.persistence.spring.data.repository; + } + + + + + + + + + + diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/annotation/OptionalParam.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/annotation/OptionalParam.java new file mode 100644 index 0000000000..305647213f --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/annotation/OptionalParam.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.blazebit.persistence.view.EntityViewSetting; + +/** + * Annotation to let method parameters be bound to the {@link EntityViewSetting}. + * + * @author Giovanni Lovato + * @author Eugen Mayer + * @since 1.6.9 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface OptionalParam { + + /** + * The name of the parameter. + */ + String value(); +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/EntityViewSortUtil.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/EntityViewSortUtil.java new file mode 100644 index 0000000000..42fc68494e --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/EntityViewSortUtil.java @@ -0,0 +1,115 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.base; + +import jakarta.persistence.metamodel.Attribute; +import jakarta.persistence.metamodel.ManagedType; + +import com.blazebit.persistence.FullQueryBuilder; +import com.blazebit.persistence.parser.expression.ExpressionFactory; +import com.blazebit.persistence.parser.expression.PathElementExpression; +import com.blazebit.persistence.parser.expression.PathExpression; +import com.blazebit.persistence.view.EntityViewManager; +import com.blazebit.persistence.view.metamodel.ManagedViewType; +import com.blazebit.persistence.view.metamodel.MethodAttribute; +import com.blazebit.persistence.view.metamodel.PluralAttribute; +import com.blazebit.persistence.view.metamodel.SingularAttribute; +import com.blazebit.persistence.view.metamodel.Type; +import org.springframework.data.domain.Sort; + +/** + * Utility methods to handle entity view sorting. + * + * @author Moritz Becker + * @author Giovanni Lovato + * @author Eugen Mayer + * @since 1.6.9 + */ + +public final class EntityViewSortUtil { + + private EntityViewSortUtil() { + } + + /** + * Resolves the deterministic select item alias for an entity view attribute. + * + * @param viewType entity view type + * @param attributePath the absolute attribute path based on the {@code entityViewClass} for which the select alias should be resolved + * @return the select item alias for the (nested) entity view attribute targeted by {@code attributePath} or {@code null} + * if the {@code attributePath} cannot be resolved + */ + private static String resolveViewAttributeSelectAlias(ManagedViewType viewType, String attributePath) { + StringBuilder aliasBuilder = new StringBuilder(viewType.getJavaType().getSimpleName()); + for (String pathElement : attributePath.split("\\.")) { + if (viewType == null) { + return null; + } else { + MethodAttribute attribute = viewType.getAttribute(pathElement); + if (attribute == null) { + return null; + } else { + aliasBuilder.append('_').append(pathElement); + Type type; + if (attribute instanceof SingularAttribute) { + type = ((SingularAttribute) attribute).getType(); + } else { + type = ((PluralAttribute) attribute).getElementType(); + } + if (type instanceof ManagedViewType) { + // It's a view type, continue descending + viewType = (ManagedViewType) type; + } else { + // It's a basic type, so we cannot got further + viewType = null; + } + } + } + } + return aliasBuilder.toString(); + } + + public static void applySort(EntityViewManager evm, Class entityViewClass, FullQueryBuilder cb, Sort sort) { + ManagedViewType viewType = evm.getMetamodel().managedViewOrError(entityViewClass); + for (Sort.Order order : sort) { + String entityViewAttributeAlias; + if ((entityViewAttributeAlias = EntityViewSortUtil.resolveViewAttributeSelectAlias(viewType, order.getProperty())) == null) { + PathExpression pathExpression; + try { + pathExpression = cb.getService(ExpressionFactory.class).createPathExpression(order.getProperty()); + } catch (RuntimeException ex) { + throw new IllegalArgumentException("Sort order [" + order.getProperty() + "] is not resolvable to a path expression.", ex); + } + jakarta.persistence.metamodel.Type resultType = cb.getMetamodel().entity(viewType.getEntityClass()); + for (PathElementExpression expression : pathExpression.getExpressions()) { + if (!(resultType instanceof ManagedType)) { + throw new IllegalArgumentException("Sort order [" + order.getProperty() + "] is not resolvable to a valid path expression, because the type [" + resultType.getJavaType().getName() + "] can't be de-referenced."); + } + ManagedType managedType = (ManagedType) resultType; + Attribute attribute = managedType.getAttribute(expression.toString()); + if (!(attribute instanceof jakarta.persistence.metamodel.SingularAttribute)) { + throw new IllegalArgumentException("Sort order [" + order.getProperty() + "] is not resolvable to a valid path expression, because the type [" + resultType.getJavaType().getName() + "] does not contain a singular attribute named [" + expression + "]."); + } + resultType = ( (jakarta.persistence.metamodel.SingularAttribute) attribute ).getType(); + } + cb.orderBy(order.getProperty(), order.isAscending(), order.getNullHandling() == Sort.NullHandling.NULLS_FIRST); + } else { + cb.orderBy(entityViewAttributeAlias, order.isAscending(), order.getNullHandling() == Sort.NullHandling.NULLS_FIRST); + } + } + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/AbstractCriteriaQueryParameterBinder.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/AbstractCriteriaQueryParameterBinder.java new file mode 100644 index 0000000000..56b8391ae2 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/AbstractCriteriaQueryParameterBinder.java @@ -0,0 +1,110 @@ +/* + * Copyright 2011-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blazebit.persistence.spring.data.base.query; + +import com.blazebit.persistence.view.EntityViewManager; +import com.blazebit.persistence.view.spi.type.EntityViewProxy; + +import jakarta.persistence.Parameter; +import org.springframework.data.repository.query.Parameters; +import org.springframework.util.Assert; + +import jakarta.persistence.Query; +import jakarta.persistence.EntityManager; +import jakarta.persistence.criteria.CriteriaQuery; +import java.util.Date; +import java.util.Iterator; + +/** + * Special {@link org.springframework.data.jpa.repository.query.ParameterBinder} to bind {@link CriteriaQuery} parameters. parameters. + * + * Christian Beikov: Copied to be able to share code between Spring Data integrations for 1.x and 2.x. + * + * @author Oliver Gierke + * @author Thomas Darimont + * @author Mark Paluch + * @author Eugen Mayer + * @since 1.6.9 + */ +public abstract class AbstractCriteriaQueryParameterBinder extends ParameterBinder { + + private final EntityManager em; + private final EntityViewManager evm; + private final Iterator> expressions; + + /** + * Creates a new {@link AbstractCriteriaQueryParameterBinder} for the given {@link Parameters}, values and some + * {@link jakarta.persistence.criteria.ParameterExpression}. + * + * @param parameters must not be {@literal null}. + * @param values must not be {@literal null}. + * @param expressions must not be {@literal null}. + */ + public AbstractCriteriaQueryParameterBinder(EntityManager em, EntityViewManager evm, JpaParameters parameters, Object[] values, Iterable> expressions) { + + super(parameters, values); + + Assert.notNull(expressions, "Iterable of ParameterMetadata must not be null!"); + this.em = em; + this.evm = evm; + this.expressions = expressions.iterator(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.query.ParameterBinder#bind(jakarta.persistence.Query, org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter, java.lang.Object, int) + */ + @Override + @SuppressWarnings("unchecked") + protected void bind(Query query, JpaParameters.JpaParameter parameter, Object value, int position) { + + ParameterMetadataProvider.ParameterMetadata metadata = (ParameterMetadataProvider.ParameterMetadata) expressions.next(); + + if (metadata.isIsNullParameter()) { + return; + } + Object paramValue = metadata.prepare(value); + + // Christian Beikov: Parameters are now set by name or position instead of by the ParameterExpression object if possible + if (parameter.isTemporalParameter()) { + if (metadata.getExpression().getPosition() == null) { + if (metadata.getExpression().getName() == null) { + query.setParameter((Parameter) metadata.getExpression(), (Date) paramValue, + parameter.getTemporalType()); + } else { + query.setParameter(metadata.getExpression().getName(), (Date) paramValue, + parameter.getTemporalType()); + } + } else { + query.setParameter(metadata.getExpression().getPosition(), (Date) paramValue, + parameter.getTemporalType()); + } + } else { + if (paramValue instanceof EntityViewProxy) { + paramValue = evm.getEntityReference(em, paramValue); + } + if (metadata.getExpression().getPosition() == null) { + if (metadata.getExpression().getName() == null) { + query.setParameter(metadata.getExpression(), paramValue); + } else { + query.setParameter(metadata.getExpression().getName(), paramValue); + } + } else { + query.setParameter(metadata.getExpression().getPosition(), paramValue); + } + } + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/AbstractPartTreeBlazePersistenceQuery.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/AbstractPartTreeBlazePersistenceQuery.java new file mode 100644 index 0000000000..99fc86b420 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/AbstractPartTreeBlazePersistenceQuery.java @@ -0,0 +1,553 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * Copyright 2010-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.base.query; + +import com.blazebit.persistence.CriteriaBuilderFactory; +import com.blazebit.persistence.FullQueryBuilder; +import com.blazebit.persistence.OrderByBuilder; +import com.blazebit.persistence.PaginatedCriteriaBuilder; +import com.blazebit.persistence.criteria.BlazeCriteria; +import com.blazebit.persistence.criteria.BlazeCriteriaBuilder; +import com.blazebit.persistence.criteria.BlazeCriteriaQuery; +import com.blazebit.persistence.spring.data.base.EntityViewSortUtil; +import com.blazebit.persistence.spring.data.base.query.JpaParameters.JpaParameter; +import com.blazebit.persistence.spring.data.repository.BlazeSpecification; +import com.blazebit.persistence.spring.data.repository.EntityViewSettingProcessor; +import com.blazebit.persistence.spring.data.repository.KeysetPageable; +import com.blazebit.persistence.view.EntityViewManager; +import com.blazebit.persistence.view.EntityViewSetting; +import com.blazebit.persistence.view.Sorter; + + + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.provider.PersistenceProvider; +import org.springframework.data.jpa.repository.query.AbstractJpaQuery; +import org.springframework.data.jpa.repository.query.FixedJpaCountQueryCreator; +import org.springframework.data.jpa.repository.query.FixedJpaQueryCreator; +import org.springframework.data.jpa.repository.query.Jpa21Utils; +import org.springframework.data.jpa.repository.query.JpaEntityGraph; +import org.springframework.data.jpa.repository.query.PartTreeJpaQuery; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.parser.PartTree; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.Query; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Implementation is similar to {@link PartTreeJpaQuery} but was modified to work with entity views. + * + * About sorting + * The implementation of both {@link AbstractPartTreeBlazePersistenceQuery} and {@link com.blazebit.persistence.spring.data.base.repository.AbstractEntityViewAwareRepository} + * aims to support mixed sorting, i.e. sorting by entity view attributes and entity attributes at the same time. + * To make this work we abstain from using the attribute sorter API of {@link EntityViewSetting} because it would not + * allow us to add the entity attribute sorts. Instead, we add both entity view attribute and entity attribute sorters + * uniformly via the core order API of {@link OrderByBuilder} *after* the entity view settings have been applied: + * - For entity view attribute sorts we resolve deterministic aliases from the order property that correspond to the + * select item aliases + * - For entity attribute sorts we just use the order property as is + * + * Sorters are retrieved from 4 sources. The order these sources are applied to the final query is as follows: + * 1. method name sorters e.g. findAllByOrderByNameAsc() + * 2./3. {@link Pageable} or {@link Sort} method parameter + * 4. {@link EntityViewSettingProcessor} method parameter that adds attribute sorters to the entity view settings + * + * @author Moritz Becker + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public abstract class AbstractPartTreeBlazePersistenceQuery extends AbstractJpaQuery { + + private static final String QUERY_PATTERN = "find|read|get|query|stream"; + private static final String COUNT_PATTERN = "count"; + private static final String EXISTS_PATTERN = "exists"; + private static final String DELETE_PATTERN = "delete|remove"; + private static final Pattern PREFIX_TEMPLATE = Pattern.compile( // + "^(" + QUERY_PATTERN + "|" + COUNT_PATTERN + "|" + EXISTS_PATTERN + "|" + DELETE_PATTERN + ")((\\p{Lu}.*?))??By"); + + private final EntityViewAwareJpaQueryMethod method; + private final Class domainClass; + private final Class entityViewClass; + private final PartTree tree; + private final JpaParameters parameters; + + private final AbstractPartTreeBlazePersistenceQuery.QueryPreparer query; + private final CriteriaBuilderFactory cbf; + private final Object escape; + protected final EntityViewManager evm; + + public AbstractPartTreeBlazePersistenceQuery(EntityViewAwareJpaQueryMethod method, EntityManager em, PersistenceProvider persistenceProvider, Object escape, CriteriaBuilderFactory cbf, EntityViewManager evm) { + + super(method, em); + + this.method = method; + this.escape = escape; + this.cbf = cbf; + this.evm = evm; + + this.entityViewClass = method.getEntityViewClass(); + this.domainClass = method.getEntityInformation().getJavaType(); + + this.parameters = method.getJpaParameters(); + String methodName = method.getName(); + boolean skipMethodNamePredicateMatching = !PREFIX_TEMPLATE.matcher(methodName).find(); + String source = skipMethodNamePredicateMatching ? "" : methodName; + this.tree = new PartTree(source, domainClass); + + boolean hasEntityViewSettingProcessorParameter = parameters.hasEntityViewSettingProcessorParameter(); + boolean hasSpecificationParameter = parameters.hasSpecificationParameter(); + boolean hasCriteriaBuilderProcessorParameter = parameters.hasBlazeSpecificationParameter(); + boolean recreateQueries = parameters.hasDynamicProjection() || parameters.potentiallySortsDynamically() + || entityViewClass != null + || skipMethodNamePredicateMatching + || hasEntityViewSettingProcessorParameter + || hasSpecificationParameter + || hasCriteriaBuilderProcessorParameter; + this.query = isCountProjection(tree) ? new AbstractPartTreeBlazePersistenceQuery.CountQueryPreparer(persistenceProvider, + recreateQueries) : new AbstractPartTreeBlazePersistenceQuery.QueryPreparer(persistenceProvider, recreateQueries); + } + + protected abstract ParameterMetadataProvider createParameterMetadataProvider(CriteriaBuilder builder, ParametersParameterAccessor accessor, PersistenceProvider provider, Object escape); + + protected abstract ParameterMetadataProvider createParameterMetadataProvider(CriteriaBuilder builder, JpaParameters parameters, PersistenceProvider provider, Object escape); + + protected abstract boolean isCountProjection(PartTree tree); + + protected boolean isDelete() { + return isDelete(this.tree); + } + + protected boolean isExists() { + return isExists(this.tree); + } + + protected abstract boolean isDelete(PartTree tree); + + protected abstract boolean isExists(PartTree tree); + + protected abstract int getOffset(Pageable pageable); + + protected abstract int getLimit(Pageable pageable); + + protected abstract ParameterBinder createCriteriaQueryParameterBinder(JpaParameters parameters, Object[] values, List> expressions); + + protected abstract Map tryGetFetchGraphHints(JpaEntityGraph entityGraph, Class entityType); + + public Query doCreateQuery(Object[] values) { + return query.createQuery(values); + } + + public Query createPaginatedQuery(Object[] values, boolean withCount) { + Query paginatedQuery = query.createPaginatedQuery(values, withCount); + if (method.getLockModeType() != null) { + paginatedQuery.setLockMode(method.getLockModeType()); + } + + JpaEntityGraph entityGraph = method.getEntityGraph(); + if (entityGraph != null) { + Map hints = tryGetFetchGraphHints(method.getEntityGraph(), this.getQueryMethod().getEntityInformation().getJavaType()); + for (Map.Entry entry : hints.entrySet()) { + paginatedQuery.setHint(entry.getKey(), entry.getValue()); + } + } + Map hints = method.getHints(); + if (!hints.isEmpty()) { + for (Map.Entry entry : hints.entrySet()) { + paginatedQuery.setHint(entry.getKey(), entry.getValue()); + } + } + return paginatedQuery; + } + + public TypedQuery doCreateCountQuery(Object[] values) { + throw new UnsupportedOperationException(); + } + + /** + * Query preparer to create {@link CriteriaQuery} instances and potentially cache them. + * + * @author Oliver Gierke + * @author Thomas Darimont + */ + private class QueryPreparer { + + private final CriteriaQuery cachedCriteriaQuery; + private final List> expressions; + private final PersistenceProvider persistenceProvider; + + public QueryPreparer(PersistenceProvider persistenceProvider, boolean recreateQueries) { + + this.persistenceProvider = persistenceProvider; + + FixedJpaQueryCreator creator = createCreator(null, persistenceProvider); + + this.cachedCriteriaQuery = recreateQueries ? null : invokeQueryCreator(creator, null); + this.expressions = recreateQueries ? null : creator.getParameterExpressions(); + } + + /****************************************** + * Moritz Becker, Christian Beikov: + * The following methods were modified to work with entity views. + ******************************************/ + private TypedQuery createQuery(CriteriaQuery criteriaQuery, Object[] values) { + if (this.cachedCriteriaQuery != null) { + synchronized (this.cachedCriteriaQuery) { + return createQuery0(criteriaQuery, values); + } + } + return createQuery0(criteriaQuery, values); + } + + protected TypedQuery createQuery0(CriteriaQuery criteriaQuery, Object[] values) { + processSpecification(criteriaQuery, values); + + com.blazebit.persistence.CriteriaBuilder cb = ((BlazeCriteriaQuery) criteriaQuery).createCriteriaBuilder(getEntityManager()); + + processBlazeSpecification(cb, values); + + Class entityViewClass = AbstractPartTreeBlazePersistenceQuery.this.entityViewClass; + if (parameters.hasDynamicProjection()) { + // If the dynamic projection is an entity view, we use that and null it for the result processor + entityViewClass = (Class) values[parameters.getDynamicProjectionIndex()]; + if (evm.getMetamodel().managedView(entityViewClass) == null) { + entityViewClass = null; + } else { + values[parameters.getDynamicProjectionIndex()] = null; + } + } + if (entityViewClass == null) { + return cb.getQuery(); + } else { + EntityViewSetting setting = EntityViewSetting.create(entityViewClass); + // the entity view processor may append attribute sorters to the settings + setting = processSetting(setting, values); + // we do not want to apply the sorters during evm.applySetting so we extract them here for later application + // in order to maintain the defined order of sort applications + Map settingProcessorAttributeSorters = new HashMap<>(setting.getAttributeSorters()); + setting.getAttributeSorters().clear(); + FullQueryBuilder fqb = evm.applySetting(setting, cb); + processSort(fqb, values, entityViewClass, settingProcessorAttributeSorters); + return fqb.getQuery(); + } + } + + Query createPaginatedQuery(Object[] values, boolean withCount) { + CriteriaQuery criteriaQuery = cachedCriteriaQuery; + List> expressions = this.expressions; + ParametersParameterAccessor accessor = new ParametersParameterAccessor(parameters, values); + + if (cachedCriteriaQuery == null || accessor.hasBindableNullValue()) { + FixedJpaQueryCreator creator = createCreator(accessor, persistenceProvider); + criteriaQuery = invokeQueryCreator(creator, appliesSortThroughAttributeSorters() ? null : getDynamicSort(values)); + expressions = creator.getParameterExpressions(); + } + + processSpecification(criteriaQuery, values); + + com.blazebit.persistence.CriteriaBuilder cb = ((BlazeCriteriaQuery) criteriaQuery).createCriteriaBuilder(getEntityManager()); + + processBlazeSpecification(cb, values); + + TypedQuery jpaQuery; + ParameterBinder binder = getBinder(values, expressions); + int firstResult = getOffset(binder.getPageable()); + int maxResults = getLimit(binder.getPageable()); + Class entityViewClass = AbstractPartTreeBlazePersistenceQuery.this.entityViewClass; + if (parameters.hasDynamicProjection()) { + // If the dynamic projection is an entity view, we use that and null it for the result processor + entityViewClass = (Class) values[parameters.getDynamicProjectionIndex()]; + if (evm.getMetamodel().managedView(entityViewClass) == null) { + entityViewClass = null; + } else { + values[parameters.getDynamicProjectionIndex()] = null; + } + } + + boolean withKeysetExtraction = false; + boolean withExtractAllKeysets = false; + Pageable pageable = binder.getPageable(); + if (!withCount) { + maxResults++; + } + + if (entityViewClass == null) { + PaginatedCriteriaBuilder paginatedCriteriaBuilder; + if (pageable instanceof KeysetPageable) { + KeysetPageable keysetPageable = (KeysetPageable) pageable; + paginatedCriteriaBuilder = (PaginatedCriteriaBuilder) cb.page(keysetPageable.getKeysetPage(), firstResult, maxResults); + withKeysetExtraction = true; + withExtractAllKeysets = keysetPageable.isWithExtractAllKeysets(); + } else { + paginatedCriteriaBuilder = (PaginatedCriteriaBuilder) cb.page(firstResult, maxResults); + } + if (withKeysetExtraction) { + paginatedCriteriaBuilder.withKeysetExtraction(true); + paginatedCriteriaBuilder.withExtractAllKeysets(withExtractAllKeysets); + } + if (withCount) { + paginatedCriteriaBuilder.withCountQuery(true); + } else { + paginatedCriteriaBuilder.withHighestKeysetOffset(1).withCountQuery(false); + } + jpaQuery = paginatedCriteriaBuilder.getQuery(); + } else { + EntityViewSetting setting = EntityViewSetting.create(entityViewClass, firstResult, maxResults); + if (pageable instanceof KeysetPageable) { + KeysetPageable keysetPageable = (KeysetPageable) pageable; + withKeysetExtraction = true; + withExtractAllKeysets = keysetPageable.isWithExtractAllKeysets(); + setting.withKeysetPage(keysetPageable.getKeysetPage()); + } + setting = processSetting(setting, values); + Map settingProcessorAttributeSorters = new HashMap<>(setting.getAttributeSorters()); + setting.getAttributeSorters().clear(); + PaginatedCriteriaBuilder paginatedCriteriaBuilder = (PaginatedCriteriaBuilder) evm.applySetting(setting, cb); + processSort(paginatedCriteriaBuilder, values, entityViewClass, settingProcessorAttributeSorters); + if (withCount) { + paginatedCriteriaBuilder.withCountQuery(true); + } else { + paginatedCriteriaBuilder.withHighestKeysetOffset(1).withCountQuery(false); + } + if (withKeysetExtraction) { + paginatedCriteriaBuilder.withKeysetExtraction(true); + paginatedCriteriaBuilder.withExtractAllKeysets(withExtractAllKeysets); + } + jpaQuery = paginatedCriteriaBuilder.getQuery(); + } + + // Just bind the parameters, not the pagination information + return binder.bind(jpaQuery); + } + + @SuppressWarnings("unchecked") + protected EntityViewSetting processSetting(EntityViewSetting setting, Object[] values) { + EntityViewSetting processedSetting = setting; + int entityViewSettingProcessorIndex = parameters.getEntityViewSettingProcessorIndex(); + if (entityViewSettingProcessorIndex >= 0) { + EntityViewSettingProcessor processor = (EntityViewSettingProcessor) values[entityViewSettingProcessorIndex]; + if (processor != null) { + processedSetting = processor.acceptEntityViewSetting(setting); + } + } + for (JpaParameter parameter : parameters.getOptionalParameters()) { + String parameterName = parameter.getParameterName(); + Object parameterValue = values[parameter.getIndex()]; + processedSetting.addOptionalParameter(parameterName, parameterValue); + } + return processedSetting; + } + + protected void processSort(FullQueryBuilder cb, Object[] values, Class entityViewClass, Map evsAttributeSorter) { + Sort sort; + int sortIndex; + int pageableIndex; + if ((sortIndex = parameters.getSortIndex()) >= 0 && (sort = (Sort) values[sortIndex]) != null) { + EntityViewSortUtil.applySort(evm, entityViewClass, cb, sort); + } else if ((pageableIndex = parameters.getPageableIndex()) >= 0 && (sort = ((Pageable) values[pageableIndex]).getSort()) != null) { + EntityViewSortUtil.applySort(evm, entityViewClass, cb, sort); + } + for (Map.Entry attributeSorterEntry : evsAttributeSorter.entrySet()) { + attributeSorterEntry.getValue().apply((OrderByBuilder) cb, attributeSorterEntry.getKey()); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected void processSpecification(CriteriaQuery criteriaQuery, Object[] values) { + BlazeCriteriaQuery blazeCriteriaQuery = (BlazeCriteriaQuery) criteriaQuery; + int specificationIndex = parameters.getSpecificationIndex(); + if (specificationIndex >= 0) { + Specification specification = (Specification) values[specificationIndex]; + if (specification != null) { + Root root = criteriaQuery.getRoots().iterator().next(); + BlazeCriteriaBuilder criteriaBuilder = blazeCriteriaQuery.getCriteriaBuilder(); + Predicate predicate = specification.toPredicate(root, criteriaQuery, criteriaBuilder); + criteriaQuery.where(predicate); + } + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected void processBlazeSpecification(com.blazebit.persistence.CriteriaBuilder criteriaBuilder, Object[] values) { + int criteriaBuilderProcessorIndex = parameters.getBlazeSpecificationIndex(); + if (criteriaBuilderProcessorIndex >= 0) { + String rootAlias = criteriaBuilder.getRoots().iterator().next().getAlias(); + BlazeSpecification blazeSpecification = (BlazeSpecification) values[criteriaBuilderProcessorIndex]; + if (blazeSpecification != null) { + blazeSpecification.applySpecification(rootAlias, criteriaBuilder); + } + } + } + + protected FixedJpaQueryCreator createCreator(ParametersParameterAccessor accessor, + PersistenceProvider persistenceProvider) { + BlazeCriteriaQuery cq = BlazeCriteria.get(cbf, Long.class); + CriteriaBuilder builder = cq.getCriteriaBuilder(); + + ParameterMetadataProvider provider = accessor == null + ? createParameterMetadataProvider(builder, parameters, persistenceProvider, escape) + : createParameterMetadataProvider(builder, accessor, persistenceProvider, escape); + + return new FixedJpaQueryCreator(tree, domainClass, builder, provider); + } + /****************************************** + * end of changes + ******************************************/ + + /** + * Creates a new {@link Query} for the given parameter values. + * + * @param values + * @return + */ + public Query createQuery(Object[] values) { + CriteriaQuery criteriaQuery = cachedCriteriaQuery; + List> expressions = this.expressions; + ParametersParameterAccessor accessor = new ParametersParameterAccessor(parameters, values); + + if (cachedCriteriaQuery == null || accessor.hasBindableNullValue()) { + FixedJpaQueryCreator creator = createCreator(accessor, persistenceProvider); + criteriaQuery = invokeQueryCreator(creator, appliesSortThroughAttributeSorters() ? null : getDynamicSort(values)); + expressions = creator.getParameterExpressions(); + } + + TypedQuery jpaQuery = createQuery(criteriaQuery, values); + + return restrictMaxResultsIfNecessary(invokeBinding(getBinder(values, expressions), jpaQuery)); + } + + private boolean appliesSortThroughAttributeSorters() { + return entityViewClass != null || parameters.hasDynamicProjection(); + } + + protected CriteriaQuery invokeQueryCreator(FixedJpaQueryCreator creator, Sort sort) { + if (sort == null) { + return creator.createQuery().select(null); + } else { + return creator.createQuery(sort).select(null); + } + } + + /** + * Restricts the max results of the given {@link Query} if the current {@code tree} marks this {@code query} as + * limited. + * + * @param query + * @return + */ + private Query restrictMaxResultsIfNecessary(Query query) { + if (tree.isLimiting()) { + if (query.getMaxResults() != Integer.MAX_VALUE) { + /* + * In order to return the correct results, we have to adjust the first result offset to be returned if: + * - a Pageable parameter is present + * - AND the requested page number > 0 + * - AND the requested page size was bigger than the derived result limitation via the First/Top keyword. + */ + if (query.getMaxResults() > tree.getMaxResults() && query.getFirstResult() > 0) { + query.setFirstResult(query.getFirstResult() - (query.getMaxResults() - tree.getMaxResults())); + } + } + + query.setMaxResults(tree.getMaxResults()); + } + + return query; + } + + /** + * Invokes parameter binding on the given {@link TypedQuery}. + * + * @param binder + * @param query + * @return + */ + protected Query invokeBinding(ParameterBinder binder, TypedQuery query) { + return binder.bindAndPrepare(query); + } + + private ParameterBinder getBinder(Object[] values, List> expressions) { + return createCriteriaQueryParameterBinder(parameters, values, expressions); + } + + private Sort getDynamicSort(Object[] values) { + return parameters.potentiallySortsDynamically() ? new ParametersParameterAccessor(parameters, values).getSort() + : null; + } + } + + /** + * Special {@link PartTreeJpaQuery.QueryPreparer} to create count queries. + * + * @author Oliver Gierke + * @author Thomas Darimont + */ + private class CountQueryPreparer extends AbstractPartTreeBlazePersistenceQuery.QueryPreparer { + + public CountQueryPreparer(PersistenceProvider persistenceProvider, boolean recreateQueries) { + super(persistenceProvider, recreateQueries); + } + + @Override + protected FixedJpaQueryCreator createCreator(ParametersParameterAccessor accessor, + PersistenceProvider persistenceProvider) { + + EntityManager entityManager = getEntityManager(); + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + + ParameterMetadataProvider provider = accessor == null + ? createParameterMetadataProvider(builder, parameters, persistenceProvider, escape) + : createParameterMetadataProvider(builder, accessor, persistenceProvider, escape); + + return new FixedJpaCountQueryCreator(tree, domainClass, builder, provider); + } + + /** + * Customizes binding by skipping the pagination. + * + * @see AbstractPartTreeBlazePersistenceQuery.QueryPreparer#invokeBinding(ParameterBinder, + * jakarta.persistence.TypedQuery) + */ + @Override + protected Query invokeBinding(ParameterBinder binder, jakarta.persistence.TypedQuery query) { + return binder.bind(query); + } + + @Override + protected TypedQuery createQuery0(CriteriaQuery criteriaQuery, Object[] values) { + return getEntityManager().createQuery(criteriaQuery); + } + + @Override + protected CriteriaQuery invokeQueryCreator(FixedJpaQueryCreator creator, Sort sort) { + return creator.createQuery(); + } + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/EntityViewAwareJpaQueryMethod.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/EntityViewAwareJpaQueryMethod.java new file mode 100644 index 0000000000..99ffe3f53f --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/EntityViewAwareJpaQueryMethod.java @@ -0,0 +1,121 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.base.query; + +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.jpa.provider.QueryExtractor; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.QueryHints; +import org.springframework.data.jpa.repository.query.JpaEntityGraph; +import org.springframework.data.jpa.repository.query.JpaQueryMethod; +import org.springframework.data.projection.ProjectionFactory; + +import jakarta.persistence.QueryHint; +import jakarta.persistence.LockModeType; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Moritz Becker + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public class EntityViewAwareJpaQueryMethod extends JpaQueryMethod { + + private final Class entityViewClass; + private final JpaParameters parameters; + private final LockModeType lockModeType; + private final JpaEntityGraph entityGraph; + private final Map queryHints; + + /** + * Creates a {@link JpaQueryMethod}. + * + * @param method must not be {@literal null} + * @param extractor must not be {@literal null} + * @param metadata must not be {@literal null} + */ + public EntityViewAwareJpaQueryMethod(Method method, EntityViewAwareRepositoryMetadata metadata, ProjectionFactory factory, + QueryExtractor extractor) { + + super(method, metadata, factory, extractor); + this.lockModeType = findLockModeType(method); + this.entityGraph = findEntityGraph(method); + this.queryHints = findQueryHints(method); + this.entityViewClass = metadata.getReturnedEntityViewClass(method); + this.parameters = new JpaParameters(metadata, method); + } + + public LockModeType getLockModeType() { + return lockModeType; + } + + public JpaEntityGraph getEntityGraph() { + return entityGraph; + } + + public Map getHints() { + return queryHints; + } + + public boolean isEntityViewQuery() { + return entityViewClass != null; + } + + public Class getEntityViewClass() { + return entityViewClass; + } + + public JpaParameters getJpaParameters() { + return parameters; + } + + private JpaEntityGraph findEntityGraph(Method method) { + EntityGraph entityGraph = AnnotatedElementUtils.findMergedAnnotation(method, EntityGraph.class); + return entityGraph == null ? null : new JpaEntityGraph(entityGraph, this.getNamedQueryName()); + } + + private LockModeType findLockModeType(Method method) { + Lock annotation = AnnotatedElementUtils.findMergedAnnotation(method, Lock.class); + return annotation == null ? null : (LockModeType) AnnotationUtils.getValue(annotation); + } + + private Map findQueryHints(Method method) { + Map queryHints = new HashMap<>(); + QueryHints queryHintsAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, QueryHints.class); + + if (queryHintsAnnotation != null) { + + for (QueryHint hint : queryHintsAnnotation.value()) { + queryHints.put(hint.name(), hint.value()); + } + } + + QueryHint queryHintAnnotation = AnnotationUtils.findAnnotation(method, QueryHint.class); + + if (queryHintAnnotation != null) { + queryHints.put(queryHintAnnotation.name(), queryHintAnnotation.value()); + } + + return Collections.unmodifiableMap(queryHints); + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/EntityViewAwareRepositoryMetadata.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/EntityViewAwareRepositoryMetadata.java new file mode 100644 index 0000000000..f3a6fe8815 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/EntityViewAwareRepositoryMetadata.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.base.query; + +import com.blazebit.persistence.view.EntityViewManager; +import org.springframework.data.repository.core.RepositoryMetadata; + +import java.lang.reflect.Method; + +/** + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public interface EntityViewAwareRepositoryMetadata extends RepositoryMetadata { + + public EntityViewManager getEntityViewManager(); + + public Class getEntityViewType(); + + public Class getReturnedEntityViewClass(Method method); + +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/JpaParameters.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/JpaParameters.java new file mode 100644 index 0000000000..fa7b7dd26f --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/JpaParameters.java @@ -0,0 +1,342 @@ +/* + * Copyright 2013-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blazebit.persistence.spring.data.base.query; + +import com.blazebit.persistence.spring.data.annotation.OptionalParam; +import com.blazebit.persistence.spring.data.base.query.JpaParameters.JpaParameter; +import com.blazebit.persistence.spring.data.repository.KeysetPageable; +import jakarta.persistence.TemporalType; +import org.springframework.core.MethodParameter; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.Temporal; + +import com.blazebit.persistence.spring.data.repository.EntityViewSettingProcessor; +import com.blazebit.persistence.spring.data.repository.BlazeSpecification; + +import org.springframework.data.repository.query.*; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import org.springframework.data.util.TypeInformation; + +/** + * Custom extension of {@link Parameters} discovering additional query parameter annotations. + * + * Christian Beikov: Copied to be able to share code between Spring Data integrations for 1.x and 2.x. + * + * @author Thomas Darimont + * @author Mark Paluch + * @author Eugen Mayer + * @since 1.6.9 + */ +public class JpaParameters extends Parameters { + + private final int keysetPageableIndex; + + /** + * Creates a new {@link JpaParameters} instance from the given {@link Method}. + * + * @param metadata + * @param method must not be {@literal null}. + */ + public JpaParameters(EntityViewAwareRepositoryMetadata metadata, Method method) { + super( + ParametersSource.of(method), + param -> new JpaParameter( + param, + ParametersSource.of(metadata, method).getDomainTypeInformation() + ) + ); + if (super.hasPageableParameter()) { + this.keysetPageableIndex = -1; + } else { + Class[] types = method.getParameterTypes(); + int i = 0; + for (; i < types.length; i++) { + if (types[i] == KeysetPageable.class) { + break; + } + } + if (i == types.length) { + this.keysetPageableIndex = -1; + } else { + this.keysetPageableIndex = i; + } + } + } + + private JpaParameters(List parameters) { + super(parameters); + if (super.hasPageableParameter()) { + this.keysetPageableIndex = -1; + } else { + int i = 0; + for (; i < parameters.size(); i++) { + JpaParameter jpaParameter = parameters.get(i); + if (jpaParameter.getType() == KeysetPageable.class) { + break; + } + } + if (i == parameters.size()) { + this.keysetPageableIndex = -1; + } else { + this.keysetPageableIndex = i; + } + } + } + + @Override + public boolean hasPageableParameter() { + return this.keysetPageableIndex != -1 || super.hasPageableParameter(); + } + + @Override + public int getPageableIndex() { + return keysetPageableIndex == -1 ? super.getPageableIndex() : keysetPageableIndex; + } + + /** + * Gets the parameters annotated with {@link OptionalParam}. + * + * @return the optional parameters + */ + public JpaParameters getOptionalParameters() { + + List parameters = new ArrayList<>(); + + for (JpaParameter candidate : this) { + if (candidate.isOptionalParameter()) { + parameters.add(candidate); + } + } + + return createFrom(parameters); + } + + /** + * Returns the index of the {@link Specification} {@link Method} parameter if available. Will return {@literal -1} if there + * is no {@link Specification} parameter in the {@link Method}'s parameter list. + * + * @return the index of the specification parameter, or -1 if not present + */ + public int getSpecificationIndex() { + int index = 0; + + for (JpaParameter candidate : this) { + if (candidate.isSpecificationParameter()) { + return index; + } + ++index; + } + + return -1; + } + + /** + * Returns whether the method the {@link Parameters} was created for contains a {@link Specification} parameter. + * + * @return true if the methods has a specification parameter + */ + public boolean hasSpecificationParameter() { + return getSpecificationIndex() >= 0; + } + + /** + * Returns the index of the {@link EntityViewSettingProcessor} {@link Method} parameter if available. Will return + * {@literal -1} if there is no {@link EntityViewSettingProcessor} parameter in the {@link Method}'s parameter list. + * + * @return the index of the processor parameter, or -1 if not present + */ + public int getEntityViewSettingProcessorIndex() { + int index = 0; + + for (JpaParameter candidate : this) { + if (candidate.isEntityViewSettingProcessorParameter()) { + return index; + } + ++index; + } + + return -1; + } + + /** + * Returns whether the method the {@link Parameters} was created for contains a {@link EntityViewSettingProcessor} + * parameter. + * + * @return true if the methods has a processor parameter + */ + public boolean hasEntityViewSettingProcessorParameter() { + return getEntityViewSettingProcessorIndex() >= 0; + } + + /** + * Returns the index of the {@link BlazeSpecification} {@link Method} parameter if available. Will return + * {@literal -1} if there is no {@link BlazeSpecification} parameter in the {@link Method}'s parameter list. + * + * @return the index of the processor parameter, or -1 if not present + */ + public int getBlazeSpecificationIndex() { + int index = 0; + + for (JpaParameter candidate : this) { + if (candidate.isBlazeSpecificationParameter()) { + return index; + } + ++index; + } + + return -1; + } + + /** + * Returns whether the method the {@link Parameters} was created for contains a {@link BlazeSpecification} + * parameter. + * + * @return true if the methods has a processor parameter + */ + public boolean hasBlazeSpecificationParameter() { + return getBlazeSpecificationIndex() >= 0; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.Parameters#createFrom(java.util.List) + */ + @Override + protected JpaParameters createFrom(List parameters) { + return new JpaParameters(parameters); + } + + /** + * Custom {@link Parameter} implementation adding parameters of type {@link Temporal} to the special ones. + * + * @author Thomas Darimont + * @author Oliver Gierke + */ + public static class JpaParameter extends Parameter { + + private final MethodParameter parameter; + private final OptionalParam optional; + private final Temporal annotation; + private TemporalType temporalType; + + /** + * Creates a new {@link JpaParameter}. + * + * @param parameter must not be {@literal null}. + */ + JpaParameter(MethodParameter parameter, TypeInformation typeInformation) { + + super(parameter, typeInformation); + + this.parameter = parameter; + this.optional = parameter.getParameterAnnotation(OptionalParam.class); + this.annotation = parameter.getParameterAnnotation(Temporal.class); + this.temporalType = null; + + if (!isDateParameter() && hasTemporalParamAnnotation()) { + throw new IllegalArgumentException( + Temporal.class.getSimpleName() + " annotation is only allowed on Date parameter!"); + } + } + + public String getParameterName() { + Param annotation = parameter.getParameterAnnotation(Param.class); + if (annotation != null) { + return annotation.value(); + } + return optional == null ? parameter.getParameterName() : optional.value(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.Parameter#isBindable() + */ + @Override + public boolean isBindable() { + return super.isBindable() || isTemporalParameter(); + } + + @Override + public boolean isSpecialParameter() { + return super.isSpecialParameter() || isOptionalParameter() || isSpecificationParameter() + || isEntityViewSettingProcessorParameter() + || isBlazeSpecificationParameter(); + } + + boolean isOptionalParameter() { + return optional != null; + } + + boolean isSpecificationParameter() { + return Specification.class.isAssignableFrom(parameter.getParameterType()); + } + + boolean isEntityViewSettingProcessorParameter() { + return EntityViewSettingProcessor.class.isAssignableFrom(parameter.getParameterType()); + } + + boolean isBlazeSpecificationParameter() { + return BlazeSpecification.class.isAssignableFrom(parameter.getParameterType()); + } + + /** + * @return {@literal true} if this parameter is of type {@link Date} and has an {@link Temporal} annotation. + */ + boolean isTemporalParameter() { + return isDateParameter() && hasTemporalParamAnnotation(); + } + + /** + * @return the {@link TemporalType} on the {@link Temporal} annotation of the given {@link Parameter}. + */ + TemporalType getTemporalType() { + + if (temporalType == null) { + this.temporalType = annotation == null ? null : annotation.value(); + } + + return this.temporalType; + } + + /** + * @return the required {@link TemporalType} on the {@link Temporal} annotation of the given {@link Parameter}. + * @throws IllegalStateException if the parameter does not define a {@link TemporalType}. + * @since 2.0 + */ + TemporalType getRequiredTemporalType() throws IllegalStateException { + + TemporalType temporalType = getTemporalType(); + + if (temporalType != null) { + return temporalType; + } + + throw new IllegalStateException(String.format("Required temporal type not found for %s!", getType())); + } + + private boolean hasTemporalParamAnnotation() { + return annotation != null; + } + + private boolean isDateParameter() { + return getType().equals(Date.class); + } + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/KeysetAwarePageImpl.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/KeysetAwarePageImpl.java new file mode 100644 index 0000000000..0b6989dc3a --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/KeysetAwarePageImpl.java @@ -0,0 +1,85 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.base.query; + +import com.blazebit.persistence.KeysetPage; +import com.blazebit.persistence.PagedList; +import com.blazebit.persistence.spring.data.repository.KeysetAwarePage; +import com.blazebit.persistence.spring.data.repository.KeysetPageRequest; +import com.blazebit.persistence.spring.data.repository.KeysetPageable; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +/** + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public class KeysetAwarePageImpl extends PageImpl implements KeysetAwarePage { + + private final KeysetPage keysetPage; + + public KeysetAwarePageImpl(List list) { + super(list); + this.keysetPage = null; + } + + public KeysetAwarePageImpl(PagedList list, Pageable pageable) { + super(list, keysetPageable(list.getKeysetPage(), pageable), list.getTotalSize()); + this.keysetPage = list.getKeysetPage(); + } + + public KeysetAwarePageImpl(List list, long totalSize, KeysetPage keysetPage, Pageable pageable) { + super(list, keysetPageable(keysetPage, pageable), totalSize); + this.keysetPage = keysetPage; + } + + @Override + public KeysetPage getKeysetPage() { + return keysetPage; + } + + @Override + public KeysetPageable nextPageable() { + return (KeysetPageable) super.nextPageable(); + } + + @Override + public KeysetPageable previousPageable() { + return (KeysetPageable) super.previousPageable(); + } + + private static Pageable keysetPageable(KeysetPage keysetPage, Pageable pageable) { + if (pageable instanceof KeysetPageRequest) { + if (keysetPage == null || ((KeysetPageRequest) pageable).getKeysetPage() == keysetPage) { + return pageable; + } + } + + if (keysetPage == null) { + if (pageable instanceof KeysetPageable) { + return new KeysetPageRequest(null, pageable.getSort(), ((KeysetPageable) pageable).getIntOffset(), pageable.getPageSize()); + } else { + return new KeysetPageRequest(pageable.getPageNumber(), pageable.getPageSize(), null, pageable.getSort()); + } + } else { + return new KeysetPageRequest(keysetPage, pageable.getSort()); + } + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/KeysetAwareSliceImpl.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/KeysetAwareSliceImpl.java new file mode 100644 index 0000000000..d8e7f892e1 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/KeysetAwareSliceImpl.java @@ -0,0 +1,86 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.base.query; + +import com.blazebit.persistence.KeysetPage; +import com.blazebit.persistence.PagedList; +import com.blazebit.persistence.spring.data.repository.KeysetAwareSlice; +import com.blazebit.persistence.spring.data.repository.KeysetPageRequest; +import com.blazebit.persistence.spring.data.repository.KeysetPageable; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.SliceImpl; + +import java.util.List; + +/** + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public class KeysetAwareSliceImpl extends SliceImpl implements KeysetAwareSlice { + + private final KeysetPage keysetPage; + + public KeysetAwareSliceImpl(List list) { + super(list); + this.keysetPage = null; + } + + public KeysetAwareSliceImpl(PagedList list, Pageable pageable) { + super(list.size() > pageable.getPageSize() ? list.subList(0, pageable.getPageSize()) : list, keysetPageable(list.getKeysetPage(), pageable), list.size() > pageable.getPageSize()); + this.keysetPage = list.getKeysetPage(); + } + + public KeysetAwareSliceImpl(List list, KeysetPage keysetPage, Pageable pageable) { + super(list.size() > pageable.getPageSize() ? list.subList(0, pageable.getPageSize()) : list, keysetPageable(keysetPage, pageable), list.size() > pageable.getPageSize()); + this.keysetPage = keysetPage; + } + + @Override + public KeysetPage getKeysetPage() { + return keysetPage; + } + + @Override + public KeysetPageable nextPageable() { + return (KeysetPageable) super.nextPageable(); + } + + @Override + public KeysetPageable previousPageable() { + return (KeysetPageable) super.previousPageable(); + } + + private static Pageable keysetPageable(KeysetPage keysetPage, Pageable pageable) { + if (pageable instanceof KeysetPageRequest) { + if (keysetPage == null || ((KeysetPageRequest) pageable).getKeysetPage() == keysetPage) { + return pageable; + } + } + + if (keysetPage == null) { + if (pageable instanceof KeysetPageable) { + return new KeysetPageRequest(null, pageable.getSort(), ((KeysetPageable) pageable).getIntOffset(), pageable.getPageSize()); + } else { + return new KeysetPageRequest(pageable.getPageNumber(), pageable.getPageSize(), null, pageable.getSort()); + } + } else { + return new KeysetPageRequest(keysetPage, pageable.getSort()); + } + } + +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/ParameterBinder.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/ParameterBinder.java new file mode 100644 index 0000000000..003f2c85f8 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/ParameterBinder.java @@ -0,0 +1,192 @@ +/* + * Copyright 2008-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blazebit.persistence.spring.data.base.query; + +import jakarta.persistence.Query; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.query.AbstractJpaQuery; +import org.springframework.data.jpa.repository.query.QueryUtils; +import org.springframework.data.repository.query.Parameter; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.util.Assert; + +import java.util.Date; + +/** + * {@link org.springframework.data.jpa.repository.query.ParameterBinder} is used to bind method parameters to a {@link Query}. This is usually done whenever an + * {@link AbstractJpaQuery} is executed. + * + * Christian Beikov: Copied to be able to share code between Spring Data integrations for 1.x and 2.x. + * + * @author Oliver Gierke + * @author Thomas Darimont + * @author Mark Paluch + * @author Eugen Mayer + * @since 1.6.9 + */ +public abstract class ParameterBinder { + + private final JpaParameters parameters; + private final ParameterAccessor accessor; + private final Object[] values; + + /** + * Creates a new {@link org.springframework.data.jpa.repository.query.ParameterBinder}. + * + * @param parameters must not be {@literal null}. + * @param values must not be {@literal null}. + */ + public ParameterBinder(JpaParameters parameters, Object[] values) { + + Assert.notNull(parameters, "JpaParameters must not be null!"); + Assert.notNull(values, "Values must not be null!"); + + Assert.isTrue(parameters.getNumberOfParameters() == values.length, "Invalid number of parameters given!"); + + this.parameters = parameters; + this.values = values.clone(); + this.accessor = new ParametersParameterAccessor(parameters, this.values); + } + + ParameterBinder(JpaParameters parameters) { + this(parameters, new Object[0]); + } + + /** + * Returns the {@link Pageable} of the parameters, if available. Returns {@code null} otherwise. + * + * @return + */ + public Pageable getPageable() { + return accessor.getPageable(); + } + + /** + * Returns the sort instance to be used for query creation. Will use a {@link Sort} parameter if available or the + * {@link Sort} contained in a {@link Pageable} if available. Returns {@code null} if no {@link Sort} can be found. + * + * @return + */ + public Sort getSort() { + return accessor.getSort(); + } + + /** + * Binds the parameters to the given {@link Query}. + * + * @param query + * @return + */ + public T bind(T query) { + + int bindableParameterIndex = 0; + int queryParameterPosition = 1; + + for (JpaParameters.JpaParameter parameter : parameters) { + + if (canBindParameter(parameter)) { + + Object value = accessor.getBindableValue(bindableParameterIndex); + bind(query, parameter, value, queryParameterPosition++); + bindableParameterIndex++; + } + } + + return query; + } + + /** + * Returns {@literal true} if the given parameter can be bound. + * + * @param parameter + * @return + */ + protected boolean canBindParameter(Parameter parameter) { + return parameter.isBindable(); + } + + /** + * Perform the actual query parameter binding. + * + * @param query + * @param parameter + * @param value + * @param position + */ + protected void bind(Query query, JpaParameters.JpaParameter parameter, Object value, int position) { + + if (parameter.isTemporalParameter()) { + if (hasNamedParameter(query) && parameter.isNamedParameter()) { + query.setParameter(parameter.getParameterName(), (Date) value, parameter.getTemporalType()); + } else { + query.setParameter(position, (Date) value, parameter.getTemporalType()); + } + return; + } + + if (hasNamedParameter(query) && parameter.isNamedParameter()) { + query.setParameter(parameter.getParameterName(), value); + } else { + query.setParameter(position, value); + } + } + + /** + * Binds the parameters to the given query and applies special parameter types (e.g. pagination). + * + * @param query + * @return + */ + public Query bindAndPrepare(Query query) { + return bindAndPrepare(query, parameters); + } + + boolean hasNamedParameter(Query query) { + return QueryUtils.hasNamedParameter(query); + } + + private Query bindAndPrepare(Query query, Parameters parameters) { + + Query result = bind(query); + + if (!parameters.hasPageableParameter() || getPageable() == null) { + return result; + } + + result.setFirstResult(getOffset()); + result.setMaxResults(getPageable().getPageSize()); + + return result; + } + + protected abstract int getOffset(); + + /** + * Returns the parameters. + * + * @return + */ + JpaParameters getParameters() { + return parameters; + } + + protected Object[] getValues() { + return values; + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/ParameterMetadataProvider.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/ParameterMetadataProvider.java new file mode 100644 index 0000000000..bec4ae4e01 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/query/ParameterMetadataProvider.java @@ -0,0 +1,96 @@ +/* + * Copyright 2011-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blazebit.persistence.spring.data.base.query; + +import jakarta.persistence.criteria.ParameterExpression; +import org.springframework.data.repository.query.Parameter; +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.parser.Part; + +import java.util.List; + +/** + * Helper class to allow easy creation of {@link ParameterMetadata}s. + * + * Christian Beikov: + * We have to copy the spring data ParameterMetadataProvider class unfortunately to be compatible. + * For better reuse, we introduced an interface that version specific integrations implement. + * + * @author Oliver Gierke + * @author Thomas Darimont + * @author Mark Paluch + * @author Eugen Mayer + * @since 1.6.9 + */ +public interface ParameterMetadataProvider { + + /** + * Returns all {@link ParameterMetadata}s built. + * + * @return the expressions + */ + public List> getExpressions(); + + /** + * Builds a new {@link ParameterMetadata} for given {@link Part} and the next {@link Parameter}. + * + * @param + * @return + */ + public ParameterMetadata next(Part part); + + /** + * Builds a new {@link ParameterMetadata} of the given {@link Part} and type. Forwards the underlying + * {@link Parameters} as well. + * + * @param + * @param type must not be {@literal null}. + * @return + */ + public ParameterMetadata next(Part part, Class type); + + /** + * @author Oliver Gierke + * @author Thomas Darimont + * @param + */ + public static interface ParameterMetadata { + + static final Object PLACEHOLDER = new Object(); + + /** + * Returns the {@link ParameterExpression}. + * + * @return the expression + */ + public ParameterExpression getExpression(); + + /** + * Returns whether the parameter shall be considered an {@literal IS NULL} parameter. + * + * @return + */ + public boolean isIsNullParameter(); + + /** + * Prepares the object before it's actually bound to the {@link jakarta.persistence.Query;}. + * + * @param value must not be {@literal null}. + * @return + */ + public Object prepare(Object value); + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/AbstractEntityViewAwareRepository.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/AbstractEntityViewAwareRepository.java new file mode 100644 index 0000000000..a70d528f12 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/AbstractEntityViewAwareRepository.java @@ -0,0 +1,768 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.base.repository; + +import com.blazebit.persistence.CriteriaBuilder; +import com.blazebit.persistence.CriteriaBuilderFactory; +import com.blazebit.persistence.PagedList; +import com.blazebit.persistence.PaginatedCriteriaBuilder; +import com.blazebit.persistence.criteria.BlazeCriteriaBuilder; +import com.blazebit.persistence.criteria.BlazeCriteriaQuery; +import com.blazebit.persistence.criteria.BlazeCriteria; +import com.blazebit.persistence.criteria.BlazeCriteriaDelete; +import com.blazebit.persistence.parser.EntityMetamodel; +import com.blazebit.persistence.spi.ExtendedManagedType; +import com.blazebit.persistence.spring.data.base.EntityViewSortUtil; +import com.blazebit.persistence.spring.data.base.query.KeysetAwarePageImpl; +import com.blazebit.persistence.spring.data.repository.KeysetPageable; +import com.blazebit.persistence.view.EntityViewManager; +import com.blazebit.persistence.view.EntityViewSetting; +import com.blazebit.persistence.view.spi.type.EntityViewProxy; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.LockModeType; +import jakarta.persistence.NoResultException; +import jakarta.persistence.Query; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Order; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.KeysetScrollPosition; +import org.springframework.data.domain.OffsetScrollPosition; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.query.EscapeCharacter; +import org.springframework.data.jpa.repository.query.JpaEntityGraph; +import org.springframework.data.jpa.repository.query.KeysetScrollSpecification; +import org.springframework.data.jpa.repository.query.QueryUtils; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.repository.query.FluentQuery; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static org.springframework.data.jpa.repository.query.QueryUtils.applyAndBind; +import static org.springframework.data.jpa.repository.query.QueryUtils.getQueryString; + +/** + * @author Moritz Becker + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +@Transactional(readOnly = true) +public abstract class AbstractEntityViewAwareRepository { + + private static final String ID_MUST_NOT_BE_NULL = "The given id must not be null!"; + private static final String DELETE_ALL_QUERY_STRING = "delete from %s x"; + private static final String DELETE_ALL_QUERY_BY_ID_STRING = "delete from %s x where %s in :ids"; + private static final String[] EMPTY = new String[0]; + private static final EscapeCharacter DEFAULT = EscapeCharacter.of('\\'); + private static final Pageable UNPAGED; + + static { + Pageable unpaged = null; + try { + Method unpagedMethod = Class.forName("org.springframework.data.domain.Pageable").getMethod("unpaged"); + unpaged = (Pageable) unpagedMethod.invoke(null); + } catch (Exception e) { + // ignore + } + UNPAGED = unpaged; + } + + protected EscapeCharacter escapeCharacter = DEFAULT; + + private final JpaEntityInformation entityInformation; + private final EntityManager entityManager; + private final CriteriaBuilderFactory cbf; + private final EntityViewManager evm; + private final Class entityViewClass; + private final String idAttributeName; + + private EntityViewAwareCrudMethodMetadata metadata; + + public AbstractEntityViewAwareRepository(JpaEntityInformation entityInformation, EntityManager entityManager, CriteriaBuilderFactory cbf, EntityViewManager evm, Class entityViewClass) { + this.entityInformation = entityInformation; + this.entityManager = entityManager; + this.cbf = cbf; + this.evm = evm; + this.entityViewClass = entityViewClass; + this.idAttributeName = getIdAttribute(getDomainClass()); + } + + public void setRepositoryMethodMetadata(EntityViewAwareCrudMethodMetadata crudMethodMetadata) { + this.metadata = crudMethodMetadata; + } + + public void setEscapeCharacter(EscapeCharacter escapeCharacter) { + this.escapeCharacter = escapeCharacter; + } + + protected EntityViewAwareCrudMethodMetadata getRepositoryMethodMetadata() { + return metadata; + } + + protected Class getDomainClass() { + return entityInformation.getJavaType(); + } + + protected EntityManager getEntityManager() { + return entityManager; + } + + protected abstract Map tryGetFetchGraphHints(EntityManager entityManager, JpaEntityGraph entityGraph, Class entityType); + + protected Map getQueryHints(boolean applyFetchGraph) { + if (metadata == null) { + return Collections.emptyMap(); + } + + if (metadata.getEntityGraph() == null || !applyFetchGraph) { + return metadata.getQueryHints(); + } + + Map hints = new HashMap(); + hints.putAll(metadata.getQueryHints()); + + hints.putAll(tryGetFetchGraphHints(entityManager, getEntityGraph(), getDomainClass())); + + return hints; + } + + private JpaEntityGraph getEntityGraph() { + String fallbackName = this.entityInformation.getEntityName() + "." + metadata.getMethod().getName(); + return new JpaEntityGraph(metadata.getEntityGraph(), fallbackName); + } + + @Transactional + public S save(S entity) { + if (entity instanceof EntityViewProxy) { + evm.save(entityManager, entity); + return entity; + } else if (entityInformation.isNew(entity)) { + entityManager.persist(entity); + return entity; + } else { + return entityManager.merge(entity); + } + } + + @Transactional + public List saveAll(Iterable entities) { + return save(entities); + } + + @Transactional + public List saveAllAndFlush(Iterable entities) { + List result = saveAll(entities); + flush(); + return result; + } + + @Transactional + public List save(Iterable entities) { + List result = new ArrayList(); + + if (entities == null) { + return result; + } + + for (S entity : entities) { + result.add(save(entity)); + } + + return result; + } + + @Transactional + public void flush() { + entityManager.flush(); + } + + @Transactional + public S saveAndFlush(S entity) { + S result = save(entity); + flush(); + + return result; + } + + @Transactional + public void deleteById(ID id) { + delete(id); + } + + @Transactional + public void delete(ID id) { + Assert.notNull(id, ID_MUST_NOT_BE_NULL); + + E entity = (E) findOne(id); + + if (entity == null) { + throw new EmptyResultDataAccessException( + String.format("No %s entity with id %s exists!", entityInformation.getJavaType(), id), 1); + } + + delete(entity); + } + + @Transactional + public void delete(E entity) { + Assert.notNull(entity, "The entity must not be null!"); + if (entity instanceof EntityViewProxy) { + evm.remove(entityManager, entity); + } else { + entityManager.remove(entityManager.contains(entity) ? entity : entityManager.merge(entity)); + } + } + + @Transactional + public void delete(Iterable entities) { + Assert.notNull(entities, "The given Iterable of entities not be null!"); + + for (E entity : entities) { + delete(entity); + } + } + + @Transactional + public void deleteAll() { + for (E element : (Iterable) findAll()) { + delete(element); + } + } + + @Transactional + public void deleteAll(Iterable entities) { + delete(entities); + } + + @Transactional + public void deleteInBatch(Iterable entities) { + Assert.notNull(entities, "The given Iterable of entities not be null!"); + + if (!entities.iterator().hasNext()) { + return; + } + + applyAndBind(getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName()), entities, entityManager) + .executeUpdate(); + } + + @Transactional + public void deleteAllInBatch(Iterable entities) { + deleteInBatch(entities); + } + + @Transactional + public void deleteAllById(Iterable ids) { + deleteAllByIdInBatch((Iterable) ids); + } + + @Transactional + public void deleteAllByIdInBatch(Iterable ids) { + + Assert.notNull(ids, "Ids must not be null!"); + + if (!ids.iterator().hasNext()) { + return; + } + + String queryTemplate = DELETE_ALL_QUERY_BY_ID_STRING; + String queryString = String.format(queryTemplate, entityInformation.getEntityName(), entityInformation.getIdAttribute().getName()); + + Query query = entityManager.createQuery(queryString); + query.setParameter("ids", ids); + + query.executeUpdate(); + } + + @Transactional + public void deleteAllInBatch() { + entityManager.createQuery(getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName())).executeUpdate(); + } + + @Transactional + public long delete(Specification spec) { + return getModificationQuery(spec, getDomainClass()).executeUpdate(); + } + + public E getOne(ID id) { + return (E) getReference(id); + } + + public E getById(ID id) { + return (E) getReference(id); + } + + public E getReferenceById(ID id) { + return (E) getReference(id); + } + + public long count(Example example) { + return executeCountQuery(getCountQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType())); + } + + public boolean exists(Example example) { + return !getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), (Sort) null).getResultList() + .isEmpty(); + } + + public boolean exists(Specification spec) { + return !getQuery(spec, getDomainClass(), (Sort) null).getResultList().isEmpty(); + } + + public List findAll(Example example) { + return getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), (Sort) null).getResultList(); + } + + public List findAll(Example example, Sort sort) { + return getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), sort).getResultList(); + } + + public Page findAll(Example example, Pageable pageable) { + Class probeType = example.getProbeType(); + TypedQuery query = getQuery(new ExampleSpecification<>(example, escapeCharacter), probeType, pageable); + + return pageable == null || pageable == UNPAGED ? new KeysetAwarePageImpl<>(query.getResultList()) : new KeysetAwarePageImpl<>((PagedList) query.getResultList(), pageable); + } + + public List findAll(Sort sort) { + return getQuery(null, sort).getResultList(); + } + + public Page findAll(Pageable pageable) { + if (pageable == null || pageable == UNPAGED) { + return (Page) new PageImpl<>(findAll()); + } + + return (Page) findAll((Specification) null, pageable); + } + + /** + * @author Christian Beikov + * @since 1.2.0 + */ + protected static class ExampleSpecification implements Specification { + + private static final Method GET_PREDICATE_NEW; + + static { + Method getPredicate = null; + try { + getPredicate = QueryByExamplePredicateBuilder.class.getMethod("getPredicate", Root.class, jakarta.persistence.criteria.CriteriaBuilder.class, Example.class, EscapeCharacter.class); + } catch (NoSuchMethodException e) { + // Ignore + } + GET_PREDICATE_NEW = getPredicate; + } + + private final Example example; + private final EscapeCharacter escapeCharacter; + + public ExampleSpecification(Example example, EscapeCharacter escapeCharacter) { + Assert.notNull(example, "Example must not be null!"); + Assert.notNull(escapeCharacter, "EscapeCharacter must not be null!"); + this.example = example; + this.escapeCharacter = escapeCharacter; + } + + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, jakarta.persistence.criteria.CriteriaBuilder cb) { + if (GET_PREDICATE_NEW != null) { + try { + return (Predicate) GET_PREDICATE_NEW.invoke(null, cb, example, escapeCharacter); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return QueryByExamplePredicateBuilder.getPredicate(root, cb, example); + } + } + + public V findOne(ID id) { + Assert.notNull(id, ID_MUST_NOT_BE_NULL); + + CriteriaBuilder cb = cbf.create(entityManager, getDomainClass()) + .where(idAttributeName).eq(id); + String[] fetches = EMPTY; + if (metadata != null && metadata.getEntityGraph() != null && (fetches = metadata.getEntityGraph().attributePaths()).length != 0) { + cb.fetch(fetches); + } + TypedQuery findOneQuery; + Class entityViewClass = metadata == null || metadata.getEntityViewClass() == null ? this.entityViewClass : (Class) metadata.getEntityViewClass(); + if (entityViewClass == null) { + findOneQuery = (TypedQuery) cb.getQuery(); + } else { + findOneQuery = evm.applySetting(EntityViewSetting.create(entityViewClass), cb).getQuery(); + } + + applyQueryHints(findOneQuery, fetches.length == 0); + + try { + return findOneQuery.getSingleResult(); + } catch (NoResultException e) { + return null; + } + } + + public R findBy(Specification spec, Function, R> queryFunction) { + return doFindBy(spec, getDomainClass(), queryFunction); + } + + private R doFindBy(Specification spec, Class domainClass, Function, R> queryFunction) { + Assert.notNull(spec, "Specification must not be null"); + Assert.notNull(queryFunction, "Query function must not be null"); + + FluentQuerySupport.ScrollQueryFactory scrollFunction = (sort, scrollPosition) -> { + + Specification specToUse = spec; + + if (scrollPosition instanceof KeysetScrollPosition) { + KeysetScrollSpecification keysetSpec = new KeysetScrollSpecification<>((KeysetScrollPosition) scrollPosition, sort, entityInformation); + sort = keysetSpec.sort(); + specToUse = specToUse.and(keysetSpec); + } + + TypedQuery query = getQuery(specToUse, domainClass, sort); + + if (scrollPosition instanceof OffsetScrollPosition) { + query.setFirstResult(Math.toIntExact(((OffsetScrollPosition) scrollPosition).getOffset())); + } + + return query; + }; + + Function> finder = sort -> getQuery(spec, domainClass, sort); + + FetchableFluentQueryBySpecification.SpecificationScrollDelegate scrollDelegate = new FetchableFluentQueryBySpecification.SpecificationScrollDelegate<>(scrollFunction, + entityInformation); + FluentQuery.FetchableFluentQuery fluentQuery = new FetchableFluentQueryBySpecification<>(spec, domainClass, finder, + scrollDelegate, this::count, this::exists, getEntityManager()); + + return queryFunction.apply((FluentQuery.FetchableFluentQuery) fluentQuery); + } + + public R findBy(Example example, Function, R> queryFunction) { + Assert.notNull(example, "Sample must not be null!"); + Assert.notNull(queryFunction, "Query function must not be null!"); + + ExampleSpecification spec = new ExampleSpecification<>(example, escapeCharacter); + Class probeType = example.getProbeType(); + + return doFindBy((Specification) spec, (Class) probeType, queryFunction); + } + + public V getReference(ID id) { + Assert.notNull(id, ID_MUST_NOT_BE_NULL); + Class entityViewClass = metadata == null || metadata.getEntityViewClass() == null ? this.entityViewClass : (Class) metadata.getEntityViewClass(); + if (entityViewClass == null) { + return (V) entityManager.getReference(getDomainClass(), id); + } else { + return evm.getReference(entityViewClass, id); + } + } + + public long count() { + TypedQuery countQuery = getCountQuery(null, getDomainClass()); + return ((Number) countQuery.getSingleResult()).longValue(); + } + + public boolean existsById(ID id) { + return exists(id); + } + + public boolean exists(ID id) { + Assert.notNull(id, ID_MUST_NOT_BE_NULL); + + TypedQuery existsQuery = cbf.create(entityManager, Object.class) + .from(getDomainClass()) + // Empty string because SQLServer can't interpret a number properly when using TOP clause + .select("''") + .where(idAttributeName).eq(id) + .setMaxResults(1) + .getQuery(); + + applyRepositoryMethodMetadata(existsQuery, true); + + try { + return !existsQuery.getResultList().isEmpty(); + } catch (NoResultException e) { + return false; + } + } + + public List findAll() { + return getQuery(null, getDomainClass(), null, null).getResultList(); + } + + public List findAllById(Iterable idIterable) { + return findAll(idIterable); + } + + public List findAll(Iterable idIterable) { + Assert.notNull(idIterable, ID_MUST_NOT_BE_NULL); + + List idList = new ArrayList<>(); + for (ID id : idIterable) { + idList.add(id); + } + CriteriaBuilder cb = cbf.create(entityManager, getDomainClass()) + .where(idAttributeName).in(idList); + + String[] fetches = EMPTY; + if (metadata != null && metadata.getEntityGraph() != null && (fetches = metadata.getEntityGraph().attributePaths()).length != 0) { + cb.fetch(fetches); + } + TypedQuery findAllByIdsQuery; + Class entityViewClass = metadata == null || metadata.getEntityViewClass() == null ? this.entityViewClass : (Class) metadata.getEntityViewClass(); + if (entityViewClass == null) { + findAllByIdsQuery = (TypedQuery) cb.getQuery(); + } else { + findAllByIdsQuery = evm.applySetting(EntityViewSetting.create(entityViewClass), cb).getQuery(); + } + + applyRepositoryMethodMetadata(findAllByIdsQuery, fetches.length == 0); + + return findAllByIdsQuery.getResultList(); + } + + private String getIdAttribute(Class entityClass) { + return cbf.getService(EntityMetamodel.class) + .getManagedType(ExtendedManagedType.class, entityClass) + .getIdAttribute() + .getName(); + } + + public List findAll(Specification spec) { + return (List) getQuery(spec, (Sort) null).getResultList(); + } + + public Page findAll(Specification spec, Pageable pageable) { + TypedQuery query = getQuery(spec, pageable); + if (pageable == null || pageable == UNPAGED) { + return new KeysetAwarePageImpl<>(query.getResultList()); + } + PagedList resultList = (PagedList) query.getResultList(); + Long total = resultList.getTotalSize(); + + if (total.equals(0L)) { + return new KeysetAwarePageImpl<>(Collections.emptyList(), total, null, pageable); + } + + return new KeysetAwarePageImpl<>(resultList, pageable); + } + + public List findAll(Specification spec, Sort sort) { + return (List) getQuery(spec, sort).getResultList(); + } + + public long count(Specification spec) { + return executeCountQuery(getCountQuery(spec, getDomainClass())); + } + + protected TypedQuery getQuery(Specification spec, Pageable pageable) { + Sort sort = pageable == null ? null : pageable.getSort(); + return this.getQuery(spec, getDomainClass(), pageable, sort); + } + + protected TypedQuery getQuery(Specification spec, Class domainClass, Pageable pageable) { + Sort sort = pageable == null ? null : pageable.getSort(); + return (TypedQuery) this.getQuery(spec, domainClass, pageable, sort); + } + + protected TypedQuery getQuery(Specification spec, Sort sort) { + return (TypedQuery) this.getQuery(spec, getDomainClass(), null, sort); + } + + protected TypedQuery getQuery(Specification spec, Class domainClass, Sort sort) { + return (TypedQuery) this.getQuery(spec, domainClass, null, sort); + } + + protected TypedQuery getQuery(Specification spec, Class domainClass, Pageable pageable, Sort sort) { + BlazeCriteriaQuery cq = BlazeCriteria.get(cbf, domainClass); + Root root = this.applySpecificationToCriteria(spec, domainClass, cq); + + Class entityViewClass = metadata == null + || metadata.getEntityViewClass() == null ? this.entityViewClass : (Class) metadata.getEntityViewClass(); + + if (sort != null && entityViewClass == null) { + cq.orderBy(QueryUtils.toOrders(sort, root, BlazeCriteria.get(cbf))); + } + CriteriaBuilder cb = cq.createCriteriaBuilder(entityManager); + + String[] fetches = EMPTY; + if (metadata != null && metadata.getEntityGraph() != null && (fetches = metadata.getEntityGraph().attributePaths()).length != 0) { + cb.fetch(fetches); + } + + boolean withCountQuery = true; + boolean withKeysetExtraction = false; + boolean withExtractAllKeysets = false; + + TypedQuery query; + if (entityViewClass == null) { + if (pageable == null || pageable == UNPAGED) { + query = (TypedQuery) cb.getQuery(); + } else { + PaginatedCriteriaBuilder paginatedCriteriaBuilder; + if (pageable instanceof KeysetPageable) { + KeysetPageable keysetPageable = (KeysetPageable) pageable; + paginatedCriteriaBuilder = cb.page(keysetPageable.getKeysetPage(), getOffset(pageable), pageable.getPageSize()); + withCountQuery = keysetPageable.isWithCountQuery(); + withKeysetExtraction = true; + withExtractAllKeysets = keysetPageable.isWithExtractAllKeysets(); + } else { + paginatedCriteriaBuilder = cb.page(getOffset(pageable), pageable.getPageSize()); + } + if (withKeysetExtraction) { + paginatedCriteriaBuilder.withKeysetExtraction(true); + paginatedCriteriaBuilder.withExtractAllKeysets(withExtractAllKeysets); + } + paginatedCriteriaBuilder.withCountQuery(withCountQuery); + query = (TypedQuery) paginatedCriteriaBuilder.getQuery(); + } + } else { + if (pageable == null || pageable == UNPAGED) { + EntityViewSetting> setting = EntityViewSetting.create(entityViewClass); + CriteriaBuilder fqb = evm.applySetting(setting, cb); + if (sort != null) { + EntityViewSortUtil.applySort(evm, entityViewClass, fqb, sort); + } + query = fqb.getQuery(); + } else { + EntityViewSetting> setting = EntityViewSetting.create(entityViewClass, getOffset(pageable), pageable.getPageSize()); + if (pageable instanceof KeysetPageable) { + KeysetPageable keysetPageable = (KeysetPageable) pageable; + setting.withKeysetPage(keysetPageable.getKeysetPage()); + withCountQuery = keysetPageable.isWithCountQuery(); + withKeysetExtraction = true; + withExtractAllKeysets = keysetPageable.isWithExtractAllKeysets(); + } + PaginatedCriteriaBuilder paginatedCriteriaBuilder = evm.applySetting(setting, cb); + if (withKeysetExtraction) { + paginatedCriteriaBuilder.withKeysetExtraction(true); + paginatedCriteriaBuilder.withExtractAllKeysets(withExtractAllKeysets); + } + paginatedCriteriaBuilder.withCountQuery(withCountQuery); + if (sort != null || (sort = pageable.getSort()) != null) { + EntityViewSortUtil.applySort(evm, entityViewClass, paginatedCriteriaBuilder, sort); + } + query = paginatedCriteriaBuilder.getQuery(); + } + } + + return this.applyRepositoryMethodMetadata(query, fetches.length == 0); + } + + protected Query getModificationQuery(Specification spec, Class domainClass) { + BlazeCriteriaBuilder cb = BlazeCriteria.get(cbf); + BlazeCriteriaDelete query = cb.createCriteriaDelete(getDomainClass()); + if (spec != null) { + Predicate predicate = spec.toPredicate(query.from(domainClass), null, cb); + if (predicate != null) { + query.where(predicate); + } + } + return query.createCriteriaBuilder(entityManager).getQuery(); + } + + protected abstract int getOffset(Pageable pageable); + + protected TypedQuery getCountQuery(Specification spec, Class domainClass) { + BlazeCriteriaBuilder builder = BlazeCriteria.get(cbf); + BlazeCriteriaQuery query = builder.createQuery(Long.class); + + Root root = applySpecificationToCriteria(spec, domainClass, query); + + if (query.isDistinct()) { + query.select(builder.countDistinct(root)); + } else { + query.select(builder.count(root)); + } + + // Remove all Orders the Specifications might have applied + query.orderBy(Collections.emptyList()); + + return this.applyRepositoryMethodMetadata(query.createCriteriaBuilder(entityManager).getQuery(), true); + } + + private Root applySpecificationToCriteria(Specification spec, Class domainClass, CriteriaQuery query) { + Assert.notNull(domainClass, "Domain class must not be null!"); + Assert.notNull(query, "CriteriaQuery must not be null!"); + Root root = query.from(domainClass); + if (spec == null) { + return root; + } else { + Predicate predicate = spec.toPredicate(root, query, ((BlazeCriteriaQuery) query).getCriteriaBuilder()); + if (predicate != null) { + query.where(predicate); + } + + return root; + } + } + + private TypedQuery applyRepositoryMethodMetadata(TypedQuery query, boolean applyFetchGraph) { + if (this.metadata == null) { + return query; + } else { + LockModeType type = this.metadata.getLockModeType(); + TypedQuery toReturn = type == null ? query : query.setLockMode(type); + this.applyQueryHints(toReturn, applyFetchGraph); + return toReturn; + } + } + + private void applyQueryHints(Query query, boolean applyFetchGraph) { + for (Map.Entry hint : getQueryHints(applyFetchGraph).entrySet()) { + query.setHint(hint.getKey(), hint.getValue()); + } + } + + private static Long executeCountQuery(TypedQuery query) { + + Assert.notNull(query, "TypedQuery must not be null!"); + + List totals = query.getResultList(); + Long total = 0L; + + for (Long element : totals) { + total += element == null ? 0 : element; + } + + return total; + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/EntityGraphFactory.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/EntityGraphFactory.java new file mode 100644 index 0000000000..9daf07d4fc --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/EntityGraphFactory.java @@ -0,0 +1,79 @@ +/* + * Copyright 2021-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blazebit.persistence.spring.data.base.repository; + +import jakarta.persistence.EntityGraph; +import jakarta.persistence.EntityManager; +import jakarta.persistence.Subgraph; +import org.springframework.data.mapping.PropertyPath; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Factory class to create an {@link EntityGraph} from a collection of property paths. + * + * @author Jens Schauder + * @author Petr Strnad + * @author Greg Turnquist + * @since 2.6 + */ +public abstract class EntityGraphFactory { + + public static final String HINT = "jakarta.persistence.fetchgraph"; + + /** + * Create an {@link EntityGraph} from a collection of properties. + * + * @param domainType + * @param properties + */ + public static EntityGraph create(EntityManager entityManager, Class domainType, Set properties) { + + EntityGraph entityGraph = entityManager.createEntityGraph(domainType); + Map> existingSubgraphs = new HashMap<>(); + + for (String property : properties) { + + Subgraph current = null; + String currentFullPath = ""; + + for (PropertyPath path : PropertyPath.from(property, domainType)) { + + currentFullPath += path.getSegment() + "."; + + if (path.hasNext()) { + final Subgraph finalCurrent = current; + current = current == null + ? existingSubgraphs.computeIfAbsent(currentFullPath, k -> entityGraph.addSubgraph(path.getSegment())) + : existingSubgraphs.computeIfAbsent(currentFullPath, k -> finalCurrent.addSubgraph(path.getSegment())); + continue; + } + + if (current == null) { + entityGraph.addAttributeNodes(path.getSegment()); + } else { + current.addAttributeNodes(path.getSegment()); + + } + } + } + + return entityGraph; + } + +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/EntityViewAwareCrudMethodMetadata.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/EntityViewAwareCrudMethodMetadata.java new file mode 100644 index 0000000000..5e79d2e173 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/EntityViewAwareCrudMethodMetadata.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.base.repository; + +import jakarta.persistence.LockModeType; +import org.springframework.data.jpa.repository.EntityGraph; + +import java.lang.reflect.Method; +import java.util.Map; + +/** + * Variant that is aware of entity views. + * + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public interface EntityViewAwareCrudMethodMetadata { + + LockModeType getLockModeType(); + + Map getQueryHints(); + + EntityGraph getEntityGraph(); + + Method getMethod(); + + Class getEntityViewClass(); +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/EntityViewAwareCrudMethodMetadataPostProcessor.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/EntityViewAwareCrudMethodMetadataPostProcessor.java new file mode 100644 index 0000000000..4b3a5ec604 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/EntityViewAwareCrudMethodMetadataPostProcessor.java @@ -0,0 +1,367 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * Copyright 2011-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.base.repository; + +import com.blazebit.persistence.spring.data.base.query.EntityViewAwareRepositoryMetadata; +import com.blazebit.persistence.view.EntityViewManager; +import com.blazebit.persistence.view.metamodel.ManagedViewType; +import com.blazebit.reflection.ReflectionUtils; +import jakarta.persistence.LockModeType; +import jakarta.persistence.QueryHint; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.ProxyMethodInvocation; +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.interceptor.ExposeInvocationInterceptor; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.QueryHints; +import org.springframework.data.jpa.repository.support.CrudMethodMetadata; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Adapted {@link org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor} to be able to use the {@link EntityViewManager} so that we can safely determine if a class is an entity view. + * + * @author Oliver Gierke + * @author Thomas Darimont + * @author Christoph Strobl + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public class EntityViewAwareCrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, BeanClassLoaderAware { + + private ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader) + */ + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader == null ? ClassUtils.getDefaultClassLoader() : classLoader; + + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.RepositoryProxyPostProcessor#postProcess(org.springframework.aop.framework.ProxyFactory, org.springframework.data.repository.core.RepositoryInformation) + */ + @Override + public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) { + factory.addAdvice(new CrudMethodMetadataPopulatingMethodInterceptor(((EntityViewAwareRepositoryMetadata) repositoryInformation).getEntityViewManager())); + } + + /** + * Returns a {@link CrudMethodMetadata} proxy that will lookup the actual target object by obtaining a thread bound + * instance from the {@link TransactionSynchronizationManager} later. + */ + public EntityViewAwareCrudMethodMetadata getCrudMethodMetadata() { + + ProxyFactory factory = new ProxyFactory(); + + factory.addInterface(EntityViewAwareCrudMethodMetadata.class); + factory.setTargetSource(new ThreadBoundTargetSource()); + + return (EntityViewAwareCrudMethodMetadata) factory.getProxy(this.classLoader); + } + + /** + * {@link MethodInterceptor} to build and cache {@link EntityViewAwareDefaultCrudMethodMetadata} instances for the invoked methods. + * Will bind the found information to a {@link TransactionSynchronizationManager} for later lookup. + * + * @author Oliver Gierke + * @author Thomas Darimont + * @see EntityViewAwareDefaultCrudMethodMetadata + */ + private static final class CrudMethodMetadataPopulatingMethodInterceptor implements MethodInterceptor { + + private final ConcurrentMap metadataCache = new ConcurrentHashMap<>(); + private final EntityViewManager evm; + + private CrudMethodMetadataPopulatingMethodInterceptor(EntityViewManager evm) { + this.evm = evm; + } + + /* + * (non-Javadoc) + * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) + */ + public Object invoke(MethodInvocation invocation) throws Throwable { + Method method = invocation.getMethod(); + EntityViewAwareCrudMethodMetadata metadata = (EntityViewAwareCrudMethodMetadata) TransactionSynchronizationManager.getResource(method); + + if (metadata != null) { + return invocation.proceed(); + } + + EntityViewMetadataCacheKey cacheKey = new EntityViewMetadataCacheKey(method, ((ProxyMethodInvocation) invocation).getProxy().getClass()); + EntityViewAwareCrudMethodMetadata methodMetadata = metadataCache.get(cacheKey); + + if (methodMetadata == null) { + methodMetadata = new EntityViewAwareDefaultCrudMethodMetadata(cacheKey.proxyClass, method, evm); + EntityViewAwareCrudMethodMetadata tmp = metadataCache.putIfAbsent(cacheKey, methodMetadata); + + if (tmp != null) { + methodMetadata = tmp; + } + } + + TransactionSynchronizationManager.bindResource(method, methodMetadata); + + try { + return invocation.proceed(); + } finally { + TransactionSynchronizationManager.unbindResource(method); + } + } + } + + /** + * Cache key for entity view metadata. + * + * @author Christian Beikov + * @since 1.3.0 + */ + private static class EntityViewMetadataCacheKey { + private final Method method; + private final Class proxyClass; + + public EntityViewMetadataCacheKey(Method method, Class proxyClass) { + this.method = method; + this.proxyClass = proxyClass; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof EntityViewMetadataCacheKey)) { + return false; + } + + EntityViewMetadataCacheKey that = (EntityViewMetadataCacheKey) o; + return method.equals(that.method) && proxyClass.equals(that.proxyClass); + } + + @Override + public int hashCode() { + int result = method.hashCode(); + result = 31 * result + proxyClass.hashCode(); + return result; + } + } + + /** + * Default implementation of {@link CrudMethodMetadata} that will inspect the backing method for annotations. + * + * @author Oliver Gierke + * @author Thomas Darimont + */ + private static class EntityViewAwareDefaultCrudMethodMetadata implements EntityViewAwareCrudMethodMetadata { + + private final LockModeType lockModeType; + private final Map queryHints; + private final EntityGraph entityGraph; + private final Class entityViewClass; + private final Method method; + + /** + * Creates a new {@link EntityViewAwareDefaultCrudMethodMetadata} for the given {@link Method}. + * @param repositoryClass The repository class + * @param method must not be {@literal null}. + * @param evm the {@link EntityViewManager} + */ + public EntityViewAwareDefaultCrudMethodMetadata(Class repositoryClass, Method method, EntityViewManager evm) { + Assert.notNull(method, "Method must not be null!"); + + Method annotationTargetMethod = findAnnotationTargetMethod(method); + this.lockModeType = findLockModeType(annotationTargetMethod); + this.queryHints = findQueryHints(annotationTargetMethod); + this.entityGraph = findEntityGraph(annotationTargetMethod); + this.entityViewClass = findEntityViewClass(repositoryClass, method, evm); + this.method = method; + } + + private static Method findAnnotationTargetMethod(Method method) { + if (method.isBridge() || method.isSynthetic()) { + try { + return method.getDeclaringClass().getMethod(method.getName(), method.getParameterTypes()); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + return method; + } + + private static Class findEntityViewClass(Class repositoryClass, Method methodToAnalyze, EntityViewManager evm) { + Class entityViewClass; + Class[] typeArguments = ReflectionUtils.getResolvedMethodReturnTypeArguments(repositoryClass, methodToAnalyze); + if (typeArguments.length == 0) { + entityViewClass = ReflectionUtils.getResolvedMethodReturnType(repositoryClass, methodToAnalyze); + } else { + entityViewClass = typeArguments[typeArguments.length - 1]; + } + ManagedViewType managedViewType = evm.getMetamodel().managedView(entityViewClass); + if (managedViewType == null) { + return null; + } + return managedViewType.getJavaType(); + } + + private static EntityGraph findEntityGraph(Method method) { + return AnnotatedElementUtils.findMergedAnnotation(method, EntityGraph.class); + } + + private static LockModeType findLockModeType(Method method) { + + Lock annotation = AnnotatedElementUtils.findMergedAnnotation(method, Lock.class); + return annotation == null ? null : (LockModeType) AnnotationUtils.getValue(annotation); + } + + private static Map findQueryHints(Method method) { + + Map queryHints = new HashMap(); + QueryHints queryHintsAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, QueryHints.class); + + if (queryHintsAnnotation != null) { + + for (QueryHint hint : queryHintsAnnotation.value()) { + queryHints.put(hint.name(), hint.value()); + } + } + + QueryHint queryHintAnnotation = AnnotationUtils.findAnnotation(method, QueryHint.class); + + if (queryHintAnnotation != null) { + queryHints.put(queryHintAnnotation.name(), queryHintAnnotation.value()); + } + + return Collections.unmodifiableMap(queryHints); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getLockModeType() + */ + @Override + public LockModeType getLockModeType() { + return lockModeType; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getQueryHints() + */ + @Override + public Map getQueryHints() { + return queryHints; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getEntityGraph() + */ + @Override + public EntityGraph getEntityGraph() { + return entityGraph; + } + + /* + * (non-Javadoc) + * @see com.blazebit.persistence.spring.data.impl.repository.EntityViewAwareCrudMethodMetadata#getEntityViewClass() + */ + @Override + public Class getEntityViewClass() { + return entityViewClass; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getMethod() + */ + @Override + public Method getMethod() { + return method; + } + } + + /** + * @author Oliver Gierke + * @author Thomas Darimont + * @author Christoph Strobl + * @author Christian Beikov + * @since 1.2.0 + */ + private static class ThreadBoundTargetSource implements TargetSource { + + /* + * (non-Javadoc) + * @see org.springframework.aop.TargetSource#getTargetClass() + */ + @Override + public Class getTargetClass() { + return EntityViewAwareCrudMethodMetadata.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.aop.TargetSource#isStatic() + */ + @Override + public boolean isStatic() { + return false; + } + + /* + * (non-Javadoc) + * @see org.springframework.aop.TargetSource#getTarget() + */ + @Override + public Object getTarget() throws Exception { + + MethodInvocation invocation = ExposeInvocationInterceptor.currentInvocation(); + return TransactionSynchronizationManager.getResource(invocation.getMethod()); + } + + /* + * (non-Javadoc) + * @see org.springframework.aop.TargetSource#releaseTarget(java.lang.Object) + */ + @Override + public void releaseTarget(Object target) throws Exception { + } + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/FetchableFluentQueryBySpecification.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/FetchableFluentQueryBySpecification.java new file mode 100644 index 0000000000..fa8d70a681 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/FetchableFluentQueryBySpecification.java @@ -0,0 +1,245 @@ +/* + * Copyright 2021-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blazebit.persistence.spring.data.base.repository; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.Query; +import jakarta.persistence.TypedQuery; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Window; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.query.ScrollDelegate; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.jpa.support.PageableUtils; +import org.springframework.data.repository.query.FluentQuery; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Immutable implementation of {@link FetchableFluentQuery} based on a {@link Specification}. All methods that return a + * {@link FetchableFluentQuery} will return a new instance, not the original. + * + * @param Domain type + * @param Result type + * @author Greg Turnquist + * @since 3.0 + */ +public class FetchableFluentQueryBySpecification extends FluentQuerySupport + implements FluentQuery.FetchableFluentQuery { + + private final Specification spec; + private final Function> finder; + private final SpecificationScrollDelegate scroll; + private final Function, Long> countOperation; + private final Function, Boolean> existsOperation; + private final EntityManager entityManager; + + public FetchableFluentQueryBySpecification(Specification spec, Class entityType, + Function> finder, SpecificationScrollDelegate scrollDelegate, + Function, Long> countOperation, Function, Boolean> existsOperation, + EntityManager entityManager) { + this(spec, entityType, (Class) entityType, Sort.unsorted(), 0, Collections.emptySet(), finder, scrollDelegate, + countOperation, existsOperation, entityManager); + } + + private FetchableFluentQueryBySpecification(Specification spec, Class entityType, Class resultType, + Sort sort, int limit, Collection properties, Function> finder, + SpecificationScrollDelegate scrollDelegate, Function, Long> countOperation, + Function, Boolean> existsOperation, EntityManager entityManager) { + + super(resultType, sort, limit, properties, entityType); + this.spec = spec; + this.finder = finder; + this.scroll = scrollDelegate; + this.countOperation = countOperation; + this.existsOperation = existsOperation; + this.entityManager = entityManager; + } + + @Override + public FetchableFluentQuery sortBy(Sort sort) { + + Assert.notNull(sort, "Sort must not be null"); + + return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, this.sort.and(sort), limit, + properties, finder, scroll, countOperation, existsOperation, entityManager); + } + + @Override + public FetchableFluentQuery limit(int limit) { + + Assert.isTrue(limit >= 0, "Limit must not be negative"); + + return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, this.sort.and(sort), limit, + properties, finder, scroll, countOperation, existsOperation, entityManager); + } + + @Override + public FetchableFluentQuery as(Class resultType) { + + Assert.notNull(resultType, "Projection target type must not be null"); + if (!resultType.isInterface()) { + throw new UnsupportedOperationException("Class-based DTOs are not yet supported."); + } + + return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort, limit, properties, finder, + scroll, countOperation, existsOperation, entityManager); + } + + @Override + public FetchableFluentQuery project(Collection properties) { + + return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort, limit, properties, finder, + scroll, countOperation, existsOperation, entityManager); + } + + @Override + public R oneValue() { + + List results = createSortedAndProjectedQuery() // + .setMaxResults(2) // Never need more than 2 values + .getResultList(); + + if (results.size() > 1) { + throw new IncorrectResultSizeDataAccessException(1); + } + + return results.isEmpty() ? null : getConversionFunction().apply(results.get(0)); + } + + @Override + public R firstValue() { + + List results = createSortedAndProjectedQuery() // + .setMaxResults(1) // Never need more than 1 value + .getResultList(); + + return results.isEmpty() ? null : getConversionFunction().apply(results.get(0)); + } + + @Override + public List all() { + return convert(createSortedAndProjectedQuery().getResultList()); + } + + @Override + public Window scroll(ScrollPosition scrollPosition) { + + Assert.notNull(scrollPosition, "ScrollPosition must not be null"); + + return scroll.scroll(sort, limit, scrollPosition).map(getConversionFunction()); + } + + @Override + public Page page(Pageable pageable) { + return pageable.isUnpaged() ? new PageImpl<>(all()) : readPage(pageable); + } + + @Override + public Stream stream() { + + return createSortedAndProjectedQuery() // + .getResultStream() // + .map(getConversionFunction()); + } + + @Override + public long count() { + return countOperation.apply(spec); + } + + @Override + public boolean exists() { + return existsOperation.apply(spec); + } + + private TypedQuery createSortedAndProjectedQuery() { + + TypedQuery query = finder.apply(sort); + + if (!properties.isEmpty()) { + query.setHint(EntityGraphFactory.HINT, EntityGraphFactory.create(entityManager, entityType, properties)); + } + + if (limit != 0) { + query.setMaxResults(limit); + } + + return query; + } + + private Page readPage(Pageable pageable) { + + TypedQuery pagedQuery = createSortedAndProjectedQuery(); + + if (pageable.isPaged()) { + pagedQuery.setFirstResult(PageableUtils.getOffsetAsInteger(pageable)); + pagedQuery.setMaxResults(pageable.getPageSize()); + } + + List paginatedResults = convert(pagedQuery.getResultList()); + + return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> countOperation.apply(spec)); + } + + private List convert(List resultList) { + + Function conversionFunction = getConversionFunction(); + List mapped = new ArrayList<>(resultList.size()); + + for (S s : resultList) { + mapped.add(conversionFunction.apply(s)); + } + return mapped; + } + + private Function getConversionFunction() { + return getConversionFunction(entityType, resultType); + } + + static class SpecificationScrollDelegate extends ScrollDelegate { + + private final ScrollQueryFactory scrollFunction; + + SpecificationScrollDelegate(ScrollQueryFactory scrollQueryFactory, JpaEntityInformation entity) { + super(entity); + this.scrollFunction = scrollQueryFactory; + } + + public Window scroll(Sort sort, int limit, ScrollPosition scrollPosition) { + + Query query = scrollFunction.createQuery(sort, scrollPosition); + + if (limit > 0) { + query = query.setMaxResults(limit); + } + + return scroll(query, sort, scrollPosition); + } + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/FluentQuerySupport.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/FluentQuerySupport.java new file mode 100644 index 0000000000..8266aefbba --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/base/repository/FluentQuerySupport.java @@ -0,0 +1,90 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blazebit.persistence.spring.data.base.repository; + +import jakarta.persistence.Query; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Sort; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; + +/** + * Supporting class containing some state and convenience methods for building and executing fluent queries. + * + * Christian Beikov: Copied to be able to share code between Spring Data integrations for 2.6 and 2.7. + * + * @param The resulting type of the query. + * @author Greg Turnquist + * @author Jens Schauder + * @since 2.6 + */ +public abstract class FluentQuerySupport { + + protected final Class resultType; + protected final Sort sort; + protected final int limit; + protected final Set properties; + protected final Class entityType; + + private final SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory(); + + public FluentQuerySupport(Class resultType, Sort sort, int limit, Collection properties, Class entityType) { + + this.resultType = resultType; + this.sort = sort; + this.limit = limit; + + if (properties != null) { + this.properties = new HashSet<>(properties); + } else { + this.properties = Collections.emptySet(); + } + + this.entityType = entityType; + } + + final Collection mergeProperties(Collection additionalProperties) { + + Set newProperties = new HashSet<>(); + newProperties.addAll(properties); + newProperties.addAll(additionalProperties); + return Collections.unmodifiableCollection(newProperties); + } + + @SuppressWarnings("unchecked") + final Function getConversionFunction(Class inputType, Class targetType) { + + if (targetType.isAssignableFrom(inputType)) { + return (Function) Function.identity(); + } + + if (targetType.isInterface()) { + return o -> projectionFactory.createProjection(targetType, o); + } + + return o -> DefaultConversionService.getSharedInstance().convert(o, targetType); + } + + interface ScrollQueryFactory { + Query createQuery(Sort sort, ScrollPosition scrollPosition); + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/BlazeSpecification.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/BlazeSpecification.java new file mode 100644 index 0000000000..702d3902f1 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/BlazeSpecification.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.repository; + +import com.blazebit.persistence.CriteriaBuilder; + +/** + * @author Moritz Becker + * @author Eugen Mayer + * @since 1.6.9 + */ +public interface BlazeSpecification { + + void applySpecification(String rootAlias, CriteriaBuilder builder); +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewReplacingMethodInterceptor.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewReplacingMethodInterceptor.java new file mode 100644 index 0000000000..a0aa40c658 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewReplacingMethodInterceptor.java @@ -0,0 +1,73 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.repository; + +import com.blazebit.persistence.view.EntityViewManager; +import com.blazebit.persistence.view.spi.type.BasicDirtyTracker; +import com.blazebit.persistence.view.spi.type.EntityViewProxy; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor; + +import jakarta.persistence.EntityManager; + +/** + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public class EntityViewReplacingMethodInterceptor implements MethodInterceptor, RepositoryProxyPostProcessor { + + private final EntityManager em; + private final EntityViewManager evm; + + public EntityViewReplacingMethodInterceptor(EntityManager em, EntityViewManager evm) { + this.em = em; + this.evm = evm; + } + + @Override + public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) { + factory.addAdvice(this); + } + + @Override + public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable { + Object o = invocation.proceed(); + + if (invocation.getMethod().getName().startsWith("save")) { + Object[] arguments = invocation.getArguments(); + if (arguments.length == 1) { + arguments[0] = convertToEntity(arguments[0]); + } else { + return convertToEntity(o); + } + } + + return o; + } + + private Object convertToEntity(Object entityOrView) { + if (entityOrView instanceof BasicDirtyTracker) { + EntityViewProxy view = (EntityViewProxy) entityOrView; + return evm.getEntityReference(em, view); + } + return entityOrView; + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewRepository.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewRepository.java new file mode 100644 index 0000000000..c223b03e8c --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewRepository.java @@ -0,0 +1,74 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.repository; + +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.Repository; + +import java.io.Serializable; + +/** + * Base entity view repository interface. + * + * @param Entity view type. + * @param Entity ID type. + * + * @author Moritz Becker + * @author Eugen Mayer + * @since 1.6.9 + */ +@NoRepositoryBean +public interface EntityViewRepository extends Repository { + + /** + * Finds the entity view of type {@code } with the given id. + * + * @param id the id of the entity view of type {@code } to find + * @return the entity view of type {@code } with the given id + */ + T findOne(ID id); + + /** + * Checks if an entity view of type {@code } with the given id exists. + * + * @param id the id to check for existence + * @return true if an entity view of type {@code } exists, else false + */ + boolean exists(ID id); + + /** + * Returns all entity views of type {@code }. + * + * @return an iterator over all entity views of type {@code } + */ + Iterable findAll(); + + /** + * Finds all entity views of type {@code } with the given ids. + * + * @param idIterable the ids of the entity views of type {@code } to find + * @return an iterator over the entity views of type {@code } + */ + Iterable findAll(Iterable idIterable); + + /** + * Gets the number of existing entity views.of type {@code }. + * + * @return the number of existing entity views of type {@code } + */ + long count(); +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewSettingProcessor.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewSettingProcessor.java new file mode 100644 index 0000000000..0453395c26 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewSettingProcessor.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.repository; + +import com.blazebit.persistence.view.EntityViewSetting; + +/** + * @author Giovanni Lovato + * @author Eugen Mayer + * @since 1.6.9 + */ +public interface EntityViewSettingProcessor { + + /** + * Processes the {@link EntityViewSetting} to allow additional Entity View customization during query creation. + * + * @param setting the {@link EntityViewSetting} to be processed + * @return the final {@link EntityViewSetting} to allow further processing + */ + EntityViewSetting acceptEntityViewSetting(EntityViewSetting setting); +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewSpecificationExecutor.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewSpecificationExecutor.java new file mode 100644 index 0000000000..15e14feb72 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/EntityViewSpecificationExecutor.java @@ -0,0 +1,79 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; + +import java.util.List; + +/** + * Like {@link org.springframework.data.jpa.repository.JpaSpecificationExecutor} but allows to specify an entity view + * return type. + * + * @param The view type + * @param The entity type + * @author Moritz Becker + * @author Eugen Mayer + * @since 1.6.9 + */ +public interface EntityViewSpecificationExecutor { + + /** + * Returns a single view matching the given {@link Specification}. + * + * @param spec The specification for filtering + * @return The matching view + */ + V findOne(Specification spec); + + /** + * Returns all views matching the given {@link Specification}. + * + * @param spec The specification for filtering + * @return All matching views + */ + List findAll(Specification spec); + + /** + * Returns a {@link Page} of views matching the given {@link Specification}. + * + * @param spec The specification for filtering + * @param pageable The pagination information + * @return The requested page of matching views + */ + Page findAll(Specification spec, Pageable pageable); + + /** + * Returns all views matching the given {@link Specification} in the order defined by {@link Sort}. + * + * @param spec The specification for filtering + * @param sort The sort order definition + * @return All matching views in the requested order + */ + List findAll(Specification spec, Sort sort); + + /** + * Returns the number of instances that the given {@link Specification} will return. + * + * @param spec the {@link Specification} to count instances for + * @return the number of instances + */ + long count(Specification spec); +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetAwarePage.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetAwarePage.java new file mode 100644 index 0000000000..3ecfd73198 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetAwarePage.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.repository; + +import com.blazebit.persistence.KeysetPage; +import org.springframework.data.domain.Page; + +/** + * Like {@link Page} but contains keyset information. + * + * @param Element type. + * + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public interface KeysetAwarePage extends Page, KeysetAwareSlice { + + /** + * Returns the keyset page associated to the results of this page. + * + * @return The keyset page + */ + public KeysetPage getKeysetPage(); + + @Override + KeysetPageable nextPageable(); + + @Override + KeysetPageable previousPageable(); +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetAwareSlice.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetAwareSlice.java new file mode 100644 index 0000000000..80805af6de --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetAwareSlice.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.repository; + +import com.blazebit.persistence.KeysetPage; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Slice; + +/** + * Like {@link Page} but contains keyset information. + * + * @param Element type. + * + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public interface KeysetAwareSlice extends Slice { + + /** + * Returns the keyset page associated to the results of this page. + * + * @return The keyset page + */ + public KeysetPage getKeysetPage(); + + @Override + KeysetPageable nextPageable(); + + @Override + KeysetPageable previousPageable(); +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetPageRequest.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetPageRequest.java new file mode 100644 index 0000000000..a3821a5b75 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetPageRequest.java @@ -0,0 +1,183 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.repository; + +import com.blazebit.persistence.KeysetPage; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +/** + * Like {@link org.springframework.data.domain.PageRequest} but with support for keyset pagination. + * + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public class KeysetPageRequest extends PageRequest implements KeysetPageable { + + private final KeysetPage keysetPage; + private final int offset; + private final boolean withCountQuery; + private final boolean withExtractAllKeysets; + + /** + * Construct a page request with an optional keyset page that may be used for keyset pagination. + * + * @param page The page number, 0-based + * @param pageSize The number of elements per page + * @param keysetPage The keyset page + * @param sort The sort specification + */ + public KeysetPageRequest(int page, int pageSize, KeysetPage keysetPage, Sort sort) { + this(keysetPage, sort, page * pageSize, pageSize); + } + + /** + * Construct a page request from a pageable with an optional keyset page that may be used for keyset pagination. + * + * @param keysetPage The keyset page + * @param pageable The pageable + */ + public KeysetPageRequest(KeysetPage keysetPage, Pageable pageable) { + this(keysetPage, pageable.getSort(), pageable.getPageNumber() * pageable.getPageSize(), pageable.getPageSize()); + } + + /** + * Construct a page request representing the current page via a keyset page and a sort specification. + * + * @param keysetPage The keyset page + * @param sort The sort specification + */ + public KeysetPageRequest(KeysetPage keysetPage, Sort sort) { + this(keysetPage, sort, keysetPage.getFirstResult(), keysetPage.getMaxResults()); + } + + /** + * Construct a page request with an optional keyset page that may be used for keyset pagination. + * + * @param keysetPage The keyset page + * @param sort The sort specification + * @param offset The offset number, 0-based + * @param pageSize The number of elements per page + * @since 1.3.0 + */ + public KeysetPageRequest(KeysetPage keysetPage, Sort sort, int offset, int pageSize) { + this(keysetPage, sort, offset, pageSize, true, false); + } + + /** + * Construct a page request with an optional keyset page that may be used for keyset pagination and flags to enable the count query and keyset extraction. + * + * @param keysetPage The keyset page + * @param sort The sort specification + * @param offset The offset number, 0-based + * @param pageSize The number of elements per page + * @param withCountQuery True to enable the count query + * @param withExtractAllKeysets True to enable extraction of all keysets + * @since 1.4.0 + */ + public KeysetPageRequest(KeysetPage keysetPage, Sort sort, int offset, int pageSize, boolean withCountQuery, boolean withExtractAllKeysets) { + super(offset / pageSize, pageSize, sort); + this.keysetPage = keysetPage; + this.offset = offset; + this.withCountQuery = withCountQuery; + this.withExtractAllKeysets = withExtractAllKeysets; + } + + @Override + public boolean isWithCountQuery() { + return withCountQuery; + } + + @Override + public boolean isWithExtractAllKeysets() { + return withExtractAllKeysets; + } + + @Override + public int getIntOffset() { + return offset; + } + + + @Override + public KeysetPage getKeysetPage() { + return keysetPage; + } + + @Override + public KeysetPageRequest next() { + return new KeysetPageRequest(keysetPage, getSort(), getIntOffset() + getPageSize(), getPageSize(), isWithCountQuery(), isWithExtractAllKeysets()); + } + + @Override + public KeysetPageRequest previousOrFirst() { + if (getIntOffset() == 0) { + return this; + } + return new KeysetPageRequest(keysetPage, getSort(), getIntOffset() - getPageSize(), getPageSize(), isWithCountQuery(), isWithExtractAllKeysets()); + } + + @Override + public KeysetPageRequest first() { + if (getIntOffset() == 0) { + return this; + } + return new KeysetPageRequest(keysetPage, getSort(), 0, getPageSize(), isWithCountQuery(), isWithExtractAllKeysets()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof KeysetPageable)) { + return false; + } + + KeysetPageable that = (KeysetPageable) o; + + if (isWithCountQuery() != that.isWithCountQuery()) { + return false; + } + if (isWithExtractAllKeysets() != that.isWithExtractAllKeysets()) { + return false; + } + if (getIntOffset() != that.getIntOffset()) { + return false; + } + if (getPageSize() != that.getPageSize()) { + return false; + } + if (getKeysetPage() != null ? !getKeysetPage().equals(that.getKeysetPage()) : that.getKeysetPage() != null) { + return false; + } + return getSort() != null ? getSort().equals(that.getSort()) : that.getSort() == null; + } + + @Override + public int hashCode() { + int result = getKeysetPage() != null ? getKeysetPage().hashCode() : 0; + result = 31 * result + (getSort() != null ? getSort().hashCode() : 0); + result = 31 * result + (isWithCountQuery() ? 1 : 0); + result = 31 * result + (isWithExtractAllKeysets() ? 1 : 0); + result = 31 * result + getIntOffset(); + result = 31 * result + getPageSize(); + return result; + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetPageable.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetPageable.java new file mode 100644 index 0000000000..faf6774dbc --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/KeysetPageable.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.repository; + +import com.blazebit.persistence.KeysetPage; +import org.springframework.data.domain.Pageable; + +/** + * Like {@link Pageable} but contains keyset information. + * + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public interface KeysetPageable extends Pageable { + + /** + * Returns the keyset page information. + * + * @return The keyset page + */ + public KeysetPage getKeysetPage(); + + /** + * Returns whether count query execution is enabled or not. + * + * @return true when enabled, false otherwise + * @since 1.4.0 + */ + public boolean isWithCountQuery(); + + /** + * Returns whether extraction for all keysets is enabled or not. + * + * @return true when enabled, false otherwise + * @since 1.4.0 + */ + public boolean isWithExtractAllKeysets(); + + /** + * Returns the offset as int. + * + * @return The offset as int + * @since 1.3.0 + */ + public int getIntOffset(); +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/config/BlazeRepositoriesRegistrar.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/config/BlazeRepositoriesRegistrar.java new file mode 100644 index 0000000000..9f683edb90 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/config/BlazeRepositoriesRegistrar.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.repository.config; + +import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport; +import org.springframework.data.repository.config.RepositoryConfigurationExtension; + +import java.lang.annotation.Annotation; + +/** + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public class BlazeRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport { + + @Override + protected Class getAnnotation() { + return EnableBlazeRepositories.class; + } + + @Override + protected RepositoryConfigurationExtension getExtension() { + return new BlazeRepositoryConfigExtension(); + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/config/BlazeRepositoryConfigExtension.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/config/BlazeRepositoryConfigExtension.java new file mode 100644 index 0000000000..6e13296680 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/config/BlazeRepositoryConfigExtension.java @@ -0,0 +1,77 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.repository.config; + +import com.blazebit.persistence.spring.data.repository.EntityViewRepository; +import com.blazebit.persistence.spring.data.repository.EntityViewSpecificationExecutor; +import com.blazebit.persistence.view.EntityView; + +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension; +import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; +import org.springframework.data.repository.config.RepositoryConfigurationSource; + +import jakarta.persistence.Entity; +import jakarta.persistence.MappedSuperclass; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; + +/** + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + */ +public class BlazeRepositoryConfigExtension extends JpaRepositoryConfigExtension { + @Override + public String getModuleName() { + return "Blaze-Persistence"; + } + + public String getRepositoryFactoryClassName() { + return getRepositoryFactoryBeanClassName(); + } + + public String getRepositoryFactoryBeanClassName() { + return "com.blazebit.persistence.spring.data.impl.repository.BlazePersistenceRepositoryFactoryBean"; + } + + @Override + protected Collection> getIdentifyingAnnotations() { + return Arrays.asList(Entity.class, MappedSuperclass.class, EntityView.class); + } + + @Override + protected Collection> getIdentifyingTypes() { + return Arrays.asList(JpaRepository.class, EntityViewRepository.class, EntityViewSpecificationExecutor.class); + } + + @Override + public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource config) { + AnnotationAttributes attributes = ((AnnotationRepositoryConfigurationSource) config).getAttributes(); + if (attributes.get("repositoryFactoryBeanClass") == void.class) { + try { + attributes.put("repositoryFactoryBeanClass", Class.forName(getRepositoryFactoryBeanClassName())); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + super.registerBeansForRoot(registry, config); + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/config/EnableBlazeRepositories.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/config/EnableBlazeRepositories.java new file mode 100644 index 0000000000..3ffdec5f1b --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/config/EnableBlazeRepositories.java @@ -0,0 +1,170 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.repository.config; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.repository.config.BootstrapMode; +import org.springframework.data.repository.config.DefaultRepositoryBaseClass; +import org.springframework.data.repository.query.QueryLookupStrategy; +import org.springframework.transaction.PlatformTransactionManager; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to enable Blaze repositories. Will scan the package of the annotated configuration class for Spring Data + * repositories by default. + * + * Like {@link org.springframework.data.jpa.repository.config.EnableJpaRepositories}, but with a custom registrar. + * + * @author Oliver Gierke + * @author Thomas Darimont + * @author Christian Beikov + * @author Eugen Mayer + * @since 1.6.9 + * @see org.springframework.data.jpa.repository.config.EnableJpaRepositories + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Import(BlazeRepositoriesRegistrar.class) +public @interface EnableBlazeRepositories { + + /** + * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.: + * {@code @EnableBlazeRepositories("org.my.pkg")} instead of {@code @EnableBlazeRepositories(basePackages="org.my.pkg")}. + */ + String[] value() default {}; + + /** + * Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this + * attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. + */ + String[] basePackages() default {}; + + /** + * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The + * package of each class specified will be scanned. Consider creating a special no-op marker class or interface in + * each package that serves no purpose other than being referenced by this attribute. + */ + Class[] basePackageClasses() default {}; + + /** + * Specifies which types are eligible for component scanning. Further narrows the set of candidate components from + * everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters. + */ + ComponentScan.Filter[] includeFilters() default {}; + + /** + * Specifies which types are not eligible for component scanning. + */ + ComponentScan.Filter[] excludeFilters() default {}; + + /** + * Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So + * for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning + * for {@code PersonRepositoryImpl}. + * + */ + String repositoryImplementationPostfix() default "Impl"; + + /** + * Configures the location of where to find the Spring Data named queries properties file. Will default to + * {@code META-INF/jpa-named-queries.properties}. + * + */ + String namedQueriesLocation() default ""; + + /** + * Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to + * {@link QueryLookupStrategy.Key#CREATE_IF_NOT_FOUND}. + * + */ + QueryLookupStrategy.Key queryLookupStrategy() default QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND; + + /** + * Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to + * {@code com.blazebit.persistence.spring.data.impl.repository.BlazePersistenceRepositoryFactoryBean}. + * + */ + Class repositoryFactoryBeanClass() default void.class; + + /** + * Configure the repository base class to be used to create repository proxies for this particular configuration. + * + */ + Class repositoryBaseClass() default DefaultRepositoryBaseClass.class; + + // JPA specific configuration + + /** + * Configures the name of the {@link EntityManagerFactory} bean definition to be used to create repositories + * discovered through this annotation. Defaults to {@code entityManagerFactory}. + * + */ + String entityManagerFactoryRef() default "entityManagerFactory"; + + /** + * Configures the name of the {@link PlatformTransactionManager} bean definition to be used to create repositories + * discovered through this annotation. Defaults to {@code transactionManager}. + * + */ + String transactionManagerRef() default "transactionManager"; + + /** + * Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the + * repositories infrastructure. + */ + boolean considerNestedRepositories() default false; + + /** + * Configures whether to enable default transactions for Spring Data JPA repositories. Defaults to {@literal true}. If + * disabled, repositories must be used behind a facade that's configuring transactions (e.g. using Spring's annotation + * driven transaction facilities) or repository methods have to be used to demarcate transactions. + * + * @return whether to enable default transactions, defaults to {@literal true}. + */ + boolean enableDefaultTransactions() default true; + + /** + * Configures when the repositories are initialized in the bootstrap lifecycle. {@link BootstrapMode#DEFAULT} + * (default) means eager initialization except all repository interfaces annotated with {@link Lazy}, + * {@link BootstrapMode#LAZY} means lazy by default including injection of lazy-initialization proxies into client + * beans so that those can be instantiated but will only trigger the initialization upon first repository usage (i.e a + * method invocation on it). This means repositories can still be uninitialized when the application context has + * completed its bootstrap. {@link BootstrapMode#DEFERRED} is fundamentally the same as {@link BootstrapMode#LAZY}, + * but triggers repository initialization when the application context finishes its bootstrap. + * + */ + BootstrapMode bootstrapMode() default BootstrapMode.DEFAULT; + + /** + * Configures what character is used to escape the wildcards {@literal _} and {@literal %} in derived queries with + * {@literal contains}, {@literal startsWith} or {@literal endsWith} clauses. + * + * @return a single character used for escaping. + */ + char escapeCharacter() default '\\'; +} diff --git a/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/package-info.java b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/package-info.java new file mode 100644 index 0000000000..86df4bdb65 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/com/blazebit/persistence/spring/data/repository/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Blaze-Persistence Spring-Data integration. + * + * @author Moritz Becker + * @author Eugen Mayer + * @since 1.6.9 + */ +package com.blazebit.persistence.spring.data.repository; diff --git a/integration/spring-data/base-3.3/src/main/java/org/springframework/data/jpa/repository/query/FixedJpaCountQueryCreator.java b/integration/spring-data/base-3.3/src/main/java/org/springframework/data/jpa/repository/query/FixedJpaCountQueryCreator.java new file mode 100644 index 0000000000..e1c4bc4b76 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/org/springframework/data/jpa/repository/query/FixedJpaCountQueryCreator.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.jpa.repository.query; + +import com.blazebit.persistence.spring.data.base.query.ParameterMetadataProvider; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.query.parser.PartTree; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; + +/** + * @author Moritz Becker + * @since 1.2.0 + */ +public class FixedJpaCountQueryCreator extends FixedJpaQueryCreator { + + public FixedJpaCountQueryCreator(PartTree tree, Class domainClass, CriteriaBuilder builder, + ParameterMetadataProvider provider) { + super(tree, domainClass, builder, provider); + } + + @Override + protected CriteriaQuery complete(Predicate predicate, Sort sort, CriteriaQuery query, + CriteriaBuilder builder, Root root) { + CriteriaQuery select = query.select(query.isDistinct() ? builder.countDistinct(root) : builder.count(root)); + return predicate == null ? select : select.where(predicate); + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/org/springframework/data/jpa/repository/query/FixedJpaQueryCreator.java b/integration/spring-data/base-3.3/src/main/java/org/springframework/data/jpa/repository/query/FixedJpaQueryCreator.java new file mode 100644 index 0000000000..1d973d6289 --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/org/springframework/data/jpa/repository/query/FixedJpaQueryCreator.java @@ -0,0 +1,300 @@ +/* + * Copyright 2014 - 2024 Blazebit. + * Copyright 2010-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.jpa.repository.query; + +import com.blazebit.persistence.spring.data.base.query.ParameterMetadataProvider; + +import org.springframework.data.domain.Sort; +import org.springframework.data.mapping.PropertyPath; +import org.springframework.data.repository.query.parser.AbstractQueryCreator; +import org.springframework.data.repository.query.parser.Part; +import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.util.Assert; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import static org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively; +import static org.springframework.data.repository.query.parser.Part.Type.NOT_CONTAINING; +import static org.springframework.data.repository.query.parser.Part.Type.NOT_LIKE; + +/** + * Query creator to create a {@link CriteriaQuery} from a {@link PartTree}. + * + * Moritz Becker: Changed inner PredicateBuilder to work around an EclipseLink bug. + * + * @author Oliver Gierke + * @author Moritz Becker + * + * @since 1.2.0 + */ +public class FixedJpaQueryCreator extends AbstractQueryCreator, Predicate> { + private final CriteriaBuilder builder; + private final Root root; + private final CriteriaQuery query; + private final ParameterMetadataProvider provider; + + public FixedJpaQueryCreator(PartTree tree, Class domainClass, CriteriaBuilder builder, + ParameterMetadataProvider provider) { + super(tree); + + this.builder = builder; + this.query = builder.createQuery().distinct(tree.isDistinct()); + this.root = query.from(domainClass); + this.provider = provider; + } + + /** + * Returns all {@link jakarta.persistence.criteria.ParameterExpression} created when creating the query. + * + * @return the parameterExpressions + */ + public List> getParameterExpressions() { + return provider.getExpressions(); + } + + @Override + protected Predicate create(Part part, Iterator iterator) { + return toPredicate(part, root); + } + + @Override + protected Predicate and(Part part, Predicate base, Iterator iterator) { + return builder.and(base, toPredicate(part, root)); + } + + @Override + protected Predicate or(Predicate base, Predicate predicate) { + return builder.or(base, predicate); + } + + /** + * Finalizes the given {@link Predicate} and applies the given sort. Delegates to + * {@link #complete(Predicate, Sort, CriteriaQuery, CriteriaBuilder, Root)} and hands it the current {@link CriteriaQuery} + * and {@link CriteriaBuilder}. + */ + @Override + protected final CriteriaQuery complete(Predicate predicate, Sort sort) { + return complete(predicate, sort, query, builder, root); + } + + /** + * Template method to finalize the given {@link Predicate} using the given {@link CriteriaQuery} and + * {@link CriteriaBuilder}. + * + * @param predicate + * @param sort + * @param query + * @param builder + * @return + */ + protected CriteriaQuery complete(Predicate predicate, Sort sort, CriteriaQuery query, + CriteriaBuilder builder, Root root) { + CriteriaQuery select = this.query.select(root).orderBy(QueryUtils.toOrders(sort, root, builder)); + return predicate == null ? select : select.where(predicate); + } + + /** + * Creates a {@link Predicate} from the given {@link Part}. + * + * @param part + * @param root + * @return + */ + private Predicate toPredicate(Part part, Root root) { + return new FixedJpaQueryCreator.PredicateBuilder(part, root).build(); + } + + /** + * Simple builder to contain logic to create JPA {@link Predicate}s from {@link Part}s. + * + * Moritz Becker: Rewrote NOT and NOT_IN cases to explicitely cast criteria IN argument to Expression<Collection<?>> which is needed + * to work around an EclipseLink bug. + * + * @author Phil Webb + * @author Oliver Gierke + * @author Moritz Becker + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private class PredicateBuilder { + + private final Part part; + private final Root root; + + /** + * Creates a new {@link JpaQueryCreator.PredicateBuilder} for the given {@link Part} and {@link Root}. + * + * @param part must not be {@literal null}. + * @param root must not be {@literal null}. + */ + public PredicateBuilder(Part part, Root root) { + + Assert.notNull(part, "part cannot be null"); + Assert.notNull(root, "root cannot be null"); + this.part = part; + this.root = root; + } + + /** + * Builds a JPA {@link Predicate} from the underlying {@link Part}. + * + * @return + */ + public Predicate build() { + + PropertyPath property = part.getProperty(); + Part.Type type = part.getType(); + + switch (type) { + case BETWEEN: + ParameterMetadataProvider.ParameterMetadata first = provider.next(part); + ParameterMetadataProvider.ParameterMetadata second = provider.next(part); + return builder.between(getComparablePath(root, part), first.getExpression(), second.getExpression()); + case AFTER: + case GREATER_THAN: + return builder.greaterThan(getComparablePath(root, part), + provider.next(part, Comparable.class).getExpression()); + case GREATER_THAN_EQUAL: + return builder.greaterThanOrEqualTo(getComparablePath(root, part), + provider.next(part, Comparable.class).getExpression()); + case BEFORE: + case LESS_THAN: + return builder.lessThan(getComparablePath(root, part), provider.next(part, Comparable.class).getExpression()); + case LESS_THAN_EQUAL: + return builder.lessThanOrEqualTo(getComparablePath(root, part), + provider.next(part, Comparable.class).getExpression()); + case IS_NULL: + return getTypedPath(root, part).isNull(); + case IS_NOT_NULL: + return getTypedPath(root, part).isNotNull(); + /************************************************ + * Moritz Becker: + * Added cast to Expression> to work around an EclipseLink bug. + ************************************************/ + case NOT_IN: + return getTypedPath(root, part).in((Expression>) provider.next(part, Collection.class).getExpression()).not(); + case IN: + return getTypedPath(root, part).in((Expression>) provider.next(part, Collection.class).getExpression()); + /************************************************ + * end of changes + ************************************************/ + //CHECKSTYLE:OFF: checkstyle:FallThrough + case STARTING_WITH: + case ENDING_WITH: + case CONTAINING: + case NOT_CONTAINING: + + if (property.getLeafProperty().isCollection()) { + + Expression> propertyExpression = traversePath(root, property); + Expression parameterExpression = (Expression) provider.next(part).getExpression(); + + // Can't just call .not() in case of negation as EclipseLink chokes on that. + return type.equals(NOT_CONTAINING) ? builder.isNotMember(parameterExpression, propertyExpression) + : builder.isMember(parameterExpression, propertyExpression); + } + //CHECKSTYLE:OFF: checkstyle:FallThrough + case LIKE: + case NOT_LIKE: + Expression stringPath = getTypedPath(root, part); + Expression propertyExpression = upperIfIgnoreCase(stringPath); + Expression parameterExpression = upperIfIgnoreCase(provider.next(part, String.class).getExpression()); + Predicate like = builder.like(propertyExpression, parameterExpression); + return type.equals(NOT_LIKE) || type.equals(NOT_CONTAINING) ? like.not() : like; + case TRUE: + Expression truePath = getTypedPath(root, part); + return builder.isTrue(truePath); + case FALSE: + Expression falsePath = getTypedPath(root, part); + return builder.isFalse(falsePath); + case SIMPLE_PROPERTY: + ParameterMetadataProvider.ParameterMetadata expression = provider.next(part); + Expression path = getTypedPath(root, part); + return expression.isIsNullParameter() ? path.isNull() + : builder.equal(upperIfIgnoreCase(path), upperIfIgnoreCase(expression.getExpression())); + case NEGATING_SIMPLE_PROPERTY: + return builder.notEqual(upperIfIgnoreCase(getTypedPath(root, part)), + upperIfIgnoreCase(provider.next(part).getExpression())); + default: + throw new IllegalArgumentException("Unsupported keyword " + type); + } + } + + /** + * Applies an {@code UPPERCASE} conversion to the given {@link Expression} in case the underlying {@link Part} + * requires ignoring case. + * + * @param expression must not be {@literal null}. + * @return + */ + private Expression upperIfIgnoreCase(Expression expression) { + + switch (part.shouldIgnoreCase()) { + + case ALWAYS: + + Assert.state(canUpperCase(expression), "Unable to ignore case of " + expression.getJavaType().getName() + + " types, the property '" + part.getProperty().getSegment() + "' must reference a String"); + return (Expression) builder.upper((Expression) expression); + + case WHEN_POSSIBLE: + + if (canUpperCase(expression)) { + return (Expression) builder.upper((Expression) expression); + } + + case NEVER: + default: + + return (Expression) expression; + } + } + + private boolean canUpperCase(Expression expression) { + return String.class.equals(expression.getJavaType()); + } + + /** + * Returns a path to a {@link Comparable}. + * + * @param root + * @param part + * @return + */ + private Expression getComparablePath(Root root, Part part) { + return getTypedPath(root, part); + } + + private Expression getTypedPath(Root root, Part part) { + return toExpressionRecursively(root, part.getProperty()); + } + + private Expression traversePath(Path root, PropertyPath path) { + + Path result = root.get(path.getSegment()); + return (Expression) (path.hasNext() ? traversePath(result, path.next()) : result); + } + } +} diff --git a/integration/spring-data/base-3.3/src/main/java/org/springframework/data/repository/config/BootstrapMode.java b/integration/spring-data/base-3.3/src/main/java/org/springframework/data/repository/config/BootstrapMode.java new file mode 100644 index 0000000000..1e43e9e27b --- /dev/null +++ b/integration/spring-data/base-3.3/src/main/java/org/springframework/data/repository/config/BootstrapMode.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.repository.config; + +/** + * Enumeration to define in which way repositories are bootstrapped. + * + * @author Oliver Gierke + * @see RepositoryConfigurationSource#getBootstrapMode() + * @since 2.1 + * @soundtrack Dave Matthews Band - She (Come Tomorrow) + */ +public enum BootstrapMode { + + /** + * Repository proxies are instantiated eagerly, just like any other Spring bean, except explicitly marked as lazy. + * Thus, injection into repository clients will trigger initialization. + */ + DEFAULT, + + /** + * Repository bean definitions are considered lazy and clients will get repository proxies injected that will + * initialize on first access. Repository initialization is triggered on application context bootstrap completion. + */ + DEFERRED, + + /** + * Repository bean definitions are considered lazy, lazily inject and only initialized on first use, i.e. the + * application might have fully started without the repositories initialized. + */ + LAZY; +} diff --git a/integration/spring-data/pom.xml b/integration/spring-data/pom.xml index b927d7c126..1876136dde 100644 --- a/integration/spring-data/pom.xml +++ b/integration/spring-data/pom.xml @@ -60,6 +60,7 @@ base base-3.1 + base-3.3 1.x 2.0 2.1 @@ -70,6 +71,7 @@ 2.6 2.7 3.1 + 3.3 webmvc webmvc-jakarta webflux diff --git a/integration/spring-data/testsuite/webflux-jakarta-runner/pom.xml b/integration/spring-data/testsuite/webflux-jakarta-runner/pom.xml index 5840774bfd..b6a86e5d80 100644 --- a/integration/spring-data/testsuite/webflux-jakarta-runner/pom.xml +++ b/integration/spring-data/testsuite/webflux-jakarta-runner/pom.xml @@ -32,7 +32,10 @@ com.blazebit.persistence.integration.spring.data.testsuite.webflux.runner 17 ${test.java.version} - ${version.spring-data-3.1-spring} + ${version.spring-data-3.3-spring} + ${version.spring-data-3.3-spring-boot} + ${version.spring-data-3.3} + ${version.hibernate-6.2} ${h2-2.version} com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 @@ -75,7 +78,7 @@ org.springframework.data spring-data-jpa - ${version.spring-data-3.1} + ${version.spring-data} test @@ -114,24 +117,24 @@ com.blazebit - blaze-persistence-integration-spring-data-3.1 + blaze-persistence-integration-spring-data-3.3 test org.hibernate.orm hibernate-core - ${version.hibernate-6.2} + ${version.hibernate} org.hibernate.orm hibernate-envers - ${version.hibernate-6.2} + ${version.hibernate} org.hibernate.orm hibernate-testing - ${version.hibernate-6.2} + ${version.hibernate} jakarta.xml.bind @@ -156,7 +159,7 @@ org.springframework.boot spring-boot-starter-websocket - ${version.spring-data-3.1-spring-boot} + ${version.spring-boot} ${project.groupId} @@ -191,13 +194,13 @@ org.springframework.data spring-data-commons - ${version.spring-data-3.1} + ${version.spring-data} true com.fasterxml.jackson.core jackson-databind - 2.13.4.1 + 2.17.1 ${project.groupId} @@ -214,7 +217,7 @@ org.springframework.boot spring-boot-starter-test - ${version.spring-data-3.1-spring-boot} + ${version.spring-boot} test @@ -252,5 +255,111 @@ + + + hibernate-5.6 + + + ${version.hibernate-6.2} + + + + hibernate-6.2 + + ${version.hibernate-6.2} + + + + hibernate-6.4 + + ${version.hibernate-6.4} + + + + hibernate-6.5 + + true + + + ${version.hibernate-6.5} + + + + hibernate-6.6 + + ${version.hibernate-6.6} + + + + spring-data-2.7.x + + + ${version.spring-data-3.1-spring} + ${version.spring-data-3.1-spring-boot} + ${version.spring-data-3.1} + + + + com.blazebit + blaze-persistence-integration-spring-data-3.1 + test + + + + + spring-data-3.1.x + + ${version.spring-data-3.1-spring} + ${version.spring-data-3.1-spring-boot} + ${version.spring-data-3.1} + + + + com.blazebit + blaze-persistence-integration-spring-data-3.1 + test + + + + + spring-data-3.2.x + + ${version.spring-data-3.2-spring} + ${version.spring-data-3.2-spring-boot} + ${version.spring-data-3.2} + + + + com.blazebit + blaze-persistence-integration-spring-data-3.1 + test + + + + + spring-data-3.3.x + + true + + + ${version.spring-data-3.3-spring} + ${version.spring-data-3.3-spring-boot} + ${version.spring-data-3.3} + + + + com.blazebit + blaze-persistence-integration-spring-data-3.3 + test + + + + diff --git a/integration/spring-data/testsuite/webflux/pom.xml b/integration/spring-data/testsuite/webflux/pom.xml index 45f6ae8be4..19e65bfd15 100644 --- a/integration/spring-data/testsuite/webflux/pom.xml +++ b/integration/spring-data/testsuite/webflux/pom.xml @@ -1790,6 +1790,242 @@ + + hibernate-6.5 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + hibernate + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + compile + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.bsc.maven + maven-processor-plugin + + + process + + process + + generate-sources + + + ${project.build.directory}/generated-sources/metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jta} + + + jakarta.activation + jakarta.activation-api + ${version.activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/metamodel + + + + + + + + + + hibernate-6.6 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + hibernate + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + compile + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.bsc.maven + maven-processor-plugin + + + process + + process + + generate-sources + + + ${project.build.directory}/generated-sources/metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jta} + + + jakarta.activation + jakarta.activation-api + ${version.activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/metamodel + + + + + + + + eclipselink @@ -2830,5 +3066,104 @@ + + spring-data-3.1.x + + + ${version.spring-data-2.7-spring} + ${version.spring-data-2.7-spring-boot} + + + + org.springframework.data + spring-data-jpa + ${version.spring-data-2.7} + test + + + com.blazebit + blaze-persistence-integration-spring-data-2.7 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + spring-data-3.2.x + + + ${version.spring-data-2.7-spring} + ${version.spring-data-2.7-spring-boot} + + + + org.springframework.data + spring-data-jpa + ${version.spring-data-2.7} + test + + + com.blazebit + blaze-persistence-integration-spring-data-2.7 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + spring-data-3.3.x + + + ${version.spring-data-2.7-spring} + ${version.spring-data-2.7-spring-boot} + + + + org.springframework.data + spring-data-jpa + ${version.spring-data-2.7} + test + + + com.blazebit + blaze-persistence-integration-spring-data-2.7 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + \ No newline at end of file diff --git a/integration/spring-data/testsuite/webmvc-jakarta-runner/pom.xml b/integration/spring-data/testsuite/webmvc-jakarta-runner/pom.xml index a477499bd5..3786277f3a 100644 --- a/integration/spring-data/testsuite/webmvc-jakarta-runner/pom.xml +++ b/integration/spring-data/testsuite/webmvc-jakarta-runner/pom.xml @@ -32,7 +32,10 @@ com.blazebit.persistence.integration.spring.data.testsuite.webmvc.runner 17 ${test.java.version} - ${version.spring-data-3.1-spring} + ${version.spring-data-3.3-spring} + ${version.spring-data-3.3-spring-boot} + ${version.spring-data-3.3} + ${version.hibernate-6.2} ${h2-2.version} com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 @@ -75,7 +78,7 @@ org.springframework.data spring-data-jpa - ${version.spring-data-3.1} + ${version.spring-data} test @@ -112,26 +115,21 @@ blaze-persistence-entity-view-impl-jakarta test - - com.blazebit - blaze-persistence-integration-spring-data-3.1 - test - org.hibernate.orm hibernate-core - ${version.hibernate-6.2} + ${version.hibernate} org.hibernate.orm hibernate-envers - ${version.hibernate-6.2} + ${version.hibernate} org.hibernate.orm hibernate-testing - ${version.hibernate-6.2} + ${version.hibernate} jakarta.xml.bind @@ -194,7 +192,13 @@ com.fasterxml.jackson.core jackson-databind - 2.9.8 + 2.17.1 + test + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.17.1 test @@ -238,5 +242,111 @@ + + + hibernate-5.6 + + + ${version.hibernate-6.2} + + + + hibernate-6.2 + + ${version.hibernate-6.2} + + + + hibernate-6.4 + + ${version.hibernate-6.4} + + + + hibernate-6.5 + + true + + + ${version.hibernate-6.5} + + + + hibernate-6.6 + + ${version.hibernate-6.6} + + + + spring-data-2.7.x + + + ${version.spring-data-3.1-spring} + ${version.spring-data-3.1-spring-boot} + ${version.spring-data-3.1} + + + + com.blazebit + blaze-persistence-integration-spring-data-3.1 + test + + + + + spring-data-3.1.x + + ${version.spring-data-3.1-spring} + ${version.spring-data-3.1-spring-boot} + ${version.spring-data-3.1} + + + + com.blazebit + blaze-persistence-integration-spring-data-3.1 + test + + + + + spring-data-3.2.x + + ${version.spring-data-3.2-spring} + ${version.spring-data-3.2-spring-boot} + ${version.spring-data-3.2} + + + + com.blazebit + blaze-persistence-integration-spring-data-3.1 + test + + + + + spring-data-3.3.x + + true + + + ${version.spring-data-3.3-spring} + ${version.spring-data-3.3-spring-boot} + ${version.spring-data-3.3} + + + + com.blazebit + blaze-persistence-integration-spring-data-3.3 + test + + + + diff --git a/integration/spring-data/testsuite/webmvc/pom.xml b/integration/spring-data/testsuite/webmvc/pom.xml index 65da73fef8..f4b82f044a 100644 --- a/integration/spring-data/testsuite/webmvc/pom.xml +++ b/integration/spring-data/testsuite/webmvc/pom.xml @@ -1840,6 +1840,242 @@ + + hibernate-6.5 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + hibernate + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + compile + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.bsc.maven + maven-processor-plugin + + + process + + process + + generate-sources + + + ${project.build.directory}/generated-sources/metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jta} + + + jakarta.activation + jakarta.activation-api + ${version.activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/metamodel + + + + + + + + + + hibernate-6.6 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + hibernate + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + compile + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.bsc.maven + maven-processor-plugin + + + process + + process + + generate-sources + + + ${project.build.directory}/generated-sources/metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jta} + + + jakarta.activation + jakarta.activation-api + ${version.activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/metamodel + + + + + + + + eclipselink @@ -2805,5 +3041,101 @@ + + spring-data-3.1.x + + + ${version.spring-data-2.7-spring} + + + + org.springframework.data + spring-data-jpa + ${version.spring-data-2.7} + test + + + com.blazebit + blaze-persistence-integration-spring-data-2.7 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + spring-data-3.2.x + + + ${version.spring-data-2.7-spring} + + + + org.springframework.data + spring-data-jpa + ${version.spring-data-2.7} + test + + + com.blazebit + blaze-persistence-integration-spring-data-2.7 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + spring-data-3.3.x + + + ${version.spring-data-2.7-spring} + + + + org.springframework.data + spring-data-jpa + ${version.spring-data-2.7} + test + + + com.blazebit + blaze-persistence-integration-spring-data-2.7 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + diff --git a/integration/spring-data/webflux/pom.xml b/integration/spring-data/webflux/pom.xml index cc118e2589..595be7476c 100644 --- a/integration/spring-data/webflux/pom.xml +++ b/integration/spring-data/webflux/pom.xml @@ -237,6 +237,87 @@ + + spring-data-3.1.x + + + ${version.spring-data-2.7-spring} + ${version.spring-data-2.7} + + + + com.blazebit + blaze-persistence-integration-spring-data-2.7 + provided + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + spring-data-3.2.x + + + ${version.spring-data-2.7-spring} + ${version.spring-data-2.7} + + + + com.blazebit + blaze-persistence-integration-spring-data-2.7 + provided + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + spring-data-3.3.x + + + ${version.spring-data-2.7-spring} + ${version.spring-data-2.7} + + + + com.blazebit + blaze-persistence-integration-spring-data-2.7 + provided + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + \ No newline at end of file diff --git a/integration/spring-data/webmvc/pom.xml b/integration/spring-data/webmvc/pom.xml index 01f845f7d7..43818c8f99 100644 --- a/integration/spring-data/webmvc/pom.xml +++ b/integration/spring-data/webmvc/pom.xml @@ -300,6 +300,102 @@ + + spring-data-3.1.x + + + ${version.spring-data-2.7-spring} + + + + org.springframework.data + spring-data-commons + ${version.spring-data-2.7} + true + + + com.blazebit + blaze-persistence-integration-spring-data-2.7 + provided + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + spring-data-3.2.x + + + ${version.spring-data-2.7-spring} + + + + org.springframework.data + spring-data-commons + ${version.spring-data-2.7} + true + + + com.blazebit + blaze-persistence-integration-spring-data-2.7 + provided + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + spring-data-3.3.x + + + ${version.spring-data-2.7-spring} + + + + org.springframework.data + spring-data-commons + ${version.spring-data-2.7} + true + + + com.blazebit + blaze-persistence-integration-spring-data-2.7 + provided + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + \ No newline at end of file diff --git a/integration/spring-hateoas/webmvc-jakarta/pom.xml b/integration/spring-hateoas/webmvc-jakarta/pom.xml index 6fcb6dbee3..5482b9c6ce 100644 --- a/integration/spring-hateoas/webmvc-jakarta/pom.xml +++ b/integration/spring-hateoas/webmvc-jakarta/pom.xml @@ -14,10 +14,10 @@ com.blazebit.persistence.integration.spring.hateoas.webmvc - ${version.spring-data-3.1-spring} - ${version.spring-data-3.1-spring-boot} - ${version.spring-data-3.1} - 2.0.2 + ${version.spring-data-3.3-spring} + ${version.spring-data-3.3-spring-boot} + ${version.spring-data-3.3} + 2.2.2 17 @@ -43,13 +43,13 @@ org.springframework.data spring-data-jpa - ${version.spring.data} + ${version.spring-data} provided org.springframework.hateoas spring-hateoas - ${version.spring.hateoas} + ${version.spring-hateoas} compile @@ -65,13 +65,13 @@ org.springframework.boot spring-boot-autoconfigure - ${version.spring.boot} + ${version.spring-boot} provided com.fasterxml.jackson.core jackson-databind - 2.13.4.1 + 2.17.1 provided @@ -84,7 +84,7 @@ ${project.groupId} - blaze-persistence-integration-spring-data-base-3.1 + blaze-persistence-integration-spring-data-base-3.3 compile @@ -174,11 +174,6 @@ ${version.hibernate-6.2} provided - - ${project.groupId} - blaze-persistence-integration-spring-data-3.1 - test - @@ -299,4 +294,126 @@ + + + hibernate-5.6 + + + ${version.hibernate-6.2} + + + + hibernate-6.2 + + ${version.hibernate-6.2} + + + + hibernate-6.4 + + ${version.hibernate-6.4} + + + + hibernate-6.5 + + true + + + ${version.hibernate-6.5} + + + + hibernate-6.6 + + ${version.hibernate-6.6} + + + + spring-data-2.7.x + + + ${version.spring-data-3.1-spring} + ${version.spring-data-3.1-spring-boot} + ${version.spring-data-3.1} + 2.1.5 + + + + com.blazebit + blaze-persistence-integration-spring-data-3.1 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + spring-data-3.1.x + + ${version.spring-data-3.1-spring} + ${version.spring-data-3.1-spring-boot} + ${version.spring-data-3.1} + 2.1.5 + + + + com.blazebit + blaze-persistence-integration-spring-data-3.1 + test + + + + + spring-data-3.2.x + + ${version.spring-data-3.2-spring} + ${version.spring-data-3.2-spring-boot} + ${version.spring-data-3.2} + 2.2.2 + + + + com.blazebit + blaze-persistence-integration-spring-data-3.1 + test + + + + + spring-data-3.3.x + + true + + + ${version.spring-data-3.3-spring} + ${version.spring-data-3.3-spring-boot} + ${version.spring-data-3.3} + 2.3.0 + + + + com.blazebit + blaze-persistence-integration-spring-data-3.3 + test + + + + diff --git a/integration/spring-hateoas/webmvc/pom.xml b/integration/spring-hateoas/webmvc/pom.xml index 192bbef414..774578877a 100644 --- a/integration/spring-hateoas/webmvc/pom.xml +++ b/integration/spring-hateoas/webmvc/pom.xml @@ -421,6 +421,93 @@ + + spring-data-3.1.x + + + ${version.spring-data-2.7-spring} + ${version.spring-data-2.7-spring-boot} + ${version.spring-data-2.7} + 1.5.1 + + + + ${project.groupId} + blaze-persistence-integration-spring-data-2.7 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + spring-data-3.2.x + + + ${version.spring-data-2.7-spring} + ${version.spring-data-2.7-spring-boot} + ${version.spring-data-2.7} + 1.5.1 + + + + ${project.groupId} + blaze-persistence-integration-spring-data-2.7 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + spring-data-3.3.x + + + ${version.spring-data-2.7-spring} + ${version.spring-data-2.7-spring-boot} + ${version.spring-data-2.7} + 1.5.1 + + + + ${project.groupId} + blaze-persistence-integration-spring-data-2.7 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + - \ No newline at end of file + diff --git a/jpa-criteria/testsuite-jakarta-runner/pom.xml b/jpa-criteria/testsuite-jakarta-runner/pom.xml index 373fd6ed02..2297e66f95 100644 --- a/jpa-criteria/testsuite-jakarta-runner/pom.xml +++ b/jpa-criteria/testsuite-jakarta-runner/pom.xml @@ -304,6 +304,130 @@ + + hibernate-6.5 + + ${version.jakarta-jpa-3.1-api} + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate.orm + hibernate-core + ${version.hibernate-6.5} + + + org.hibernate.orm + hibernate-envers + ${version.hibernate-6.5} + + + org.hibernate.orm + hibernate-testing + ${version.hibernate-6.5} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate6 + ${project.version} + compile + + + + org.hibernate.orm + hibernate-jpamodelgen + ${version.hibernate-6.5} + provided + + + + + hibernate-6.6 + + ${version.jakarta-jpa-3.1-api} + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate.orm + hibernate-core + ${version.hibernate-6.6} + + + org.hibernate.orm + hibernate-envers + ${version.hibernate-6.6} + + + org.hibernate.orm + hibernate-testing + ${version.hibernate-6.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jakarta-jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jakarta-jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jakarta-jta} + + + jakarta.activation + jakarta.activation-api + ${version.jakarta-activation} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate6 + ${project.version} + compile + + + + org.hibernate.orm + hibernate-jpamodelgen + ${version.hibernate-6.6} + provided + + + diff --git a/jpa-criteria/testsuite/pom.xml b/jpa-criteria/testsuite/pom.xml index 4a26fbfa51..00f89f5607 100644 --- a/jpa-criteria/testsuite/pom.xml +++ b/jpa-criteria/testsuite/pom.xml @@ -90,6 +90,11 @@ org.mockito mockito-core + + ${project.groupId} + blaze-persistence-testsuite-base-assertion + ${project.version} + @@ -1413,6 +1418,240 @@ + + hibernate-6.5 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + test + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.bsc.maven + maven-processor-plugin + + + process-test + + process-test + + generate-test-sources + + + ${project.build.directory}/test-metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jta} + + + jakarta.activation + jakarta.activation-api + ${version.activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source-metamodel + generate-test-sources + + add-test-source + + + + ${project.build.directory}/test-metamodel + + + + + + + + + + hibernate-6.6 + + + com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate,com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate60 + + + + org.hibernate + hibernate-core + ${version.hibernate-5.6} + + + ${project.groupId} + blaze-persistence-integration-hibernate-6.2 + test + + + ${project.groupId} + blaze-persistence-testsuite-base-hibernate + test + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + provided + + + + + + maven-surefire-plugin + + true + + + + maven-jar-plugin + + + + test-jar + + + + + + org.bsc.maven + maven-processor-plugin + + + process-test + + process-test + + generate-test-sources + + + ${project.build.directory}/test-metamodel + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${version.hibernate-5.6} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${version.jaxb-api} + + + com.sun.xml.bind + jaxb-impl + ${version.jaxb} + + + jakarta.transaction + jakarta.transaction-api + ${version.jta} + + + jakarta.activation + jakarta.activation-api + ${version.activation} + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source-metamodel + generate-test-sources + + add-test-source + + + + ${project.build.directory}/test-metamodel + + + + + + + + eclipselink diff --git a/parent/pom.xml b/parent/pom.xml index dc9c27c0a1..2220038bd5 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -77,6 +77,8 @@ 6.2.13.Final 6.3.1.Final 6.4.2.Final + 6.5.2.Final + 6.6.0.Alpha1 4.1.17 4.1.19 @@ -127,10 +129,15 @@ 2.7.12 5.3.27 2.7.12 - 3.1.0 - 6.0.5 - 3.1.0 - + 3.1.12 + 6.0.20 + 3.1.12 + 3.2.6 + 6.1.8 + 3.2.6 + 3.3.2 + 6.1.8 + 3.3.2 4.7.5 6.0-6 @@ -496,6 +503,11 @@ blaze-persistence-integration-spring-data-base-3.1 ${project.version} + + com.blazebit + blaze-persistence-integration-spring-data-base-3.3 + ${project.version} + com.blazebit blaze-persistence-integration-spring-data-1.x @@ -546,6 +558,11 @@ blaze-persistence-integration-spring-data-3.1 ${project.version} + + ${project.groupId} + blaze-persistence-integration-spring-data-3.3 + ${project.version} + com.blazebit blaze-persistence-integration-spring-data-webmvc diff --git a/testsuite-base/assertion/pom.xml b/testsuite-base/assertion/pom.xml new file mode 100644 index 0000000000..41dcba36bd --- /dev/null +++ b/testsuite-base/assertion/pom.xml @@ -0,0 +1,54 @@ + + + + + 4.0.0 + + + com.blazebit + blaze-persistence-testsuite-base + 1.6.12-SNAPSHOT + ../pom.xml + + + blaze-persistence-testsuite-base-assertion + jar + + Blazebit Persistence Testsuite Base Assertion + + + com.blazebit.persistence.testsuite.base.assertion + + + + + ${project.groupId} + blaze-persistence-testsuite-base-jpa + provided + + + com.github.jsqlparser + jsqlparser + 4.9 + + + junit + junit + compile + + + \ No newline at end of file diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AbstractAssertStatement.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AbstractAssertStatement.java similarity index 87% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AbstractAssertStatement.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AbstractAssertStatement.java index 3115319e14..6472a70579 100644 --- a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AbstractAssertStatement.java +++ b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AbstractAssertStatement.java @@ -28,11 +28,11 @@ import net.sf.jsqlparser.statement.select.FromItemVisitor; import net.sf.jsqlparser.statement.select.FromItemVisitorAdapter; import net.sf.jsqlparser.statement.select.Join; +import net.sf.jsqlparser.statement.select.ParenthesedFromItem; +import net.sf.jsqlparser.statement.select.ParenthesedSelect; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; -import net.sf.jsqlparser.statement.select.SelectExpressionItem; import net.sf.jsqlparser.statement.select.SelectItem; -import net.sf.jsqlparser.statement.select.SubJoin; import net.sf.jsqlparser.statement.update.Update; import org.junit.Assert; @@ -134,20 +134,18 @@ protected List getFetchedFromElements(String query) { if (select.getSelectBody() instanceof PlainSelect) { PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); for (SelectItem item : plainSelect.getSelectItems()) { - if (item instanceof SelectExpressionItem) { - ((SelectExpressionItem) item).getExpression().accept(new ExpressionVisitorAdapter() { - @Override - public void visit(Column column) { - Table t = column.getTable(); - Table realTable; - if ((realTable = findTable(tables, t.getName())) == null) { - throw new IllegalStateException("Table '" + t + "' not found in determined tables: " + tables); - } - - fetchedTables.add(realTable); + item.getExpression().accept(new ExpressionVisitorAdapter() { + @Override + public void visit(Column column) { + Table t = column.getTable(); + Table realTable; + if ((realTable = findTable(tables, t.getName())) == null) { + throw new IllegalStateException("Table '" + t + "' not found in determined tables: " + tables); } - }); - } + + fetchedTables.add(realTable); + } + }); } } } @@ -180,9 +178,14 @@ public void visit(Table table) { } @Override - public void visit(SubJoin subjoin) { - subjoin.getLeft().accept(this); - for (Join join : subjoin.getJoinList()) { + public void visit(ParenthesedSelect selectBody) { + tables.addAll( getTables( selectBody.getSelect() ) ); + } + + @Override + public void visit(ParenthesedFromItem fromItem) { + fromItem.getFromItem().accept( this ); + for (Join join : fromItem.getJoins()) { join.getRightItem().accept(this); } } diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AbstractAssertStatementBuilder.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AbstractAssertStatementBuilder.java similarity index 100% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AbstractAssertStatementBuilder.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AbstractAssertStatementBuilder.java diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertDeleteStatement.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertDeleteStatement.java similarity index 100% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertDeleteStatement.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertDeleteStatement.java diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertDeleteStatementBuilder.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertDeleteStatementBuilder.java similarity index 100% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertDeleteStatementBuilder.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertDeleteStatementBuilder.java diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatement.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatement.java similarity index 100% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatement.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatement.java diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatementBuilder.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatementBuilder.java similarity index 100% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatementBuilder.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatementBuilder.java diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertMultiStatement.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertMultiStatement.java similarity index 100% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertMultiStatement.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertMultiStatement.java diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertMultiStatementBuilder.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertMultiStatementBuilder.java similarity index 100% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertMultiStatementBuilder.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertMultiStatementBuilder.java diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertSelectStatement.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertSelectStatement.java similarity index 100% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertSelectStatement.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertSelectStatement.java diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertSelectStatementBuilder.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertSelectStatementBuilder.java similarity index 100% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertSelectStatementBuilder.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertSelectStatementBuilder.java diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatement.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatement.java similarity index 100% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatement.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatement.java diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatementBuilder.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatementBuilder.java similarity index 100% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatementBuilder.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatementBuilder.java diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatement.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatement.java similarity index 100% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatement.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatement.java diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatementBuilder.java b/testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatementBuilder.java similarity index 100% rename from testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatementBuilder.java rename to testsuite-base/assertion/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatementBuilder.java diff --git a/testsuite-base/jpa-jakarta/pom.xml b/testsuite-base/jpa-jakarta/pom.xml index 719f621ccc..e487fc024f 100644 --- a/testsuite-base/jpa-jakarta/pom.xml +++ b/testsuite-base/jpa-jakarta/pom.xml @@ -66,11 +66,6 @@ HikariCP-java7 2.4.12 - - com.github.jsqlparser - jsqlparser - 3.2 - org.opentest4j opentest4j diff --git a/testsuite-base/jpa/pom.xml b/testsuite-base/jpa/pom.xml index d909f92b5c..fe7e0222ab 100644 --- a/testsuite-base/jpa/pom.xml +++ b/testsuite-base/jpa/pom.xml @@ -57,11 +57,6 @@ HikariCP-java7 2.4.12 - - com.github.jsqlparser - jsqlparser - 4.5 - org.opentest4j opentest4j diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/AbstractJpaPersistenceTest.java b/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/AbstractJpaPersistenceTest.java index 3213cace88..71b5433f68 100644 --- a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/AbstractJpaPersistenceTest.java +++ b/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/AbstractJpaPersistenceTest.java @@ -24,7 +24,6 @@ import com.blazebit.persistence.spi.ExtendedQuerySupport; import com.blazebit.persistence.spi.JoinTable; import com.blazebit.persistence.spi.JpaProvider; -import com.blazebit.persistence.testsuite.base.jpa.assertion.AssertStatementBuilder; import com.blazebit.persistence.testsuite.base.jpa.cleaner.DB2DatabaseCleaner; import com.blazebit.persistence.testsuite.base.jpa.cleaner.DatabaseCleaner; import com.blazebit.persistence.testsuite.base.jpa.cleaner.H2DatabaseCleaner; @@ -749,10 +748,10 @@ private DataSource proxyDataSource(DataSource dataSource) { .build(); } - private static final class QueryInspectorListener implements QueryExecutionListener { + protected static final class QueryInspectorListener implements QueryExecutionListener { public static final QueryInspectorListener INSTANCE = new QueryInspectorListener(); - private static final List EXECUTED_QUERIES = new ArrayList<>(); + public static final List EXECUTED_QUERIES = new ArrayList<>(); private static boolean enabled = false; private static boolean collectSequences = false; @@ -809,14 +808,6 @@ public static void assertQueryCount(int count) { } } - public AssertStatementBuilder assertOrderedQuerySequence() { - return new AssertStatementBuilder(getRelationalModelAccessor(), QueryInspectorListener.EXECUTED_QUERIES); - } - - public AssertStatementBuilder assertUnorderedQuerySequence() { - return assertOrderedQuerySequence().unordered(); - } - protected static E verifyException(T object, Consumer action) { return (E) verifyException(object, Throwable.class, action); } diff --git a/testsuite-base/pom.xml b/testsuite-base/pom.xml index bd5db7a081..3f960ee784 100644 --- a/testsuite-base/pom.xml +++ b/testsuite-base/pom.xml @@ -30,6 +30,8 @@ Blazebit Persistence Testsuite Base + assertion + jpa hibernate datanucleus