Add support for RepositoryFragmentsContributor.

This change adopts the newly added AOT changes from https://github.com/spring-projects/spring-data-commons/pull/3282 to Cassandra repositories.

- Introduces repository fragments contributor and updates the repository factories and factory beans w/ to use the default implementations.

- Also makes the `CassandraRepositoryFactoryBean` consistent with its reactive counterpart by only setting the mapping context from the `CassandraOperations` in `afterPropertiesSet` iff the mapping context was not specified by the user.

Closes #1574
This commit is contained in:
Chris Bono
2025-05-08 17:56:51 -05:00
committed by Mark Paluch
parent ccc0924bc2
commit d2755135b7
12 changed files with 604 additions and 6 deletions

View File

@@ -25,6 +25,7 @@ import org.springframework.data.cassandra.config.DefaultBeanNames;
import org.springframework.data.cassandra.core.mapping.Table;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.repository.support.CassandraRepositoryFactoryBean;
import org.springframework.data.cassandra.repository.support.SimpleCassandraRepository;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
@@ -40,6 +41,7 @@ import org.w3c.dom.Element;
* @author Mark Paluch
* @author Christoph Strobl
* @author Mateusz Szymczak
* @author Chris Bono
*/
public class CassandraRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport {
@@ -55,6 +57,11 @@ public class CassandraRepositoryConfigurationExtension extends RepositoryConfigu
return "cassandra";
}
@Override
public String getRepositoryBaseClassName() {
return SimpleCassandraRepository.class.getName();
}
@Override
public String getRepositoryFactoryBeanClassName() {
return CassandraRepositoryFactoryBean.class.getName();

View File

@@ -21,6 +21,7 @@ import java.util.Collections;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.data.cassandra.repository.ReactiveCassandraRepository;
import org.springframework.data.cassandra.repository.support.ReactiveCassandraRepositoryFactoryBean;
import org.springframework.data.cassandra.repository.support.SimpleReactiveCassandraRepository;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
import org.springframework.data.repository.core.RepositoryMetadata;
@@ -30,6 +31,7 @@ import org.springframework.util.StringUtils;
* {@link RepositoryConfigurationExtension} for Cassandra.
*
* @author Mark Paluch
* @author Chris Bono
* @since 2.0
*/
public class ReactiveCassandraRepositoryConfigurationExtension extends CassandraRepositoryConfigurationExtension {
@@ -39,6 +41,11 @@ public class ReactiveCassandraRepositoryConfigurationExtension extends Cassandra
return "Reactive Cassandra";
}
@Override
public String getRepositoryBaseClassName() {
return SimpleReactiveCassandraRepository.class.getName();
}
@Override
public String getRepositoryFactoryBeanClassName() {
return ReactiveCassandraRepositoryFactoryBean.class.getName();

View File

@@ -34,6 +34,7 @@ import org.springframework.data.projection.ProjectionFactory;
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.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.query.CachingValueExpressionDelegate;
import org.springframework.data.repository.query.QueryLookupStrategy;
@@ -50,6 +51,7 @@ import org.springframework.util.Assert;
* @author Thomas Darimont
* @author Mark Paluch
* @author John Blum
* @author Chris Bono
*/
public class CassandraRepositoryFactory extends RepositoryFactorySupport {
@@ -57,6 +59,8 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
private final CassandraOperations operations;
private CassandraRepositoryFragmentsContributor fragmentsContributor = CassandraRepositoryFragmentsContributor.DEFAULT;
/**
* Create a new {@link CassandraRepositoryFactory} with the given {@link CassandraOperations}.
*
@@ -70,6 +74,17 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
this.mappingContext = operations.getConverter().getMappingContext();
}
/**
* Configures the {@link CassandraRepositoryFragmentsContributor} to be used. Defaults to
* {@link CassandraRepositoryFragmentsContributor#DEFAULT}.
*
* @param fragmentsContributor
* @since 5.0
*/
public void setFragmentsContributor(CassandraRepositoryFragmentsContributor fragmentsContributor) {
this.fragmentsContributor = fragmentsContributor;
}
@Override
protected ProjectionFactory getProjectionFactory(@Nullable ClassLoader classLoader,
@Nullable BeanFactory beanFactory) {
@@ -103,6 +118,23 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
new CachingValueExpressionDelegate(valueExpressionDelegate), mappingContext));
}
@Override
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
return getRepositoryFragments(metadata, operations);
}
/**
* Creates {@link RepositoryFragments} based on {@link RepositoryMetadata} to add Cassandra-specific extensions.
* Built-in fragment contribution can be customized by configuring {@link CassandraRepositoryFragmentsContributor}.
*
* @param metadata repository metadata.
* @param operations the Cassandra operations manager.
* @return {@link RepositoryFragments} to be added to the repository.
* @since 5.0
*/
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata, CassandraOperations operations) {
return fragmentsContributor.contribute(metadata, getEntityInformation(metadata.getDomainType()), operations);
}
private record CassandraQueryLookupStrategy(CassandraOperations operations,
ValueExpressionDelegate valueExpressionDelegate,
@@ -130,4 +162,3 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
}
}

