DATACMNS-1289 - Allow fragment creation only from implementations that implement their declared class.

We now check that fragment implementations created via RepositoryFragment.implemented(Class<T>, T) are a subtype of the given class. This assertion raises an exception that prevents errors during runtime.

This change addresses an issue with customized intermediate base repositories in combination with ambiguous naming of implementations. Method invocations fail if a repository derives from a customized base repository interface that implements e.g. CrudRepository and there's an implementation matching the base repository name followed by the implementation suffix. We assume in that case, the implementation contains implementations of the methods declared in the customized base interface.

Original pull request: #280.
This commit is contained in:
Mark Paluch
2018-04-05 15:27:50 +02:00
committed by Oliver Gierke
parent 0e772a560b
commit 532af1a6b9
2 changed files with 66 additions and 3 deletions

View File

@@ -155,12 +155,27 @@ public interface RepositoryFragment<T> {
}
}
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = false)
static class ImplementedRepositoryFragment<T> implements RepositoryFragment<T> {
private final @NonNull Optional<Class<T>> interfaceClass;
private final @NonNull T implementation;
private final Optional<Class<T>> interfaceClass;
private final T implementation;
public ImplementedRepositoryFragment(Optional<Class<T>> interfaceClass, T implementation) {
Assert.notNull(interfaceClass, "Interface class must not be null!");
Assert.notNull(implementation, "Implementation object must not be null!");
interfaceClass.ifPresent(it -> {
Assert.isTrue(ClassUtils.isAssignableValue(it, implementation),
() -> String.format("Fragment implementation %s does not implement %s!", ClassUtils.getQualifiedName(it),
ClassUtils.getQualifiedName(implementation.getClass())));
});
this.interfaceClass = interfaceClass;
this.implementation = implementation;
}
/*
* (non-Javadoc)

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2018 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
*
* http://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.core.support;
import static org.assertj.core.api.Assertions.*;
import org.junit.Test;
/**
* Unit tests for {@link RepositoryFragment}.
*
* @author Mark Paluch
*/
public class RepositoryFragmentUnitTests {
@SuppressWarnings("unchecked")
@Test // DATACMNS-1289
public void fragmentCreationFromUnrelatedTypesShouldFail() {
assertThatThrownBy(() -> RepositoryFragment.implemented((Class) CustomFragment.class, new UnrelatedImpl()))
.hasMessageMatching("Fragment implementation .* does not implement .*UnrelatedImpl!")
.isInstanceOf(IllegalArgumentException.class);
}
@Test // DATACMNS-1289
public void fragmentCreationFromRelatedTypesShouldCreateNewFragment() {
assertThat(RepositoryFragment.implemented(CustomFragment.class, new CustomFragmentImpl())).isNotNull();
}
interface CustomFragment {}
private static class CustomFragmentImpl implements CustomFragment {}
private static class UnrelatedImpl {}
}