Skip to content

Add support for RepositoryFragmentsContributor. #3011

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 8.0.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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.neo4j.repository.support;

import static org.springframework.data.querydsl.QuerydslUtils.*;

import org.springframework.data.neo4j.core.Neo4jOperations;
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.repository.query.CypherdslConditionExecutorImpl;
import org.springframework.data.neo4j.repository.query.QuerydslNeo4jPredicateExecutor;
import org.springframework.data.neo4j.repository.query.SimpleQueryByExampleExecutor;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.core.support.RepositoryFragmentsContributor;

/**
* Built-in {@link RepositoryFragmentsContributor} contributing Query by Example, Querydsl, and Cypher condition
* fragments if a repository implements the corresponding interfaces.
*
* @author Mark Paluch
* @since 8.0
*/
enum BuiltinContributor implements Neo4jRepositoryFragmentsContributor {

INSTANCE;

@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public RepositoryFragments contribute(RepositoryMetadata metadata, Neo4jEntityInformation<?, ?> entityInformation,
Neo4jOperations operations, Neo4jMappingContext mappingContext) {

RepositoryFragments fragments = RepositoryFragments
.of(RepositoryFragment.implemented(new SimpleQueryByExampleExecutor(operations, mappingContext)));

if (isQuerydslRepository(metadata)) {
fragments = fragments.append(RepositoryFragment
.implemented(new QuerydslNeo4jPredicateExecutor(mappingContext, entityInformation, operations)));
}

if (CypherdslConditionExecutor.class.isAssignableFrom(metadata.getRepositoryInterface())) {
fragments = fragments
.append(RepositoryFragment.implemented(new CypherdslConditionExecutorImpl(entityInformation, operations)));
}

return fragments;
}

@Override
public RepositoryFragments describe(RepositoryMetadata metadata) {

RepositoryFragments fragments = RepositoryFragments
.of(RepositoryFragment.structural(SimpleQueryByExampleExecutor.class));

if (isQuerydslRepository(metadata)) {
fragments = fragments.append(RepositoryFragment.structural(QuerydslNeo4jPredicateExecutor.class));
}

if (CypherdslConditionExecutor.class.isAssignableFrom(metadata.getRepositoryInterface())) {
fragments = fragments.append(RepositoryFragment.structural(CypherdslConditionExecutorImpl.class));
}

return fragments;
}

private static boolean isQuerydslRepository(RepositoryMetadata metadata) {
return QUERY_DSL_PRESENT && QuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,13 @@
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.CypherdslConditionExecutorImpl;
import org.springframework.data.neo4j.repository.query.Neo4jQueryLookupStrategy;
import org.springframework.data.neo4j.repository.query.QuerydslNeo4jPredicateExecutor;
import org.springframework.data.neo4j.repository.query.SimpleQueryByExampleExecutor;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.QuerydslUtils;
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.core.support.RepositoryFragment;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.ValueExpressionDelegate;
Expand All @@ -46,6 +40,7 @@
*
* @author Gerrit Meier
* @author Michael J. Simons
* @author Mark Paluch
* @since 6.0
*/
final class Neo4jRepositoryFactory extends RepositoryFactorySupport {
Expand All @@ -54,12 +49,15 @@ final class Neo4jRepositoryFactory extends RepositoryFactorySupport {

private final Neo4jMappingContext mappingContext;

private final Neo4jRepositoryFragmentsContributor fragmentsContributor;

private Configuration cypherDSLConfiguration = Configuration.defaultConfig();

Neo4jRepositoryFactory(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext) {
Neo4jRepositoryFactory(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext, Neo4jRepositoryFragmentsContributor fragmentsContributor) {

this.neo4jOperations = neo4jOperations;
this.mappingContext = mappingContext;
this.fragmentsContributor = fragmentsContributor;
}

@Override
Expand All @@ -79,44 +77,7 @@ protected Object getTargetRepository(RepositoryInformation metadata) {

@Override
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {

RepositoryFragments fragments = RepositoryFragments.empty();

Object byExampleExecutor = instantiateClass(SimpleQueryByExampleExecutor.class, neo4jOperations,
mappingContext);

fragments = fragments.append(RepositoryFragment.implemented(byExampleExecutor));

boolean isQueryDslRepository = QuerydslUtils.QUERY_DSL_PRESENT
&& QuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface());

if (isQueryDslRepository) {

fragments = fragments.append(createDSLPredicateExecutorFragment(metadata, QuerydslNeo4jPredicateExecutor.class));
}

if (CypherdslConditionExecutor.class.isAssignableFrom(metadata.getRepositoryInterface())) {

fragments = fragments.append(createDSLExecutorFragment(metadata, CypherdslConditionExecutorImpl.class));
}

return fragments;
}

private RepositoryFragment<Object> createDSLPredicateExecutorFragment(RepositoryMetadata metadata, Class<?> implementor) {

Neo4jEntityInformation<?, ?> entityInformation = getEntityInformation(metadata);
Object querydslFragment = instantiateClass(implementor, mappingContext, entityInformation, neo4jOperations);

return RepositoryFragment.implemented(querydslFragment);
}

private RepositoryFragment<Object> createDSLExecutorFragment(RepositoryMetadata metadata, Class<?> implementor) {

Neo4jEntityInformation<?, ?> entityInformation = getEntityInformation(metadata);
Object querydslFragment = instantiateClass(implementor, entityInformation, neo4jOperations);

return RepositoryFragment.implemented(querydslFragment);
return fragmentsContributor.contribute(metadata, getEntityInformation(metadata), neo4jOperations, mappingContext);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
*
* @author Michael J. Simons
* @author Gerrit Meier
* @author Mark Paluch
* @param <T> the type of the repository
* @param <S> type of the domain class to map
* @param <ID> identifier type in the domain class
Expand All @@ -43,6 +44,8 @@ public final class Neo4jRepositoryFactoryBean<T extends Repository<S, ID>, S, ID

private Neo4jMappingContext neo4jMappingContext;

private Neo4jRepositoryFragmentsContributor repositoryFragmentsContributor = Neo4jRepositoryFragmentsContributor.DEFAULT;

/**
* Creates a new {@link TransactionalRepositoryFactoryBeanSupport} for the given repository interface.
*
Expand All @@ -61,8 +64,17 @@ public void setNeo4jMappingContext(Neo4jMappingContext neo4jMappingContext) {
this.neo4jMappingContext = neo4jMappingContext;
}

@Override
public Neo4jRepositoryFragmentsContributor getRepositoryFragmentsContributor() {
return repositoryFragmentsContributor;
}

public void setRepositoryFragmentsContributor(Neo4jRepositoryFragmentsContributor repositoryFragmentsContributor) {
this.repositoryFragmentsContributor = repositoryFragmentsContributor;
}

@Override
protected RepositoryFactorySupport doCreateRepositoryFactory() {
return new Neo4jRepositoryFactory(neo4jOperations, neo4jMappingContext);
return new Neo4jRepositoryFactory(neo4jOperations, neo4jMappingContext, repositoryFragmentsContributor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
* The CDI pendant to the {@link Neo4jRepositoryFactoryBean}. It creates instances of {@link Neo4jRepositoryFactory}.
*
* @author Michael J. Simons
* @author Mark Paluch
* @param <T> The type of the repository being created
* @soundtrack Various - TRON Legacy R3conf1gur3d
* @since 6.0
Expand All @@ -57,7 +58,7 @@ protected T create(CreationalContext<T> creationalContext, Class<T> repositoryTy
Neo4jOperations neo4jOperations = getReference(Neo4jOperations.class, creationalContext);
Neo4jMappingContext mappingContext = getReference(Neo4jMappingContext.class, creationalContext);

return create(() -> new Neo4jRepositoryFactory(neo4jOperations, mappingContext), repositoryType);
return create(() -> new Neo4jRepositoryFactory(neo4jOperations, mappingContext, Neo4jRepositoryFragmentsContributor.DEFAULT), repositoryType);
}

private <RT> RT getReference(Class<RT> clazz, CreationalContext<?> creationalContext) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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.neo4j.repository.support;

import org.springframework.data.neo4j.core.Neo4jOperations;
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
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;

/**
* Neo4j-specific {@link RepositoryFragmentsContributor} contributing fragments based on the repository. Typically,
* contributes Query by Example Executor, Querydsl, and Cypher condition DSL fragments.
* <p>
* Implementations must define a no-args constructor.
*
* @author Mark Paluch
* @since 8.0
* @see org.springframework.data.repository.query.QueryByExampleExecutor
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor
* @see CypherdslConditionExecutor
*/
public interface Neo4jRepositoryFragmentsContributor extends RepositoryFragmentsContributor {

Neo4jRepositoryFragmentsContributor DEFAULT = BuiltinContributor.INSTANCE;

/**
* Returns a composed {@code Neo4jRepositoryFragmentsContributor} 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 Neo4jRepositoryFragmentsContributor andThen(Neo4jRepositoryFragmentsContributor after) {

Assert.notNull(after, "Neo4jRepositoryFragmentsContributor must not be null");

return new Neo4jRepositoryFragmentsContributor() {

@Override
public RepositoryComposition.RepositoryFragments contribute(RepositoryMetadata metadata,
Neo4jEntityInformation<?, ?> entityInformation, Neo4jOperations operations,
Neo4jMappingContext mappingContext) {
return Neo4jRepositoryFragmentsContributor.this
.contribute(metadata, entityInformation, operations, mappingContext)
.append(after.contribute(metadata, entityInformation, operations, mappingContext));
}

@Override
public RepositoryComposition.RepositoryFragments describe(RepositoryMetadata metadata) {
return Neo4jRepositoryFragmentsContributor.this.describe(metadata).append(after.describe(metadata));
}
};
}

/**
* Creates {@link RepositoryComposition.RepositoryFragments} based on {@link RepositoryMetadata} to add Neo4j-specific
* extensions.
*
* @param metadata repository metadata.
* @param entityInformation must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
* @return {@link RepositoryComposition.RepositoryFragments} to be added to the repository.
*/
RepositoryComposition.RepositoryFragments contribute(RepositoryMetadata metadata,
Neo4jEntityInformation<?, ?> entityInformation, Neo4jOperations operations, Neo4jMappingContext mappingContext);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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.neo4j.repository.support;

import static org.springframework.data.querydsl.QuerydslUtils.*;

import org.springframework.data.neo4j.core.ReactiveNeo4jOperations;
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.repository.query.ReactiveCypherdslConditionExecutorImpl;
import org.springframework.data.neo4j.repository.query.ReactiveQuerydslNeo4jPredicateExecutor;
import org.springframework.data.neo4j.repository.query.SimpleReactiveQueryByExampleExecutor;
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryFragment;

/**
* Reactive Built-in {@link ReactiveNeo4jRepositoryFragmentsContributor} contributing Query by Example, Querydsl, and
* Cypher condition fragments if a repository implements the corresponding interfaces.
*
* @author Mark Paluch
* @since 8.0
*/
enum ReactiveBuiltinContributor implements ReactiveNeo4jRepositoryFragmentsContributor {

INSTANCE;

@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public RepositoryFragments contribute(RepositoryMetadata metadata, Neo4jEntityInformation<?, ?> entityInformation,
ReactiveNeo4jOperations operations, Neo4jMappingContext mappingContext) {

RepositoryFragments fragments = RepositoryFragments
.of(RepositoryFragment.implemented(new SimpleReactiveQueryByExampleExecutor(operations, mappingContext)));

if (isQuerydslRepository(metadata)) {
fragments = fragments.append(RepositoryFragment
.implemented(new ReactiveQuerydslNeo4jPredicateExecutor(mappingContext, entityInformation, operations)));
}

if (ReactiveCypherdslConditionExecutor.class.isAssignableFrom(metadata.getRepositoryInterface())) {
fragments = fragments.append(
RepositoryFragment.implemented(new ReactiveCypherdslConditionExecutorImpl(entityInformation, operations)));
}

return fragments;
}

@Override
public RepositoryFragments describe(RepositoryMetadata metadata) {

RepositoryFragments fragments = RepositoryFragments
.of(RepositoryFragment.structural(SimpleReactiveQueryByExampleExecutor.class));

if (isQuerydslRepository(metadata)) {
fragments = fragments.append(RepositoryFragment.structural(ReactiveQuerydslNeo4jPredicateExecutor.class));
}

if (ReactiveCypherdslConditionExecutor.class.isAssignableFrom(metadata.getRepositoryInterface())) {
fragments = fragments.append(RepositoryFragment.structural(ReactiveCypherdslConditionExecutorImpl.class));
}

return fragments;
}

private static boolean isQuerydslRepository(RepositoryMetadata metadata) {
return QUERY_DSL_PRESENT
&& ReactiveQuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface());
}
}
Loading