18 changed files with 1026 additions and 59 deletions
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
/* |
||||
* Copyright 2025 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.mongodb.repository.support; |
||||
|
||||
import org.springframework.data.mongodb.core.MongoOperations; |
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation; |
||||
import org.springframework.data.repository.core.RepositoryMetadata; |
||||
import org.springframework.data.repository.core.support.RepositoryComposition; |
||||
import org.springframework.data.repository.core.support.RepositoryFragmentsContributor; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* MongoDB-specific {@link RepositoryFragmentsContributor} contributing fragments based on the repository. |
||||
* <p> |
||||
* Implementations must define a no-args constructor. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 5.0 |
||||
* @see QuerydslMongoPredicateExecutor |
||||
*/ |
||||
public interface MongoRepositoryFragmentsContributor extends RepositoryFragmentsContributor { |
||||
|
||||
MongoRepositoryFragmentsContributor DEFAULT = QuerydslContributor.INSTANCE; |
||||
|
||||
/** |
||||
* Returns a composed {@code MongoRepositoryFragmentsContributor} that first applies this contributor to its inputs, |
||||
* and then applies the {@code after} contributor concatenating effectively both results. If evaluation of either |
||||
* contributors throws an exception, it is relayed to the caller of the composed contributor. |
||||
* |
||||
* @param after the contributor to apply after this contributor is applied. |
||||
* @return a composed contributor that first applies this contributor and then applies the {@code after} contributor. |
||||
*/ |
||||
default MongoRepositoryFragmentsContributor andThen(MongoRepositoryFragmentsContributor after) { |
||||
|
||||
Assert.notNull(after, "MongoRepositoryFragmentsContributor must not be null"); |
||||
|
||||
return new MongoRepositoryFragmentsContributor() { |
||||
|
||||
@Override |
||||
public RepositoryComposition.RepositoryFragments contribute(RepositoryMetadata metadata, |
||||
MongoEntityInformation<?, ?> entityInformation, MongoOperations operations) { |
||||
return MongoRepositoryFragmentsContributor.this.contribute(metadata, entityInformation, operations) |
||||
.append(after.contribute(metadata, entityInformation, operations)); |
||||
} |
||||
|
||||
@Override |
||||
public RepositoryComposition.RepositoryFragments describe(RepositoryMetadata metadata) { |
||||
return MongoRepositoryFragmentsContributor.this.describe(metadata).append(after.describe(metadata)); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Creates {@link RepositoryComposition.RepositoryFragments} based on {@link RepositoryMetadata} to add |
||||
* MongoDB-specific extensions. |
||||
* |
||||
* @param metadata repository metadata. |
||||
* @param entityInformation must not be {@literal null}. |
||||
* @param operations must not be {@literal null}. |
||||
* @return {@link RepositoryComposition.RepositoryFragments} to be added to the repository. |
||||
*/ |
||||
RepositoryComposition.RepositoryFragments contribute(RepositoryMetadata metadata, |
||||
MongoEntityInformation<?, ?> entityInformation, MongoOperations operations); |
||||
|
||||
} |
||||
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
/* |
||||
* Copyright 2025 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.mongodb.repository.support; |
||||
|
||||
import static org.springframework.data.querydsl.QuerydslUtils.*; |
||||
|
||||
import org.springframework.data.mongodb.core.MongoOperations; |
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation; |
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor; |
||||
import org.springframework.data.repository.core.RepositoryMetadata; |
||||
import org.springframework.data.repository.core.support.RepositoryComposition; |
||||
import org.springframework.data.repository.core.support.RepositoryFragment; |
||||
import org.springframework.data.repository.core.support.RepositoryFragmentsContributor; |
||||
|
||||
/** |
||||
* MongoDB-specific {@link RepositoryFragmentsContributor} contributing Querydsl fragments if a repository implements |
||||
* {@link QuerydslPredicateExecutor}. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 5.0 |
||||
* @see QuerydslMongoPredicateExecutor |
||||
*/ |
||||
enum QuerydslContributor implements MongoRepositoryFragmentsContributor { |
||||
|
||||
INSTANCE; |
||||
|
||||
@Override |
||||
public RepositoryComposition.RepositoryFragments contribute(RepositoryMetadata metadata, |
||||
MongoEntityInformation<?, ?> entityInformation, MongoOperations operations) { |
||||
|
||||
if (isQuerydslRepository(metadata)) { |
||||
|
||||
QuerydslMongoPredicateExecutor<?> executor = new QuerydslMongoPredicateExecutor<>(entityInformation, operations); |
||||
|
||||
return RepositoryComposition.RepositoryFragments |
||||
.of(RepositoryFragment.implemented(QuerydslPredicateExecutor.class, executor)); |
||||
} |
||||
|
||||
return RepositoryComposition.RepositoryFragments.empty(); |
||||
} |
||||
|
||||
@Override |
||||
public RepositoryComposition.RepositoryFragments describe(RepositoryMetadata metadata) { |
||||
|
||||
if (isQuerydslRepository(metadata)) { |
||||
return RepositoryComposition.RepositoryFragments |
||||
.of(RepositoryFragment.structural(QuerydslPredicateExecutor.class, QuerydslMongoPredicateExecutor.class)); |
||||
} |
||||
|
||||
return RepositoryComposition.RepositoryFragments.empty(); |
||||
} |
||||
|
||||
private static boolean isQuerydslRepository(RepositoryMetadata metadata) { |
||||
return QUERY_DSL_PRESENT && QuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface()); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
/* |
||||
* Copyright 2025 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.mongodb.repository.support; |
||||
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations; |
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation; |
||||
import org.springframework.data.repository.core.RepositoryMetadata; |
||||
import org.springframework.data.repository.core.support.RepositoryComposition; |
||||
import org.springframework.data.repository.core.support.RepositoryFragmentsContributor; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Reactive MongoDB-specific {@link RepositoryFragmentsContributor} contributing fragments based on the repository. |
||||
* <p> |
||||
* Implementations must define a no-args constructor. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 5.0 |
||||
* @see ReactiveQuerydslMongoPredicateExecutor |
||||
*/ |
||||
public interface ReactiveMongoRepositoryFragmentsContributor extends RepositoryFragmentsContributor { |
||||
|
||||
ReactiveMongoRepositoryFragmentsContributor DEFAULT = ReactiveQuerydslContributor.INSTANCE; |
||||
|
||||
/** |
||||
* Returns a composed {@code ReactiveMongoRepositoryFragmentsContributor} that first applies this contributor to its |
||||
* inputs, and then applies the {@code after} contributor concatenating effectively both results. If evaluation of |
||||
* either contributors throws an exception, it is relayed to the caller of the composed contributor. |
||||
* |
||||
* @param after the contributor to apply after this contributor is applied. |
||||
* @return a composed contributor that first applies this contributor and then applies the {@code after} contributor. |
||||
*/ |
||||
default ReactiveMongoRepositoryFragmentsContributor andThen(ReactiveMongoRepositoryFragmentsContributor after) { |
||||
|
||||
Assert.notNull(after, "ReactiveMongoRepositoryFragmentsContributor must not be null"); |
||||
|
||||
return new ReactiveMongoRepositoryFragmentsContributor() { |
||||
|
||||
@Override |
||||
public RepositoryComposition.RepositoryFragments contribute(RepositoryMetadata metadata, |
||||
MongoEntityInformation<?, ?> entityInformation, ReactiveMongoOperations operations) { |
||||
return ReactiveMongoRepositoryFragmentsContributor.this.contribute(metadata, entityInformation, operations) |
||||
.append(after.contribute(metadata, entityInformation, operations)); |
||||
} |
||||
|
||||
@Override |
||||
public RepositoryComposition.RepositoryFragments describe(RepositoryMetadata metadata) { |
||||
return ReactiveMongoRepositoryFragmentsContributor.this.describe(metadata).append(after.describe(metadata)); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Creates {@link RepositoryComposition.RepositoryFragments} based on {@link RepositoryMetadata} to add |
||||
* MongoDB-specific extensions. |
||||
* |
||||
* @param metadata repository metadata. |
||||
* @param entityInformation must not be {@literal null}. |
||||
* @param operations must not be {@literal null}. |
||||
* @return {@link RepositoryComposition.RepositoryFragments} to be added to the repository. |
||||
*/ |
||||
RepositoryComposition.RepositoryFragments contribute(RepositoryMetadata metadata, |
||||
MongoEntityInformation<?, ?> entityInformation, ReactiveMongoOperations operations); |
||||
|
||||
} |
||||
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
/* |
||||
* Copyright 2025 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.mongodb.repository.support; |
||||
|
||||
import static org.springframework.data.querydsl.QuerydslUtils.*; |
||||
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations; |
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation; |
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor; |
||||
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor; |
||||
import org.springframework.data.repository.core.RepositoryMetadata; |
||||
import org.springframework.data.repository.core.support.RepositoryComposition; |
||||
import org.springframework.data.repository.core.support.RepositoryFragment; |
||||
import org.springframework.data.repository.core.support.RepositoryFragmentsContributor; |
||||
|
||||
/** |
||||
* Reactive MongoDB-specific {@link RepositoryFragmentsContributor} contributing Querydsl fragments if a repository |
||||
* implements {@link QuerydslPredicateExecutor}. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 5.0 |
||||
* @see ReactiveQuerydslMongoPredicateExecutor |
||||
*/ |
||||
enum ReactiveQuerydslContributor implements ReactiveMongoRepositoryFragmentsContributor { |
||||
|
||||
INSTANCE; |
||||
|
||||
@Override |
||||
public RepositoryComposition.RepositoryFragments contribute(RepositoryMetadata metadata, |
||||
MongoEntityInformation<?, ?> entityInformation, ReactiveMongoOperations operations) { |
||||
|
||||
if (isQuerydslRepository(metadata)) { |
||||
|
||||
ReactiveQuerydslPredicateExecutor<?> executor = new ReactiveQuerydslMongoPredicateExecutor<>(entityInformation, |
||||
operations); |
||||
|
||||
return RepositoryComposition.RepositoryFragments |
||||
.of(RepositoryFragment.implemented(ReactiveQuerydslPredicateExecutor.class, executor)); |
||||
} |
||||
|
||||
return RepositoryComposition.RepositoryFragments.empty(); |
||||
} |
||||
|
||||
@Override |
||||
public RepositoryComposition.RepositoryFragments describe(RepositoryMetadata metadata) { |
||||
|
||||
if (isQuerydslRepository(metadata)) { |
||||
return RepositoryComposition.RepositoryFragments.of(RepositoryFragment |
||||
.structural(ReactiveQuerydslPredicateExecutor.class, ReactiveQuerydslMongoPredicateExecutor.class)); |
||||
} |
||||
|
||||
return RepositoryComposition.RepositoryFragments.empty(); |
||||
} |
||||
|
||||
private static boolean isQuerydslRepository(RepositoryMetadata metadata) { |
||||
return QUERY_DSL_PRESENT |
||||
&& ReactiveQuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface()); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
/* |
||||
* Copyright 2025 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.mongodb.repository.aot; |
||||
|
||||
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
import example.aot.User; |
||||
import example.aot.UserRepository; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.aot.generate.GeneratedFiles; |
||||
import org.springframework.aot.test.generate.TestGenerationContext; |
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
||||
import org.springframework.context.annotation.ComponentScan; |
||||
import org.springframework.context.annotation.FilterType; |
||||
import org.springframework.context.aot.ApplicationContextAotGenerator; |
||||
import org.springframework.core.io.InputStreamResource; |
||||
import org.springframework.core.io.InputStreamSource; |
||||
import org.springframework.data.aot.AotContext; |
||||
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration; |
||||
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; |
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor; |
||||
import org.springframework.mock.env.MockPropertySource; |
||||
|
||||
import com.mongodb.client.MongoClient; |
||||
|
||||
/** |
||||
* Integration tests for AOT processing of imperative repositories. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
class AotContributionIntegrationTests { |
||||
|
||||
@EnableMongoRepositories(considerNestedRepositories = true, includeFilters = { |
||||
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = QuerydslUserRepository.class) }) |
||||
static class AotConfiguration extends AbstractMongoClientConfiguration { |
||||
|
||||
@Override |
||||
public MongoClient mongoClient() { |
||||
return mock(MongoClient.class); |
||||
} |
||||
|
||||
@Override |
||||
protected String getDatabaseName() { |
||||
return ""; |
||||
} |
||||
} |
||||
|
||||
interface QuerydslUserRepository extends UserRepository, QuerydslPredicateExecutor<User> { |
||||
|
||||
} |
||||
|
||||
@Test // GH-3830
|
||||
void shouldGenerateMetadataForBaseRepositoryAndQuerydslFragment() throws IOException { |
||||
|
||||
TestGenerationContext generationContext = generate(AotConfiguration.class); |
||||
|
||||
InputStreamSource metadata = generationContext.getGeneratedFiles().getGeneratedFile(GeneratedFiles.Kind.RESOURCE, |
||||
QuerydslUserRepository.class.getName().replace('.', '/') + ".json"); |
||||
|
||||
InputStreamResource isr = new InputStreamResource(metadata); |
||||
String json = isr.getContentAsString(StandardCharsets.UTF_8); |
||||
|
||||
assertThatJson(json).isObject() //
|
||||
.containsEntry("name", QuerydslUserRepository.class.getName()) //
|
||||
.containsEntry("module", "MongoDB") //
|
||||
.containsEntry("type", "IMPERATIVE"); |
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'findBy')].fragment").isArray().first().isObject() |
||||
.containsEntry("interface", "org.springframework.data.querydsl.QuerydslPredicateExecutor").containsEntry( |
||||
"fragment", "org.springframework.data.mongodb.repository.support.QuerydslMongoPredicateExecutor"); |
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'existsById')].fragment").isArray().first().isObject() |
||||
.containsEntry("fragment", "org.springframework.data.mongodb.repository.support.SimpleMongoRepository"); |
||||
} |
||||
|
||||
private static TestGenerationContext generate(Class<?>... configurationClasses) { |
||||
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); |
||||
context.getEnvironment().getPropertySources() |
||||
.addFirst(new MockPropertySource().withProperty(AotContext.GENERATED_REPOSITORIES_ENABLED, "true")); |
||||
context.register(configurationClasses); |
||||
|
||||
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator(); |
||||
|
||||
TestGenerationContext generationContext = new TestGenerationContext(); |
||||
generator.processAheadOfTime(context, generationContext); |
||||
return generationContext; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,192 @@
@@ -0,0 +1,192 @@
|
||||
/* |
||||
* Copyright 2025 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.mongodb.repository.aot; |
||||
|
||||
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.*; |
||||
import static org.assertj.core.api.Assertions.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
import example.aot.UserRepository; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
import org.junit.jupiter.api.Disabled; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.context.support.AbstractApplicationContext; |
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.core.io.UrlResource; |
||||
import org.springframework.data.mongodb.core.MongoOperations; |
||||
import org.springframework.data.mongodb.test.util.MongoClientExtension; |
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; |
||||
|
||||
/** |
||||
* Integration tests for the {@link UserRepository} JSON metadata via {@link MongoRepositoryContributor}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
@ExtendWith(MongoClientExtension.class) |
||||
@SpringJUnitConfig(classes = MongoRepositoryMetadataTests.MongoRepositoryContributorConfiguration.class) |
||||
class MongoRepositoryMetadataTests { |
||||
|
||||
@Configuration |
||||
static class MongoRepositoryContributorConfiguration extends AotFragmentTestConfigurationSupport { |
||||
|
||||
public MongoRepositoryContributorConfiguration() { |
||||
super(UserRepository.class); |
||||
} |
||||
|
||||
@Bean |
||||
MongoOperations mongoOperations() { |
||||
return mock(MongoOperations.class); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Autowired AbstractApplicationContext context; |
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentBase() throws IOException { |
||||
|
||||
Resource resource = getResource(); |
||||
|
||||
assertThat(resource).isNotNull(); |
||||
assertThat(resource.exists()).isTrue(); |
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8); |
||||
|
||||
assertThatJson(json).isObject() //
|
||||
.containsEntry("name", UserRepository.class.getName()) //
|
||||
.containsEntry("module", "MongoDB") //
|
||||
.containsEntry("type", "IMPERATIVE"); |
||||
} |
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentDerivedQuery() throws IOException { |
||||
|
||||
Resource resource = getResource(); |
||||
|
||||
assertThat(resource).isNotNull(); |
||||
assertThat(resource.exists()).isTrue(); |
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8); |
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'countUsersByLastname')].query").isArray().element(0).isObject() |
||||
.containsEntry("filter", "{'lastname':?0}"); |
||||
} |
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentSortedQuery() throws IOException { |
||||
|
||||
Resource resource = getResource(); |
||||
|
||||
assertThat(resource).isNotNull(); |
||||
assertThat(resource.exists()).isTrue(); |
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8); |
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'findByLastnameStartingWithOrderByUsername')].query") //
|
||||
.isArray().element(0).isObject() //
|
||||
.containsEntry("filter", "{'lastname':{'$regex':/^\\Q?0\\E/}}") |
||||
.containsEntry("sort", "{'username':{'$numberInt':'1'}}"); |
||||
} |
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentPagedQuery() throws IOException { |
||||
|
||||
Resource resource = getResource(); |
||||
|
||||
assertThat(resource).isNotNull(); |
||||
assertThat(resource.exists()).isTrue(); |
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8); |
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'findPageOfUsersByLastnameStartingWith')].query").isArray() |
||||
.element(0).isObject().containsEntry("filter", "{'lastname':{'$regex':/^\\Q?0\\E/}}"); |
||||
} |
||||
|
||||
@Test // GH-3830
|
||||
@Disabled("No support for expressions yet") |
||||
void shouldDocumentQueryWithExpression() throws IOException { |
||||
|
||||
Resource resource = getResource(); |
||||
|
||||
assertThat(resource).isNotNull(); |
||||
assertThat(resource.exists()).isTrue(); |
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8); |
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'findValueExpressionNamedByEmailAddress')].query").isArray() |
||||
.first().isObject().containsEntry("query", "select u from User u where u.emailAddress = :__$synthetic$__1"); |
||||
} |
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentAggregation() throws IOException { |
||||
|
||||
Resource resource = getResource(); |
||||
|
||||
assertThat(resource).isNotNull(); |
||||
assertThat(resource.exists()).isTrue(); |
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8); |
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'findAllLastnames')].query").isArray().element(0).isObject() |
||||
.containsEntry("pipeline", |
||||
"[{ '$match' : { 'last_name' : { '$ne' : null } } }, { '$project': { '_id' : '$last_name' } }]"); |
||||
} |
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentPipelineUpdate() throws IOException { |
||||
|
||||
Resource resource = getResource(); |
||||
|
||||
assertThat(resource).isNotNull(); |
||||
assertThat(resource.exists()).isTrue(); |
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8); |
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'findAndIncrementVisitsViaPipelineByLastname')].query").isArray() |
||||
.element(0).isObject().containsEntry("filter", "{'lastname':?0}").containsEntry("update-pipeline", |
||||
"[{ '$set' : { 'visits' : { '$ifNull' : [ {'$add' : [ '$visits', ?1 ] }, ?1 ] } } }]"); |
||||
} |
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentBaseFragment() throws IOException { |
||||
|
||||
Resource resource = getResource(); |
||||
|
||||
assertThat(resource).isNotNull(); |
||||
assertThat(resource.exists()).isTrue(); |
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8); |
||||
System.out.println(json); |
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'existsById')].fragment").isArray().first().isObject() |
||||
.containsEntry("fragment", "org.springframework.data.mongodb.repository.support.SimpleMongoRepository"); |
||||
} |
||||
|
||||
private Resource getResource() { |
||||
|
||||
String location = UserRepository.class.getPackageName().replace('.', '/') + "/" |
||||
+ UserRepository.class.getSimpleName() + ".json"; |
||||
return new UrlResource(context.getBeanFactory().getBeanClassLoader().getResource(location)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
|
||||
/* |
||||
* Copyright 2025 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.mongodb.repository.aot; |
||||
|
||||
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
import example.aot.User; |
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.aot.generate.GeneratedFiles; |
||||
import org.springframework.aot.test.generate.TestGenerationContext; |
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
||||
import org.springframework.context.annotation.ComponentScan; |
||||
import org.springframework.context.annotation.FilterType; |
||||
import org.springframework.context.aot.ApplicationContextAotGenerator; |
||||
import org.springframework.core.io.InputStreamResource; |
||||
import org.springframework.core.io.InputStreamSource; |
||||
import org.springframework.data.aot.AotContext; |
||||
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration; |
||||
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories; |
||||
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor; |
||||
import org.springframework.data.repository.CrudRepository; |
||||
import org.springframework.mock.env.MockPropertySource; |
||||
|
||||
import com.mongodb.client.MongoClient; |
||||
|
||||
/** |
||||
* Integration tests for AOT processing of reactive repositories. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
class ReactiveAotContributionIntegrationTests { |
||||
|
||||
@EnableReactiveMongoRepositories(considerNestedRepositories = true, includeFilters = { |
||||
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = ReactiveQuerydslUserRepository.class) }) |
||||
static class AotConfiguration extends AbstractMongoClientConfiguration { |
||||
|
||||
@Override |
||||
public MongoClient mongoClient() { |
||||
return mock(MongoClient.class); |
||||
} |
||||
|
||||
@Override |
||||
protected String getDatabaseName() { |
||||
return ""; |
||||
} |
||||
} |
||||
|
||||
interface ReactiveQuerydslUserRepository |
||||
extends CrudRepository<User, String>, ReactiveQuerydslPredicateExecutor<User> { |
||||
|
||||
Flux<User> findUserNoArgumentsBy(); |
||||
|
||||
Mono<User> findOneByUsername(String username); |
||||
|
||||
} |
||||
|
||||
@Test // GH-3830
|
||||
void shouldGenerateMetadataForBaseRepositoryAndQuerydslFragment() throws IOException { |
||||
|
||||
TestGenerationContext generationContext = generate(AotConfiguration.class); |
||||
|
||||
InputStreamSource metadata = generationContext.getGeneratedFiles().getGeneratedFile(GeneratedFiles.Kind.RESOURCE, |
||||
ReactiveQuerydslUserRepository.class.getName().replace('.', '/') + ".json"); |
||||
|
||||
InputStreamResource isr = new InputStreamResource(metadata); |
||||
String json = isr.getContentAsString(StandardCharsets.UTF_8); |
||||
|
||||
assertThatJson(json).isObject() //
|
||||
.containsEntry("name", ReactiveQuerydslUserRepository.class.getName()) //
|
||||
.containsEntry("module", "MongoDB") //
|
||||
.containsEntry("type", "REACTIVE"); |
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'findBy')].fragment").isArray().first().isObject() |
||||
.containsEntry("interface", "org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor") |
||||
.containsEntry("fragment", |
||||
"org.springframework.data.mongodb.repository.support.ReactiveQuerydslMongoPredicateExecutor"); |
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'existsById')].fragment").isArray().first().isObject() |
||||
.containsEntry("fragment", "org.springframework.data.mongodb.repository.support.SimpleReactiveMongoRepository"); |
||||
} |
||||
|
||||
private static TestGenerationContext generate(Class<?>... configurationClasses) { |
||||
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); |
||||
context.getEnvironment().getPropertySources() |
||||
.addFirst(new MockPropertySource().withProperty(AotContext.GENERATED_REPOSITORIES_ENABLED, "true")); |
||||
context.register(configurationClasses); |
||||
|
||||
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator(); |
||||
|
||||
TestGenerationContext generationContext = new TestGenerationContext(); |
||||
generator.processAheadOfTime(context, generationContext); |
||||
return generationContext; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
/* |
||||
* Copyright 2025 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.mongodb.repository.support; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
import java.util.Iterator; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.data.mongodb.core.MongoOperations; |
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter; |
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; |
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; |
||||
import org.springframework.data.mongodb.repository.User; |
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation; |
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor; |
||||
import org.springframework.data.repository.Repository; |
||||
import org.springframework.data.repository.core.RepositoryMetadata; |
||||
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata; |
||||
import org.springframework.data.repository.core.support.RepositoryComposition; |
||||
import org.springframework.data.repository.core.support.RepositoryFragment; |
||||
|
||||
/** |
||||
* Unit tests for {@link MongoRepositoryFragmentsContributor}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
class MongoRepositoryFragmentsContributorUnitTests { |
||||
|
||||
@Test // GH-3279
|
||||
void composedContributorShouldCreateFragments() { |
||||
|
||||
MongoMappingContext mappingContext = new MongoMappingContext(); |
||||
MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext); |
||||
MongoOperations operations = mock(MongoOperations.class); |
||||
when(operations.getConverter()).thenReturn(converter); |
||||
|
||||
MongoRepositoryFragmentsContributor contributor = MongoRepositoryFragmentsContributor.DEFAULT |
||||
.andThen(MyMongoRepositoryFragmentsContributor.INSTANCE); |
||||
|
||||
RepositoryComposition.RepositoryFragments fragments = contributor.contribute( |
||||
AbstractRepositoryMetadata.getMetadata(QuerydslUserRepository.class), |
||||
new MappingMongoEntityInformation<>(mappingContext.getPersistentEntity(User.class)), operations); |
||||
|
||||
assertThat(fragments).hasSize(2); |
||||
|
||||
Iterator<RepositoryFragment<?>> iterator = fragments.iterator(); |
||||
|
||||
RepositoryFragment<?> querydsl = iterator.next(); |
||||
assertThat(querydsl.getImplementationClass()).contains(QuerydslMongoPredicateExecutor.class); |
||||
|
||||
RepositoryFragment<?> additional = iterator.next(); |
||||
assertThat(additional.getImplementationClass()).contains(MyFragment.class); |
||||
} |
||||
|
||||
enum MyMongoRepositoryFragmentsContributor implements MongoRepositoryFragmentsContributor { |
||||
|
||||
INSTANCE; |
||||
|
||||
@Override |
||||
public RepositoryComposition.RepositoryFragments contribute(RepositoryMetadata metadata, |
||||
MongoEntityInformation<?, ?> entityInformation, MongoOperations operations) { |
||||
return RepositoryComposition.RepositoryFragments.just(new MyFragment()); |
||||
} |
||||
|
||||
@Override |
||||
public RepositoryComposition.RepositoryFragments describe(RepositoryMetadata metadata) { |
||||
return RepositoryComposition.RepositoryFragments.just(new MyFragment()); |
||||
} |
||||
} |
||||
|
||||
static class MyFragment { |
||||
|
||||
} |
||||
|
||||
interface QuerydslUserRepository extends Repository<User, Long>, QuerydslPredicateExecutor<User> {} |
||||
|
||||
} |
||||
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
/* |
||||
* Copyright 2025 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.mongodb.repository.support; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
import java.util.Iterator; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations; |
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter; |
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; |
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; |
||||
import org.springframework.data.mongodb.repository.User; |
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation; |
||||
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor; |
||||
import org.springframework.data.repository.Repository; |
||||
import org.springframework.data.repository.core.RepositoryMetadata; |
||||
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata; |
||||
import org.springframework.data.repository.core.support.RepositoryComposition; |
||||
import org.springframework.data.repository.core.support.RepositoryFragment; |
||||
|
||||
/** |
||||
* Unit tests for {@link ReactiveMongoRepositoryFragmentsContributor}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
class ReactiveMongoRepositoryFragmentsContributorUnitTests { |
||||
|
||||
@Test // GH-3279
|
||||
void composedContributorShouldCreateFragments() { |
||||
|
||||
MongoMappingContext mappingContext = new MongoMappingContext(); |
||||
MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext); |
||||
ReactiveMongoOperations operations = mock(ReactiveMongoOperations.class); |
||||
when(operations.getConverter()).thenReturn(converter); |
||||
|
||||
ReactiveMongoRepositoryFragmentsContributor contributor = ReactiveMongoRepositoryFragmentsContributor.DEFAULT |
||||
.andThen(MyMongoRepositoryFragmentsContributor.INSTANCE); |
||||
|
||||
RepositoryComposition.RepositoryFragments fragments = contributor.contribute( |
||||
AbstractRepositoryMetadata.getMetadata(QuerydslUserRepository.class), |
||||
new MappingMongoEntityInformation<>(mappingContext.getPersistentEntity(User.class)), operations); |
||||
|
||||
assertThat(fragments).hasSize(2); |
||||
|
||||
Iterator<RepositoryFragment<?>> iterator = fragments.iterator(); |
||||
|
||||
RepositoryFragment<?> querydsl = iterator.next(); |
||||
assertThat(querydsl.getImplementationClass()).contains(ReactiveQuerydslMongoPredicateExecutor.class); |
||||
|
||||
RepositoryFragment<?> additional = iterator.next(); |
||||
assertThat(additional.getImplementationClass()).contains(MyFragment.class); |
||||
} |
||||
|
||||
enum MyMongoRepositoryFragmentsContributor implements ReactiveMongoRepositoryFragmentsContributor { |
||||
|
||||
INSTANCE; |
||||
|
||||
@Override |
||||
public RepositoryComposition.RepositoryFragments contribute(RepositoryMetadata metadata, |
||||
MongoEntityInformation<?, ?> entityInformation, ReactiveMongoOperations operations) { |
||||
return RepositoryComposition.RepositoryFragments.just(new MyFragment()); |
||||
} |
||||
|
||||
@Override |
||||
public RepositoryComposition.RepositoryFragments describe(RepositoryMetadata metadata) { |
||||
return RepositoryComposition.RepositoryFragments.just(new MyFragment()); |
||||
} |
||||
} |
||||
|
||||
static class MyFragment { |
||||
|
||||
} |
||||
|
||||
interface QuerydslUserRepository extends Repository<User, Long>, ReactiveQuerydslPredicateExecutor<User> {} |
||||
|
||||
} |
||||
Loading…
Reference in new issue