DATACMNS-1754 - Support implementation lookup for nested repositories and fragments.

Problem: while repository and fragment interfaces are discovered
correctly (provided that considerNestedRepositories=true), their
implementations are not found/registered despite proper class naming.

Cause: DefaultImplementationLookupConfiguration works in such way that
for nested interface (i.e. one whose class name includes name of the
enclosing class) expected implementation class name is built including
that enclosing class name. In the same time actual implementation class
names are always "localized" (i.e. stripped from enclosing class name,
if any) before matching. This makes matching implementation classes
against nested interface impossible.

Solution: when building expected implementation class name, use "local"
interface class name, so that it can match any implementation class that
follows naming convention "SimpleInterfaceName" + "ImplemenetationPostfix".

Original pull request: #460.
This commit is contained in:
Kyrylo Merzlikin
2020-07-18 18:49:36 +03:00
committed by Mark Paluch
parent c99f2c9459
commit 2a44d2394c
7 changed files with 125 additions and 11 deletions

View File

@@ -37,7 +37,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.config.CustomRepositoryImplementationDetector;
@@ -58,6 +57,7 @@ import org.springframework.data.repository.query.RepositoryQuery;
* @author Oliver Gierke
* @author Mark Paluch
* @author Ariel Carrera
* @author Kyrylo Merzlikin
*/
@ExtendWith(MockitoExtension.class)
class CdiRepositoryBeanUnitTests {
@@ -171,8 +171,8 @@ class CdiRepositoryBeanUnitTests {
ImplementationLookupConfiguration configuration = captor.getValue();
assertThat(configuration.getImplementationBeanName()).isEqualTo("cdiRepositoryBeanUnitTests.SampleRepositoryImpl");
assertThat(configuration.getImplementationClassName()).isEqualTo("CdiRepositoryBeanUnitTests.SampleRepositoryImpl");
assertThat(configuration.getImplementationBeanName()).isEqualTo("sampleRepositoryImpl");
assertThat(configuration.getImplementationClassName()).isEqualTo("SampleRepositoryImpl");
}
@Test // DATACMNS-1233

View File

@@ -21,9 +21,10 @@ import org.springframework.data.repository.Repository;
/**
* @author Mark Paluch
* @author Kyrylo Merzlikin
*/
interface ComposedRepository
extends Repository<Object, Serializable>, FragmentInterface, AnotherFragmentInterface {
interface ComposedRepository extends Repository<Object, Serializable>, FragmentInterface, AnotherFragmentInterface,
RepositoryFragments.NestedFragmentInterface {
// duplicate method shadowed by AnotherFragmentInterfaceImpl. The legacy custom implementation comes last, after all
// other fragments.

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.cdi;
/**
* @author Kyrylo Merzlikin
*/
public class NestedFragmentInterfaceImpl implements RepositoryFragments.NestedFragmentInterface {
@Override
public String getKey() {
return "NestedFragmentImpl";
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.cdi;
/**
* @author Kyrylo Merzlikin
*/
public interface RepositoryFragments {
interface NestedFragmentInterface {
String getKey();
}
}

View File

@@ -17,17 +17,21 @@ package org.springframework.data.repository.cdi;
import static org.assertj.core.api.Assertions.*;
import java.io.Serializable;
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.data.repository.Repository;
/**
* CDI integration tests for composed repositories.
*
*
* @author Mark Paluch
* @author Kyrylo Merzlikin
*/
class RepositoryFragmentsIntegrationTests {
@@ -66,6 +70,24 @@ class RepositoryFragmentsIntegrationTests {
assertThat(shadowed.getPriority()).isEqualTo(2);
}
@Test // DATACMNS-1754
void shouldFindCustomImplementationForNestedRepository() {
NestedRepository repository = getBean(NestedRepository.class);
assertThat(repository.getCustom()).isEqualTo("CustomImpl");
}
@Test // DATACMNS-1754
void shouldFindImplementationForNestedRepositoryFragment() {
ComposedRepository repository = getBean(ComposedRepository.class);
NestedFragmentInterfaceImpl fragment = getBean(NestedFragmentInterfaceImpl.class);
assertThat(repository.getKey()).isEqualTo("NestedFragmentImpl");
assertThat(fragment.getKey()).isEqualTo("NestedFragmentImpl");
}
protected <T> T getBean(Class<T> type) {
return container.select(type).get();
}
@@ -74,4 +96,23 @@ class RepositoryFragmentsIntegrationTests {
static void tearDown() {
container.close();
}
public interface NestedRepository extends Repository<Object, Serializable> {
String getCustom();
}
public interface NestedRepositoryCustom {
// duplicate method allowing to provide custom implementation (without modifying original repository)
String getCustom();
}
public static class NestedRepositoryImpl implements NestedRepositoryCustom {
@Override
public String getCustom() {
return "CustomImpl";
}
}
}

View File

@@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test;
* Unit tests for {@link DefaultImplementationLookupConfigurationUnitTests}.
*
* @author Mark Paluch
* @author Kyrylo Merzlikin
*/
class DefaultImplementationLookupConfigurationUnitTests {
@@ -37,6 +38,18 @@ class DefaultImplementationLookupConfigurationUnitTests {
assertThat(getImplementationBeanName(idcMock, "com.acme.UdpRepository")).isEqualTo("udpRepositoryImpl");
}
@Test // DATACMNS-1754
void shouldUseSimpleClassNameWhenDefiningImplementationNames() {
ImplementationDetectionConfiguration idcMock = mock(ImplementationDetectionConfiguration.class);
when(idcMock.getImplementationPostfix()).thenReturn("Impl");
DefaultImplementationLookupConfiguration lookupConfiguration = new DefaultImplementationLookupConfiguration(idcMock,
"com.acme.Repositories$NestedRepository");
assertThat(lookupConfiguration.getImplementationBeanName()).isEqualTo("nestedRepositoryImpl");
assertThat(lookupConfiguration.getImplementationClassName()).isEqualTo("NestedRepositoryImpl");
}
private static String getImplementationBeanName(ImplementationDetectionConfiguration idcMock, String interfaceName) {
DefaultImplementationLookupConfiguration configuration = new DefaultImplementationLookupConfiguration(idcMock,