View File

@@ -19,6 +19,7 @@ import org.jspecify.annotations.Nullable;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
@@ -31,12 +32,17 @@ import org.springframework.util.Assert;
* @author John Blum
* @author Oliver Gierke
* @author Mark Paluch
* @author Chris Bono
*/
public class CassandraRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
extends RepositoryFactoryBeanSupport<T, S, ID> {
private boolean mappingContextConfigured = false;
private @Nullable CassandraOperations cassandraOperations;
private CassandraRepositoryFragmentsContributor repositoryFragmentsContributor = CassandraRepositoryFragmentsContributor.DEFAULT;
/**
* Create a new {@link CassandraRepositoryFactoryBean} for the given repository interface.
*
@@ -51,7 +57,35 @@ public class CassandraRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
Assert.state(cassandraOperations != null, "CassandraOperations must not be null");
return new CassandraRepositoryFactory(cassandraOperations);
CassandraRepositoryFactory factory = getFactoryInstance(cassandraOperations);
factory.setFragmentsContributor(repositoryFragmentsContributor);
return factory;
}
/**
* Creates and initializes a {@link CassandraRepositoryFactory} instance.
*
* @param operations the Cassandra operations
* @return new {@link CassandraRepositoryFactory} instance
*/
protected CassandraRepositoryFactory getFactoryInstance(CassandraOperations operations) {
return new CassandraRepositoryFactory(operations);
}
@Override
public CassandraRepositoryFragmentsContributor getRepositoryFragmentsContributor() {
return this.repositoryFragmentsContributor;
}
/**
* Configures the {@link CassandraRepositoryFragmentsContributor} to contribute built-in fragment functionality to the
* repository.
*
* @param repositoryFragmentsContributor must not be {@literal null}.
* @since 5.0
*/
public void setRepositoryFragmentsContributor(CassandraRepositoryFragmentsContributor repositoryFragmentsContributor) {
this.repositoryFragmentsContributor = repositoryFragmentsContributor;
}
/**
@@ -61,9 +95,15 @@ public class CassandraRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
* on Apache Cassandra.
*/
public void setCassandraTemplate(CassandraTemplate cassandraTemplate) {
this.cassandraOperations = cassandraTemplate;
setMappingContext(cassandraTemplate.getConverter().getMappingContext());
}
@Override
protected void setMappingContext(MappingContext<?, ?> mappingContext) {
super.setMappingContext(mappingContext);
this.mappingContextConfigured = true;
}
@Override
@@ -72,5 +112,9 @@ public class CassandraRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
super.afterPropertiesSet();
Assert.notNull(cassandraOperations, "CassandraOperations must not be null");
if (!mappingContextConfigured) {
setMappingContext(cassandraOperations.getConverter().getMappingContext());
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.cassandra.repository.support;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryFragmentsContributor;
import org.springframework.util.Assert;
/**
* Cassandra-specific {@link RepositoryFragmentsContributor} contributing fragments based on the repository.
* <p>
* Implementations must define a no-args constructor.
*
* @author Chris Bono
* @since 5.0
*/
public interface CassandraRepositoryFragmentsContributor extends RepositoryFragmentsContributor {
CassandraRepositoryFragmentsContributor DEFAULT = EmptyFragmentsContributor.INSTANCE;
/**
* Returns a composed {@code CassandraRepositoryFragmentsContributor} 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 CassandraRepositoryFragmentsContributor andThen(CassandraRepositoryFragmentsContributor after) {
Assert.notNull(after, "CassandraRepositoryFragmentsContributor must not be null");
return new CassandraRepositoryFragmentsContributor() {
@Override
public RepositoryFragments contribute(RepositoryMetadata metadata,
CassandraEntityInformation<?, ?> entityInformation, CassandraOperations operations) {
return CassandraRepositoryFragmentsContributor.this.contribute(metadata, entityInformation, operations)
.append(after.contribute(metadata, entityInformation, operations));
}
@Override
public RepositoryFragments describe(RepositoryMetadata metadata) {
return CassandraRepositoryFragmentsContributor.this.describe(metadata).append(after.describe(metadata));
}
};
}
/**
* Creates {@link RepositoryFragments} based on {@link RepositoryMetadata} to add
* Cassandra-specific extensions.
*
* @param metadata repository metadata.
* @param entityInformation must not be {@literal null}.
* @param operations must not be {@literal null}.
* @return {@link RepositoryFragments} to be added to the repository.
*/
RepositoryFragments contribute(RepositoryMetadata metadata,
CassandraEntityInformation<?, ?> entityInformation, CassandraOperations operations);
}

View File

@@ -0,0 +1,43 @@
/*
* 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.cassandra.repository.support;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition;
/**
* Implementation of {@link CassandraRepositoryFragmentsContributor} that contributes empty fragments by default.
*
* @author Chris Bono
* @since 5.0
*/
enum EmptyFragmentsContributor implements CassandraRepositoryFragmentsContributor {
INSTANCE;
@Override
public RepositoryComposition.RepositoryFragments contribute(RepositoryMetadata metadata,
CassandraEntityInformation<?, ?> entityInformation, CassandraOperations operations) {
return RepositoryComposition.RepositoryFragments.empty();
}
@Override
public RepositoryComposition.RepositoryFragments describe(RepositoryMetadata metadata) {
return RepositoryComposition.RepositoryFragments.empty();
}
}

View File

@@ -34,6 +34,7 @@ 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.ReactiveRepositoryFactorySupport;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.query.CachingValueExpressionDelegate;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
@@ -46,6 +47,7 @@ import org.springframework.util.Assert;
*
* @author Mark Paluch
* @author Marcin Grzejszczak
* @author Chris Bono
* @since 2.0
*/
public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactorySupport {
@@ -54,6 +56,8 @@ public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactor
private final MappingContext<? extends CassandraPersistentEntity<?>, ? extends CassandraPersistentProperty> mappingContext;
private ReactiveCassandraRepositoryFragmentsContributor fragmentsContributor = ReactiveCassandraRepositoryFragmentsContributor.DEFAULT;
/**
* Create a new {@link ReactiveCassandraRepositoryFactory} with the given {@link ReactiveCassandraOperations}.
*
@@ -67,6 +71,17 @@ public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactor
this.mappingContext = cassandraOperations.getConverter().getMappingContext();
}
/**
* Configures the {@link ReactiveCassandraRepositoryFragmentsContributor} to be used. Defaults to
* {@link ReactiveCassandraRepositoryFragmentsContributor#DEFAULT}.
*
* @param fragmentsContributor
* @since 5.0
*/
public void setFragmentsContributor(ReactiveCassandraRepositoryFragmentsContributor fragmentsContributor) {
this.fragmentsContributor = fragmentsContributor;
}
@Override
protected ProjectionFactory getProjectionFactory(@Nullable ClassLoader classLoader,
@Nullable BeanFactory beanFactory) {
@@ -100,6 +115,24 @@ public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactor
return new MappingCassandraEntityInformation<>(entity, operations.getConverter());
}
@Override
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
return getRepositoryFragments(metadata, operations);
}
/**
* Creates {@link RepositoryFragments} based on {@link RepositoryMetadata} to add Cassandra-specific extensions.
* Built-in fragment contribution can be customized by configuring {@link CassandraRepositoryFragmentsContributor}.
*
* @param metadata repository metadata.
* @param operations the Cassandra operations manager.
* @return {@link RepositoryFragments} to be added to the repository.
* @since 5.0
*/
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata, ReactiveCassandraOperations operations) {
return fragmentsContributor.contribute(metadata, getEntityInformation(metadata.getDomainType()), operations);
}
/**
* {@link QueryLookupStrategy} to create
* {@link org.springframework.data.cassandra.repository.query.PartTreeCassandraQuery} instances.

View File

@@ -29,6 +29,7 @@ import org.springframework.util.Assert;
* {@link org.springframework.data.cassandra.repository.ReactiveCassandraRepository} instances.
*
* @author Mark Paluch
* @author Chris Bono
* @since 2.0
* @see org.springframework.data.repository.reactive.ReactiveSortingRepository
*/
@@ -39,6 +40,8 @@ public class ReactiveCassandraRepositoryFactoryBean<T extends Repository<S, ID>,
private @Nullable ReactiveCassandraOperations operations;
private ReactiveCassandraRepositoryFragmentsContributor repositoryFragmentsContributor = ReactiveCassandraRepositoryFragmentsContributor.DEFAULT;
/**
* Create a new {@link ReactiveCassandraRepositoryFactoryBean} for the given repository interface.
*
@@ -71,7 +74,9 @@ public class ReactiveCassandraRepositoryFactoryBean<T extends Repository<S, ID>,
Assert.state(operations != null, "ReactiveCassandraOperations must not be null");
return getFactoryInstance(operations);
ReactiveCassandraRepositoryFactory factory = getFactoryInstance(operations);
factory.setFragmentsContributor(repositoryFragmentsContributor);
return factory;
}
/**
@@ -80,10 +85,26 @@ public class ReactiveCassandraRepositoryFactoryBean<T extends Repository<S, ID>,
* @param operations
* @return
*/
protected RepositoryFactorySupport getFactoryInstance(ReactiveCassandraOperations operations) {
protected ReactiveCassandraRepositoryFactory getFactoryInstance(ReactiveCassandraOperations operations) {
return new ReactiveCassandraRepositoryFactory(operations);
}
@Override
public ReactiveCassandraRepositoryFragmentsContributor getRepositoryFragmentsContributor() {
return this.repositoryFragmentsContributor;
}
/**
* Configures the {@link ReactiveCassandraRepositoryFragmentsContributor} to contribute built-in fragment
* functionality to the repository.
*
* @param repositoryFragmentsContributor must not be {@literal null}.
* @since 5.0
*/
public void setRepositoryFragmentsContributor(ReactiveCassandraRepositoryFragmentsContributor repositoryFragmentsContributor) {
this.repositoryFragmentsContributor = repositoryFragmentsContributor;
}
@Override
public void afterPropertiesSet() {

View File

@@ -0,0 +1,77 @@
/*
* 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.cassandra.repository.support;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryFragmentsContributor;
import org.springframework.util.Assert;
/**
* Reactive Cassandra-specific {@link RepositoryFragmentsContributor} contributing fragments based on the repository.
* <p>
* Implementations must define a no-args constructor.
*
* @author Chris Bono
* @since 5.0
*/
public interface ReactiveCassandraRepositoryFragmentsContributor extends RepositoryFragmentsContributor {
ReactiveCassandraRepositoryFragmentsContributor DEFAULT = ReactiveEmptyFragmentsContributor.INSTANCE;
/**
* Returns a composed {@code ReactiveCassandraRepositoryFragmentsContributor} 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 ReactiveCassandraRepositoryFragmentsContributor andThen(ReactiveCassandraRepositoryFragmentsContributor after) {
Assert.notNull(after, "ReactiveCassandraRepositoryFragmentsContributor must not be null");
return new ReactiveCassandraRepositoryFragmentsContributor() {
@Override
public RepositoryFragments contribute(RepositoryMetadata metadata,
CassandraEntityInformation<?, ?> entityInformation, ReactiveCassandraOperations operations) {
return ReactiveCassandraRepositoryFragmentsContributor.this.contribute(metadata, entityInformation, operations)
.append(after.contribute(metadata, entityInformation, operations));
}
@Override
public RepositoryFragments describe(RepositoryMetadata metadata) {
return ReactiveCassandraRepositoryFragmentsContributor.this.describe(metadata).append(after.describe(metadata));
}
};
}
/**
* Creates {@link RepositoryFragments} based on {@link RepositoryMetadata} to add
* Cassandra-specific extensions.
*
* @param metadata repository metadata.
* @param entityInformation must not be {@literal null}.
* @param operations must not be {@literal null}.
* @return {@link RepositoryFragments} to be added to the repository.
*/
RepositoryFragments contribute(RepositoryMetadata metadata,
CassandraEntityInformation<?, ?> entityInformation, ReactiveCassandraOperations operations);
}

View File

@@ -0,0 +1,44 @@
/*
* 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.cassandra.repository.support;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition;
/**
* Implementation of {@link ReactiveCassandraRepositoryFragmentsContributor} that contributes empty fragments by
* default.
*
* @author Chris Bono
* @since 5.0
*/
enum ReactiveEmptyFragmentsContributor implements ReactiveCassandraRepositoryFragmentsContributor {
INSTANCE;
@Override
public RepositoryComposition.RepositoryFragments contribute(RepositoryMetadata metadata,
CassandraEntityInformation<?, ?> entityInformation, ReactiveCassandraOperations operations) {
return RepositoryComposition.RepositoryFragments.empty();
}
@Override
public RepositoryComposition.RepositoryFragments describe(RepositoryMetadata metadata) {
return RepositoryComposition.RepositoryFragments.empty();
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.cassandra.support;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.domain.User;
import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
import org.springframework.data.cassandra.repository.support.CassandraRepositoryFragmentsContributor;
import org.springframework.data.cassandra.repository.support.MappingCassandraEntityInformation;
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.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryFragment;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* Unit tests for {@link CassandraRepositoryFragmentsContributor}.
*
* @author Chris Bono
*/
class CassandraRepositoryFragmentsContributorUnitTests {
@Test // GH-3279
void composedContributorShouldCreateFragments() {
var mappingContext = new CassandraMappingContext();
var converter = new MappingCassandraConverter(mappingContext);
CassandraOperations operations = mock();
when(operations.getConverter()).thenReturn(converter);
var contributor = CassandraRepositoryFragmentsContributor.DEFAULT
.andThen(MyCassandraRepositoryFragmentsContributor.INSTANCE)
.andThen(MyOtherCassandraRepositoryFragmentsContributor.INSTANCE);
var fragments = contributor.contribute(
AbstractRepositoryMetadata.getMetadata(MyUserRepo.class),
new MappingCassandraEntityInformation<>(mappingContext.getPersistentEntity(User.class), converter),
operations);
assertThat(fragments.stream())
.extracting(RepositoryFragment::getImplementationClass)
.containsExactly(Optional.of(MyFragment.class), Optional.of(MyOtherFragment.class));
}
enum MyCassandraRepositoryFragmentsContributor implements CassandraRepositoryFragmentsContributor {
INSTANCE;
@Override
public RepositoryFragments contribute(RepositoryMetadata metadata,
CassandraEntityInformation<?, ?> entityInformation, CassandraOperations operations) {
return RepositoryFragments.just(new MyFragment());
}
@Override
public RepositoryFragments describe(RepositoryMetadata metadata) {
return RepositoryFragments.just(new MyFragment());
}
}
enum MyOtherCassandraRepositoryFragmentsContributor implements CassandraRepositoryFragmentsContributor {
INSTANCE;
@Override
public RepositoryFragments contribute(RepositoryMetadata metadata,
CassandraEntityInformation<?, ?> entityInformation, CassandraOperations operations) {
return RepositoryFragments.just(new MyOtherFragment());
}
@Override
public RepositoryFragments describe(RepositoryMetadata metadata) {
return RepositoryFragments.just(new MyOtherFragment());
}
}
static class MyFragment {
}
static class MyOtherFragment {
}
interface MyUserRepo extends Repository<User, Long> {}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.cassandra.support;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.domain.User;
import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
import org.springframework.data.cassandra.repository.support.MappingCassandraEntityInformation;
import org.springframework.data.cassandra.repository.support.ReactiveCassandraRepositoryFragmentsContributor;
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.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryFragment;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* Unit tests for {@link ReactiveCassandraRepositoryFragmentsContributor}.
*
* @author Chris Bono
*/
class ReactiveCassandraRepositoryFragmentsContributorUnitTests {
@Test // GH-3279
void composedContributorShouldCreateFragments() {
var mappingContext = new CassandraMappingContext();
var converter = new MappingCassandraConverter(mappingContext);
ReactiveCassandraOperations operations = mock();
when(operations.getConverter()).thenReturn(converter);
var contributor = ReactiveCassandraRepositoryFragmentsContributor.DEFAULT
.andThen(MyReactiveCassandraRepositoryFragmentsContributor.INSTANCE)
.andThen(MyOtherReactiveCassandraRepositoryFragmentsContributor.INSTANCE);
var fragments = contributor.contribute(
AbstractRepositoryMetadata.getMetadata(MyUserRepo.class),
new MappingCassandraEntityInformation<>(mappingContext.getPersistentEntity(User.class), converter),
operations);
assertThat(fragments.stream())
.extracting(RepositoryFragment::getImplementationClass)
.containsExactly(Optional.of(MyReactiveFragment.class), Optional.of(MyOtherReactiveFragment.class));
}
enum MyReactiveCassandraRepositoryFragmentsContributor implements ReactiveCassandraRepositoryFragmentsContributor {
INSTANCE;
@Override
public RepositoryFragments contribute(RepositoryMetadata metadata,
CassandraEntityInformation<?, ?> entityInformation, ReactiveCassandraOperations operations) {
return RepositoryFragments.just(new MyReactiveFragment());
}
@Override
public RepositoryFragments describe(RepositoryMetadata metadata) {
return RepositoryFragments.just(new MyReactiveFragment());
}
}
enum MyOtherReactiveCassandraRepositoryFragmentsContributor implements ReactiveCassandraRepositoryFragmentsContributor {
INSTANCE;
@Override
public RepositoryFragments contribute(RepositoryMetadata metadata,
CassandraEntityInformation<?, ?> entityInformation, ReactiveCassandraOperations operations) {
return RepositoryFragments.just(new MyOtherReactiveFragment());
}
@Override
public RepositoryFragments describe(RepositoryMetadata metadata) {
return RepositoryFragments.just(new MyOtherReactiveFragment());
}
}
static class MyReactiveFragment {
}
static class MyOtherReactiveFragment {
}
interface MyUserRepo extends Repository<User, Long> {}
}