From 142c9ddb5c866a7f7a652cc1da7caa0c895861d3 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 5 Apr 2018 15:27:50 +0200 Subject: [PATCH] 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) 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. --- .../core/support/RepositoryFragment.java | 21 ++++++-- .../support/RepositoryFragmentUnitTests.java | 48 +++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 src/test/java/org/springframework/data/repository/core/support/RepositoryFragmentUnitTests.java diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFragment.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFragment.java index 66fd51e98..51a664e61 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFragment.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFragment.java @@ -155,12 +155,27 @@ public interface RepositoryFragment { } } - @RequiredArgsConstructor @EqualsAndHashCode(callSuper = false) static class ImplementedRepositoryFragment implements RepositoryFragment { - private final @NonNull Optional> interfaceClass; - private final @NonNull T implementation; + private final Optional> interfaceClass; + private final T implementation; + + public ImplementedRepositoryFragment(Optional> 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) diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryFragmentUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryFragmentUnitTests.java new file mode 100644 index 000000000..976877e22 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryFragmentUnitTests.java @@ -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 {} +}