/*
* Copyright 2013-2016 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 lombok.experimental.Delegate;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.annotation.QueryAnnotation;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadataUnitTests.DummyGenericRepositorySupport;
/**
* Unit tests for {@link DefaultRepositoryInformation}.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultRepositoryInformationUnitTests {
@SuppressWarnings("rawtypes") static final Class<DummyGenericRepositorySupport> REPOSITORY = DummyGenericRepositorySupport.class;
@Mock FooRepositoryCustom customImplementation;
@Test
public void discoversRepositoryBaseClassMethod() throws Exception {
Method method = FooRepository.class.getMethod("findById", Integer.class);
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata, REPOSITORY, Optional.empty());
Method reference = information.getTargetClassMethod(method);
assertThat(reference.getDeclaringClass()).isEqualTo(REPOSITORY);
assertThat(reference.getName()).isEqualTo("findById");
}
@Test
public void discoveresNonRepositoryBaseClassMethod() throws Exception {
Method method = FooRepository.class.getMethod("findById", Long.class);
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
assertThat(information.getTargetClassMethod(method)).isEqualTo(method);
}
@Test
public void discoversCustomlyImplementedCrudMethod() throws SecurityException, NoSuchMethodException {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.of(customImplementation.getClass()));
Method source = FooRepositoryCustom.class.getMethod("save", User.class);
Method expected = customImplementation.getClass().getMethod("save", User.class);
assertThat(information.getTargetClassMethod(source)).isEqualTo(expected);
}
@Test
public void considersIntermediateMethodsAsFinderMethods() {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ConcreteRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
assertThat(information.hasCustomMethod()).isFalse();
}
@Test
public void discoversIntermediateMethodsAsBackingMethods() throws NoSuchMethodException, SecurityException {
DefaultRepositoryMetadata metadata = new DefaultRepositoryMetadata(CustomRepository.class);
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata,
PagingAndSortingRepository.class, Optional.empty());
Method method = CustomRepository.class.getMethod("findAll", Pageable.class);
assertThat(information.isBaseClassMethod(method)).isTrue();
method = getMethodFrom(CustomRepository.class, "existsById");
assertThat(information.isBaseClassMethod(method)).isTrue();
assertThat(information.getQueryMethods()).isEmpty();
}
@Test // DATACMNS-151
public void doesNotConsiderManuallyDefinedSaveMethodAQueryMethod() {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(CustomRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, PagingAndSortingRepository.class,
Optional.empty());
assertThat(information.getQueryMethods()).isEmpty();
}
@Test // DATACMNS-151
public void doesNotConsiderRedeclaredSaveMethodAQueryMethod() throws Exception {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ConcreteRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
Method saveMethod = BaseRepository.class.getMethod("save", Object.class);
Method deleteMethod = BaseRepository.class.getMethod("delete", Object.class);
Iterable<Method> queryMethods = information.getQueryMethods();
assertThat(queryMethods).doesNotContain(saveMethod, deleteMethod);
}
@Test
public void onlyReturnsMostConcreteQueryMethod() throws Exception {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ConcreteRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
Method intermediateMethod = BaseRepository.class.getMethod("genericMethodToOverride", String.class);
Method concreteMethod = ConcreteRepository.class.getMethod("genericMethodToOverride", String.class);
Iterable<Method> queryMethods = information.getQueryMethods();
assertThat(queryMethods).contains(concreteMethod);
assertThat(queryMethods).doesNotContain(intermediateMethod);
}
@Test // DATACMNS-193
public void detectsQueryMethodCorrectly() {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ConcreteRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
Method queryMethod = getMethodFrom(ConcreteRepository.class, "findBySomethingDifferent");
assertThat(information.getQueryMethods()).contains(queryMethod);
assertThat(information.isQueryMethod(queryMethod)).isTrue();
}
@Test // DATACMNS-364
public void ignoresCrudMethodsAnnotatedWithQuery() throws Exception {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ConcreteRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
Method method = BaseRepository.class.getMethod("findById", Object.class);
assertThat(information.getQueryMethods()).contains(method);
}
@Test // DATACMNS-385
public void findsTargetSaveForIterableIfEntityImplementsIterable() throws Exception {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(BossRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
Method method = BossRepository.class.getMethod("saveAll", Iterable.class);
Method reference = CrudRepository.class.getMethod("saveAll", Iterable.class);
assertThat(information.getTargetClassMethod(method)).isEqualTo(reference);
}
@Test // DATACMNS-441
public void getQueryShouldNotReturnAnyBridgeMethods() {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(CustomDefaultRepositoryMethodsRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
assertThat(information.getQueryMethods()).allMatch(method -> !method.isBridge());
}
@Test // DATACMNS-854
public void discoversCustomlyImplementedCrudMethodWithGenerics() throws SecurityException, NoSuchMethodException {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.of(customImplementation.getClass()));
Method source = FooRepositoryCustom.class.getMethod("exists", Object.class);
Method expected = customImplementation.getClass().getMethod("exists", Object.class);
assertThat(information.getTargetClassMethod(source)).isEqualTo(expected);
}
@Test // DATACMNS-912
public void discoversCustomlyImplementedCrudMethodWithGenericParameters() throws Exception {
GenericsSaveRepositoryImpl customImplementation = new GenericsSaveRepositoryImpl();
RepositoryMetadata metadata = new DefaultRepositoryMetadata(GenericsSaveRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, RepositoryFactorySupport.class,
Optional.of(customImplementation.getClass()));
Method customBaseRepositoryMethod = GenericsSaveRepository.class.getMethod("save", Object.class);
assertThat(information.isCustomMethod(customBaseRepositoryMethod)).isTrue();
}
@Test // DATACMNS-939
public void ignoresStaticMethod() throws SecurityException, NoSuchMethodException {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.of(customImplementation.getClass()));
Method method = FooRepository.class.getMethod("staticMethod");
assertThat(information.getQueryMethods()).doesNotContain(method);
}
@Test // DATACMNS-939
public void ignoresDefaultMethod() throws SecurityException, NoSuchMethodException {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.of(customImplementation.getClass()));
Method method = FooRepository.class.getMethod("defaultMethod");
assertThat(information.getQueryMethods()).doesNotContain(method);
}
@Test // DATACMNS-943
public void usesCorrectSaveOverload() throws Exception {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(DummyRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, DummyRepositoryImpl.class,
Optional.empty());
Method method = DummyRepository.class.getMethod("saveAll", Iterable.class);
assertThat(information.getTargetClassMethod(method))
.isEqualTo(DummyRepositoryImpl.class.getMethod("saveAll", Iterable.class));
}
@Test // DATACMNS-1008, DATACMNS-912, DATACMNS-854
public void discoversCustomlyImplementedCrudMethodWithoutGenericParameters() throws Exception {
SimpleSaveRepositoryImpl customImplementation = new SimpleSaveRepositoryImpl();
RepositoryMetadata metadata = new DefaultRepositoryMetadata(SimpleSaveRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, RepositoryFactorySupport.class,
Optional.of(customImplementation.getClass()));
Method customBaseRepositoryMethod = SimpleSaveRepository.class.getMethod("save", Object.class);
assertThat(information.isCustomMethod(customBaseRepositoryMethod)).isTrue();
}
private static Method getMethodFrom(Class<?> type, String name) {
return Arrays.stream(type.getMethods())//
.filter(method -> method.getName().equals(name))//
.findFirst()//
.orElseThrow(() -> new IllegalStateException("No method found with name ".concat(name).concat("!")));
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@QueryAnnotation
@interface MyQuery {
}
interface FooRepository extends CrudRepository<User, Integer>, FooRepositoryCustom {
// Redeclared method
Optional<User> findById(Integer primaryKey);
// Not a redeclared method
User findById(Long primaryKey);
static void staticMethod() {}
default void defaultMethod() {}
}
interface FooSuperInterfaceWithGenerics<T> {
boolean exists(T id);
}
interface FooRepositoryCustom extends FooSuperInterfaceWithGenerics<User> {
User save(User user);
}
@SuppressWarnings("unused")
private class User {
private String firstname;
public String getAddress() {
return null;
}
}
static class Boss implements Iterable<User> {
@Override
public Iterator<User> iterator() {
return Collections.<User> emptySet().iterator();
}
}
interface BaseRepository<S, ID> extends CrudRepository<S, ID> {
S findBySomething(String something);
S genericMethodToOverride(String something);
<K extends S> K save(K entity);
void delete(S entity);
@MyQuery
Optional<S> findById(ID id);
}
interface ConcreteRepository extends BaseRepository<User, Integer> {
User findBySomethingDifferent(String somethingDifferent);
User genericMethodToOverride(String something);
}
interface ReadOnlyRepository<T, ID> extends Repository<T, ID> {
T findById(ID id);
Iterable<T> findAll();
Page<T> findAll(Pageable pageable);
List<T> findAll(Sort sort);
boolean existsById(ID id);
long count();
}
interface CustomRepository extends ReadOnlyRepository<Object, Long> {
Object save(Object object);
}
interface BossRepository extends CrudRepository<Boss, Long> {}
interface CustomDefaultRepositoryMethodsRepository extends CrudRepository<User, Integer> {
@MyQuery
List<User> findAll();
}
// DATACMNS-854, DATACMNS-912
interface GenericsSaveRepository extends CrudRepository<Sample, Long> {}
static class GenericsSaveRepositoryImpl {
public <T extends Sample> T save(T entity) {
return entity;
}
}
static class Sample {}
interface DummyRepository extends CrudRepository<User, Integer> {
@Override
<S extends User> List<S> saveAll(Iterable<S> entites);
}
static class DummyRepositoryImpl<T, ID> implements CrudRepository<T, ID> {
private @Delegate CrudRepository<T, ID> delegate;
}
// DATACMNS-1008, DATACMNS-854, DATACMNS-912
interface SimpleSaveRepository extends CrudRepository<Sample, Long> {}
static class SimpleSaveRepositoryImpl {
public Sample save(Sample entity) {
return entity;
}
}
}