Skip to content
This repository was archived by the owner on Feb 23, 2023. It is now read-only.

Commit a813865

Browse files
christophstroblsdeleuze
authored andcommitted
Fix Spring Data custom repository fragment lookup.
Repository fragments following the pattern FragmentInterface | FragmentInterfaceImpl did not get discovered and registered correctly though annotated with @component. This commit introduces a Substitution for the CustomRepositoryImplementationDetector using the CandidateComponentsIndex directly. It also adds the missing type hint for the RepositoryFragment used to create the target repository and updates the data-elasticsearch example to cover the custom repository setup. The alternative setup using the heritage configuration format (used in the data-mongodb) still works.
1 parent 5821609 commit a813865

File tree

6 files changed

+175
-1
lines changed

6 files changed

+175
-1
lines changed

samples/data-elasticsearch/src/main/java/com/example/data/elasticsearch/CLR.java

+9
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020

2121
import org.springframework.beans.factory.annotation.Autowired;
2222
import org.springframework.boot.CommandLineRunner;
23+
import org.springframework.data.domain.PageRequest;
2324
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
2425
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
2526
import org.springframework.data.elasticsearch.core.SearchHits;
27+
import org.springframework.data.elasticsearch.core.SearchPage;
2628
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
2729
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
2830
import org.springframework.data.elasticsearch.core.query.Criteria;
@@ -72,6 +74,13 @@ public void run(String... args) throws Exception {
7274
System.out.println("repository.count(): " + repository.count());
7375
}
7476

