/*
* Copyright 2015-2017 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.query;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import io.reactivex.Flowable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Observable;
import rx.Single;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
/**
* Unit tests for {@link ResultProcessor}.
*
* @author Oliver Gierke
* @author Mark Paluch
* @author Christoph Strobl
*/
public class ResultProcessorUnitTests {
@Test // DATACMNS-89
public void leavesNonProjectingResultUntouched() throws Exception {
ResultProcessor information = new ResultProcessor(getQueryMethod("findAll"), new SpelAwareProxyProjectionFactory());
Sample sample = new Sample("Dave", "Matthews");
List<Sample> result = new ArrayList<>(Collections.singletonList(sample));
List<Sample> converted = information.processResult(result);
assertThat(converted).contains(sample);
}
@Test // DATACMNS-89
public void createsProjectionFromProperties() throws Exception {
ResultProcessor information = getProcessor("findOneProjection");
SampleProjection result = information.processResult(Collections.singletonList("Matthews"));
assertThat(result.getLastname()).isEqualTo("Matthews");
}
@Test // DATACMNS-89
public void createsListOfProjectionsFormNestedLists() throws Exception {
ResultProcessor information = getProcessor("findAllProjection");
List<String> columns = Collections.singletonList("Matthews");
List<List<String>> source = new ArrayList<>(Collections.singletonList(columns));
List<SampleProjection> result = information.processResult(source);
assertThat(result).extracting(SampleProjection::getLastname).containsExactly("Matthews");
}
@Test // DATACMNS-89
@SuppressWarnings("unchecked")
public void createsListOfProjectionsFromMaps() throws Exception {
ResultProcessor information = getProcessor("findAllProjection");
List<Map<String, Object>> source = new ArrayList<>(
Collections.singletonList(Collections.singletonMap("lastname", "Matthews")));
List<SampleProjection> result = information.processResult(source);
assertThat(result).hasSize(1);
assertThat(result.get(0).getLastname()).isEqualTo("Matthews");
}
@Test // DATACMNS-89
public void createsListOfProjectionsFromEntity() throws Exception {
ResultProcessor information = getProcessor("findAllProjection");
List<Sample> source = new ArrayList<>(Collections.singletonList(new Sample("Dave", "Matthews")));
List<SampleProjection> result = information.processResult(source);
assertThat(result).hasSize(1);
assertThat(result.get(0).getLastname()).isEqualTo("Matthews");
}
@Test // DATACMNS-89
public void createsPageOfProjectionsFromEntity() throws Exception {
ResultProcessor information = getProcessor("findPageProjection", Pageable.class);
Page<Sample> source = new PageImpl<>(Collections.singletonList(new Sample("Dave", "Matthews")));
Page<SampleProjection> result = information.processResult(source);
assertThat(result.getContent()).hasSize(1);
assertThat(result.getContent().get(0).getLastname()).isEqualTo("Matthews");
}
@Test // DATACMNS-89
public void createsDynamicProjectionFromEntity() throws Exception {
ResultProcessor information = getProcessor("findOneOpenProjection");
OpenProjection result = information.processResult(new Sample("Dave", "Matthews"));
assertThat(result.getLastname()).isEqualTo("Matthews");
assertThat(result.getFullName()).isEqualTo("Dave Matthews");
}
@Test // DATACMNS-89
public void findsDynamicProjection() throws Exception {
ParameterAccessor accessor = mock(ParameterAccessor.class);
ResultProcessor factory = getProcessor("findOneDynamic", Class.class);
assertThat(factory.withDynamicProjection(accessor)).isEqualTo(factory);
doReturn(Optional.of(SampleProjection.class)).when(accessor).getDynamicProjection();
ResultProcessor processor = factory.withDynamicProjection(accessor);
assertThat(processor.getReturnedType().getReturnedType()).isEqualTo(SampleProjection.class);
}
@Test // DATACMNS-89
public void refrainsFromProjectingIfThePreparingConverterReturnsACompatibleInstance() throws Exception {
Object result = getProcessor("findAllDtos").processResult(new Sample("Dave", "Matthews"),
source -> new SampleDto());
assertThat(result).isInstanceOf(SampleDto.class);
}
@Test // DATACMNS-828
public void returnsNullResultAsIs() throws Exception {
Object result = getProcessor("findOneDto").processResult(null);
assertThat(result).isNull();
}
@Test // DATACMNS-842
public void supportsSlicesAsReturnWrapper() throws Exception {
Slice<Sample> slice = new SliceImpl<>(Collections.singletonList(new Sample("Dave", "Matthews")));
Object result = getProcessor("findSliceProjection", Pageable.class).processResult(slice);
assertThat(result).isInstanceOf(Slice.class);
List<?> content = ((Slice<?>) result).getContent();
assertThat(content).hasSize(1).hasOnlyElementsOfType(SampleProjection.class);
}
@Test // DATACMNS-859
@SuppressWarnings("unchecked")
public void supportsStreamAsReturnWrapper() throws Exception {
Stream<Sample> samples = Collections.singletonList(new Sample("Dave", "Matthews")).stream();
Object result = getProcessor("findStreamProjection").processResult(samples);
assertThat(result).isInstanceOf(Stream.class);
List<Object> content = ((Stream<Object>) result).collect(Collectors.toList());
assertThat(content).hasSize(1).hasOnlyElementsOfType(SampleProjection.class);
}
@Test // DATACMNS-860
public void supportsWrappingDto() throws Exception {
Object result = getProcessor("findOneWrappingDto").processResult(new Sample("Dave", "Matthews"));
assertThat(result).isInstanceOf(WrappingDto.class);
}
@Test // DATACMNS-921
public void fallsBackToApproximateCollectionIfNecessary() throws Exception {
ResultProcessor processor = getProcessor("findAllProjection");
SpecialList<Sample> specialList = new SpecialList<>(new Object());
specialList.add(new Sample("Dave", "Matthews"));
processor.processResult(specialList);
}
@Test // DATACMNS-836
@SuppressWarnings("unchecked")
public void supportsMonoWrapper() throws Exception {
Mono<Sample> samples = Mono.just(new Sample("Dave", "Matthews"));
Object result = getProcessor("findMonoSample").processResult(samples);
assertThat(result).isInstanceOf(Mono.class);
Object content = ((Mono<Object>) result).block();
assertThat(content).isInstanceOf(Sample.class);
}
@Test // DATACMNS-836
@SuppressWarnings("unchecked")
public void supportsSingleWrapper() throws Exception {
Single<Sample> samples = Single.just(new Sample("Dave", "Matthews"));
Object result = getProcessor("findSingleSample").processResult(samples);
assertThat(result).isInstanceOf(Single.class);
Object content = ((Single<Object>) result).toBlocking().value();
assertThat(content).isInstanceOf(Sample.class);
}
@Test // DATACMNS-836
@SuppressWarnings("unchecked")
public void refrainsFromProjectingUsingReactiveWrappersIfThePreparingConverterReturnsACompatibleInstance()
throws Exception {
ResultProcessor processor = getProcessor("findMonoSampleDto");
Object result = processor.processResult(Mono.just(new Sample("Dave", "Matthews")), source -> new SampleDto());
assertThat(result).isInstanceOf(Mono.class);
Object content = ((Mono<Object>) result).block();
assertThat(content).isInstanceOf(SampleDto.class);
}
@Test // DATACMNS-836
@SuppressWarnings("unchecked")
public void supportsFluxProjections() throws Exception {
Flux<Sample> samples = Flux.just(new Sample("Dave", "Matthews"));
Object result = getProcessor("findFluxProjection").processResult(samples);
assertThat(result).isInstanceOf(Flux.class);
List<Object> content = ((Flux<Object>) result).collectList().block();
assertThat(content).isNotEmpty();
assertThat(content.get(0)).isInstanceOf(SampleProjection.class);
}
@Test // DATACMNS-836
@SuppressWarnings("unchecked")
public void supportsObservableProjections() throws Exception {
Observable<Sample> samples = Observable.just(new Sample("Dave", "Matthews"));
Object result = getProcessor("findObservableProjection").processResult(samples);
assertThat(result).isInstanceOf(Observable.class);
List<Object> content = ((Observable<Object>) result).toList().toBlocking().single();
assertThat(content).isNotEmpty();
assertThat(content.get(0)).isInstanceOf(SampleProjection.class);
}
@Test // DATACMNS-988
@SuppressWarnings("unchecked")
public void supportsFlowableProjections() throws Exception {
Flowable<Sample> samples = Flowable.just(new Sample("Dave", "Matthews"));
Object result = getProcessor("findFlowableProjection").processResult(samples);
assertThat(result).isInstanceOf(Flowable.class);
List<Object> content = ((Flowable<Object>) result).toList().blockingGet();
assertThat(content).isNotEmpty();
assertThat(content.get(0)).isInstanceOf(SampleProjection.class);
}
private static ResultProcessor getProcessor(String methodName, Class<?>... parameters) throws Exception {
return getQueryMethod(methodName, parameters).getResultProcessor();
}
private static QueryMethod getQueryMethod(String name, Class<?>... parameters) throws Exception {
Method method = SampleRepository.class.getMethod(name, parameters);
return new QueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
new SpelAwareProxyProjectionFactory());
}
interface SampleRepository extends Repository<Sample, Long> {
List<Sample> findAll();
List<SampleDto> findAllDtos();
List<SampleProjection> findAllProjection();
Sample findOne();
SampleDto findOneDto();
WrappingDto findOneWrappingDto();
SampleProjection findOneProjection();
OpenProjection findOneOpenProjection();
Page<SampleProjection> findPageProjection(Pageable pageable);
Slice<SampleProjection> findSliceProjection(Pageable pageable);
<T> T findOneDynamic(Class<T> type);
Stream<SampleProjection> findStreamProjection();
Mono<Sample> findMonoSample();
Mono<SampleDto> findMonoSampleDto();
Single<Sample> findSingleSample();
Flux<SampleProjection> findFluxProjection();
Observable<SampleProjection> findObservableProjection();
Flowable<SampleProjection> findFlowableProjection();
}
static class Sample {
public String firstname, lastname;
public Sample(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
}
static class SampleDto {}
@lombok.Value
// Needs to be public until https://jira.spring.io/browse/SPR-14304 is resolved
public static class WrappingDto {
Sample sample;
}
interface SampleProjection {
String getLastname();
}
interface OpenProjection {
String getLastname();
@Value("#{target.firstname + ' ' + target.lastname}")
String getFullName();
}
static class SpecialList<E> extends ArrayList<E> {
private static final long serialVersionUID = -6539525376878522158L;
public SpecialList(Object dummy) {}
}
}