77+
{
78+
System.out.println("\n--- CUSTOM REPOSITORY ---");
79+
80+
SearchPage<Conference> searchPage = repository.findBySomeCustomImplementation("eXchange", PageRequest.of(0, 10));
81+
System.out.println("custom implementation finder.size(): " + searchPage.getSearchHits().getTotalHits());
82+
}
83+
7584
String expectedDate = "2014-10-29";
7685
String expectedWord = "java";
7786
CriteriaQuery query = new CriteriaQuery(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.example.data.elasticsearch;
17+
18+
import org.springframework.data.domain.Pageable;
19+
import org.springframework.data.elasticsearch.core.SearchPage;
20+
21+
public interface ConferenceCustomRepository {
22+
23+
SearchPage<Conference> findBySomeCustomImplementation(String name, Pageable page);
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.example.data.elasticsearch;
17+
18+
import org.elasticsearch.index.query.QueryBuilders;
19+
import org.springframework.data.domain.Pageable;
20+
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
21+
import org.springframework.data.elasticsearch.core.SearchHitSupport;
22+
import org.springframework.data.elasticsearch.core.SearchHits;
23+
import org.springframework.data.elasticsearch.core.SearchPage;
24+
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
25+
import org.springframework.data.elasticsearch.core.query.Query;
26+
import org.springframework.stereotype.Component;
27+
import org.springframework.stereotype.Indexed;
28+
29+
@Indexed
30+
@Component // required for spring.components
31+
public class ConferenceCustomRepositoryImpl implements ConferenceCustomRepository {
32+
33+
private final ElasticsearchOperations operations;
34+
35+
public ConferenceCustomRepositoryImpl(ElasticsearchOperations operations) {
36+
this.operations = operations;
37+
}
38+
39+
@Override
40+
public SearchPage<Conference> findBySomeCustomImplementation(String name, Pageable page) {
41+
42+
Query query = new NativeSearchQueryBuilder()
43+
.withQuery(QueryBuilders.matchQuery("name", name))
44+
.build();
45+
46+
SearchHits<Conference> searchHits = operations.search(query, Conference.class);
47+
return SearchHitSupport.searchPageFor(searchHits, page);
48+
}
49+
}

samples/data-elasticsearch/src/main/java/com/example/data/elasticsearch/ConferenceRepository.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
2121

22-
interface ConferenceRepository extends ElasticsearchRepository<Conference, String> {
22+
interface ConferenceRepository extends ElasticsearchRepository<Conference, String>, ConferenceCustomRepository {
2323

2424
List<Conference> findByKeywordsContaining(String keyword);
2525
}

spring-native-configuration/src/main/java/org/springframework/data/SpringDataCommonsHints.java

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.data.repository.core.RepositoryMetadata;
2828
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
2929
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
30+
import org.springframework.data.repository.core.support.RepositoryFragment;
3031
import org.springframework.data.repository.core.support.RepositoryFragmentsFactoryBean;
3132
import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport;
3233
import org.springframework.data.repository.query.QueryByExampleExecutor;
@@ -43,6 +44,7 @@
4344
@TypeHint(types = {
4445
RepositoryFactoryBeanSupport.class,
4546
RepositoryFragmentsFactoryBean.class,
47+
RepositoryFragment.class,
4648
TransactionalRepositoryFactoryBeanSupport.class,
4749
QueryByExampleExecutor.class,
4850
MappingContext.class,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.nativex.substitutions.data;
17+
18+
import java.io.IOException;
19+
import java.util.LinkedHashSet;
20+
import java.util.Set;
21+
import java.util.function.Function;
22+
import java.util.stream.Collectors;
23+
import java.util.stream.Stream;
24+
25+
import com.oracle.svm.core.annotate.Substitute;
26+
import com.oracle.svm.core.annotate.TargetClass;
27+
import org.springframework.beans.factory.BeanDefinitionStoreException;
28+
import org.springframework.beans.factory.config.BeanDefinition;
29+
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
30+
import org.springframework.context.index.CandidateComponentsIndex;
31+
import org.springframework.context.index.CandidateComponentsIndexLoader;
32+
import org.springframework.core.type.classreading.MetadataReader;
33+
import org.springframework.data.repository.config.ImplementationDetectionConfiguration;
34+
import org.springframework.nativex.substitutions.OnlyIfPresent;
35+
import org.springframework.stereotype.Component;
36+
37+
38+
@TargetClass(className = "org.springframework.data.repository.config.CustomRepositoryImplementationDetector", onlyWith = {OnlyIfPresent.class})
39+
public final class Target_CustomRepositoryImplementationDetector {
40+
41+
@Substitute
42+
private Set<BeanDefinition> findCandidateBeanDefinitions(ImplementationDetectionConfiguration config) {
43+
44+
/* Using the index instead of ClassPathScanningCandidateComponentProvider with pattern.
45+
* Not sure why components are not found via the index as it should be configured on
46+
* `setResourceLoader` within ClassPathScanningCandidateComponentProvider.
47+
*
48+
* ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false, environment);
49+
*
50+
* provider.setResourceLoader(resourceLoader);
51+
* provider.setResourcePattern(String.format(CUSTOM_IMPLEMENTATION_RESOURCE_PATTERN, postfix));
52+
* provider.setMetadataReaderFactory(config.getMetadataReaderFactory());
53+
* provider.addIncludeFilter((reader, factory) -> true);
54+
*/
55+
CandidateComponentsIndex index = CandidateComponentsIndexLoader.loadIndex(config.getClass().getClassLoader());
56+
57+
return config.getBasePackages().stream()
58+
59+
.flatMap(new Function<String, Stream<? extends BeanDefinition>>() { // see oracle/graal#2479
60+
61+
@Override
62+
public Stream<? extends BeanDefinition> apply(String basePackage) {
63+
64+
Set<String> candidateTypes = index.getCandidateTypes(basePackage, Component.class.getName());
65+
if (candidateTypes.isEmpty()) {
66+
return Stream.empty();
67+
}
68+
69+
Set<BeanDefinition> beanDefinitions = new LinkedHashSet<>();
70+
for (String candidate : candidateTypes) {
71+
if (candidate.endsWith(config.getImplementationPostfix())) {
72+
73+
try {
74+
75+
MetadataReader metadataReader = config.getMetadataReaderFactory().getMetadataReader(candidate);
76+
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
77+
sbd.setResource(metadataReader.getResource());
78+
beanDefinitions.add(sbd);
79+
} catch (IOException ex) {
80+
throw new BeanDefinitionStoreException(String.format("Failure while reading metadata for %s.", candidate), ex);
81+
}
82+
}
83+
}
84+
85+
return beanDefinitions.stream();
86+
}
87+
}
88+
).collect(Collectors.toSet());
89+
}
90+
}

0 commit comments

Comments
 (0)