SGF-402 - Add support for Lucene query result projections.

This commit is contained in:
John Blum
2017-03-17 16:42:30 -07:00
parent cd92d4b373
commit 5113152b8f
15 changed files with 1584 additions and 62 deletions

View File

@@ -59,6 +59,11 @@ import org.mockito.runners.MockitoJUnitRunner;
* @see org.mockito.Spy
* @see org.mockito.runners.MockitoJUnitRunner
* @see org.springframework.data.gemfire.search.lucene.LuceneTemplate
* @see org.apache.geode.cache.lucene.LuceneQuery
* @see org.apache.geode.cache.lucene.LuceneQueryFactory
* @see org.apache.geode.cache.lucene.LuceneQueryProvider
* @see org.apache.geode.cache.lucene.LuceneResultStruct
* @see org.apache.geode.cache.lucene.PageableLuceneQueryResults
* @since 1.0.0
*/
@RunWith(MockitoJUnitRunner.class)

View File

@@ -0,0 +1,163 @@
/*
* Copyright 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.gemfire.search.lucene;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.lucene.LuceneIndex;
import org.apache.geode.cache.lucene.LuceneService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.data.gemfire.search.lucene.support.ProjectingLuceneAccessorSupport;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
/**
* Unit tests for {@link ProjectingLuceneAccessor}.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.Spy
* @see org.mockito.runners.MockitoJUnitRunner
* @see org.springframework.data.gemfire.search.lucene.ProjectingLuceneAccessor
* @since 1.1.0
*/
@RunWith(MockitoJUnitRunner.class)
public class ProjectingLuceneAccessorUnitTests {
@Mock
private BeanFactory mockBeanFactory;
@Mock
private GemFireCache mockCache;
@Mock
private ClassLoader mockClassLoader;
@Mock
private LuceneIndex mockLuceneIndex;
@Mock
private LuceneService mockLuceneService;
// SUT
private ProjectingLuceneAccessor projectingLuceneAccessor;
@Mock
private ProjectionFactory mockProjectionFactory;
@Mock
private Region<?, ?> mockRegion;
@Before
public void setup() {
projectingLuceneAccessor = spy(new ProjectingLuceneAccessorSupport() {});
doReturn(mockCache).when(projectingLuceneAccessor).resolveCache();
doReturn(mockLuceneService).when(projectingLuceneAccessor).resolveLuceneService();
doReturn("MockLuceneIndex").when(projectingLuceneAccessor).resolveIndexName();
doReturn("/Example").when(projectingLuceneAccessor).resolveRegionPath();
}
@Test
public void afterPropertiesSetInitializesTheProjectingLuceneAccessorCorrectly() throws Exception {
doReturn(mockProjectionFactory).when(projectingLuceneAccessor).resolveProjectionFactory();
projectingLuceneAccessor.afterPropertiesSet();
assertThat(projectingLuceneAccessor.getProjectionFactory()).isSameAs(mockProjectionFactory);
verify(projectingLuceneAccessor, times(1)).resolveProjectionFactory();
verifyZeroInteractions(mockProjectionFactory);
}
@Test
public void projectingLuceneAccessorIsInitializedCorrectly() {
assertThat(projectingLuceneAccessor.getBeanClassLoader()).isNull();
assertThat(projectingLuceneAccessor.getBeanFactory()).isNull();
assertThat(projectingLuceneAccessor.getCache()).isNull();
assertThat(projectingLuceneAccessor.getIndexName()).isNull();
assertThat(projectingLuceneAccessor.getLuceneIndex()).isNull();
assertThat(projectingLuceneAccessor.getLuceneService()).isNull();
assertThat(projectingLuceneAccessor.getProjectionFactory()).isNull();
assertThat(projectingLuceneAccessor.getRegion()).isNull();
assertThat(projectingLuceneAccessor.getRegionPath()).isNull();
projectingLuceneAccessor.setBeanClassLoader(mockClassLoader);
projectingLuceneAccessor.setBeanFactory(mockBeanFactory);
projectingLuceneAccessor.setCache(mockCache);
projectingLuceneAccessor.setIndexName("TestLuceneIdx");
projectingLuceneAccessor.setLuceneIndex(mockLuceneIndex);
projectingLuceneAccessor.setLuceneService(mockLuceneService);
projectingLuceneAccessor.setProjectionFactory(mockProjectionFactory);
projectingLuceneAccessor.setRegion(mockRegion);
projectingLuceneAccessor.setRegionPath("/Example");
assertThat(projectingLuceneAccessor.getBeanClassLoader()).isEqualTo(mockClassLoader);
assertThat(projectingLuceneAccessor.getBeanFactory()).isEqualTo(mockBeanFactory);
assertThat(projectingLuceneAccessor.getCache()).isEqualTo(mockCache);
assertThat(projectingLuceneAccessor.getIndexName()).isEqualTo("TestLuceneIdx");
assertThat(projectingLuceneAccessor.getLuceneIndex()).isEqualTo(mockLuceneIndex);
assertThat(projectingLuceneAccessor.getLuceneService()).isEqualTo(mockLuceneService);
assertThat(projectingLuceneAccessor.getProjectionFactory()).isEqualTo(mockProjectionFactory);
assertThat(projectingLuceneAccessor.getRegion()).isEqualTo(mockRegion);
assertThat(projectingLuceneAccessor.getRegionPath()).isEqualTo("/Example");
}
@Test
public void setThenGetProjectionFactoryIsCorrect() {
assertThat(projectingLuceneAccessor.getProjectionFactory()).isNull();
assertThat(projectingLuceneAccessor.setThenGetProjectionFactory(mockProjectionFactory))
.isSameAs(mockProjectionFactory);
assertThat(projectingLuceneAccessor.getProjectionFactory()).isSameAs(mockProjectionFactory);
}
@Test
public void resolveProjectFactoryReturnsProvidedProjectFactory() {
assertThat(projectingLuceneAccessor.getProjectionFactory()).isNull();
projectingLuceneAccessor.setProjectionFactory(mockProjectionFactory);
assertThat(projectingLuceneAccessor.getProjectionFactory()).isSameAs(mockProjectionFactory);
assertThat(projectingLuceneAccessor.resolveProjectionFactory()).isSameAs(mockProjectionFactory);
}
@Test
public void resolveProjectionFactoryCreatesNewProjectionFactory() {
assertThat(projectingLuceneAccessor.getProjectionFactory()).isNull();
ProjectionFactory projectionFactory = projectingLuceneAccessor.resolveProjectionFactory();
assertThat(projectionFactory).isInstanceOf(SpelAwareProxyProjectionFactory.class);
assertThat(projectingLuceneAccessor.getProjectionFactory()).isSameAs(projectionFactory);
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 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.gemfire.search.lucene;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.geode.cache.lucene.LuceneQueryProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
/**
* Unit tests for {@link ProjectingLuceneOperations}.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.runners.MockitoJUnitRunner
* @see org.springframework.data.gemfire.search.lucene.ProjectingLuceneOperations
* @since 1.1.0
*/
@RunWith(MockitoJUnitRunner.class)
public class ProjectingLuceneOperationsUnitTests {
@Mock
private LuceneQueryProvider mockQueryProvider;
@Mock
private TestProjectingLuceneOperations projectingLuceneOperations;
@Test
@SuppressWarnings("unchecked")
public void stringQueryCallsQueryWithResultLimit() {
when(projectingLuceneOperations.query(anyString(), anyString(), any(Class.class))).thenCallRealMethod();
projectingLuceneOperations.query("title : Up Shit Creek Without A Paddle",
"title", Book.class);
verify(projectingLuceneOperations, times(1))
.query(eq("title : Up Shit Creek Without A Paddle"), eq("title"),
eq(ProjectingLuceneOperations.DEFAULT_RESULT_LIMIT), eq(Book.class));
}
@Test
@SuppressWarnings("unchecked")
public void queryProviderQueryCallsQueryWithResultLimit() {
when(projectingLuceneOperations.query(any(LuceneQueryProvider.class), any(Class.class)))
.thenCallRealMethod();
projectingLuceneOperations.query(mockQueryProvider, Book.class);
verify(projectingLuceneOperations, times(1))
.query(eq(mockQueryProvider), eq(ProjectingLuceneOperations.DEFAULT_RESULT_LIMIT), eq(Book.class));
}
static class Book {}
abstract class TestProjectingLuceneOperations implements ProjectingLuceneOperations {
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright 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.gemfire.search.lucene;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import org.apache.geode.cache.lucene.LuceneQueryProvider;
import org.apache.geode.cache.lucene.LuceneResultStruct;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.gemfire.mapping.annotation.Region;
import org.springframework.data.projection.ProjectionFactory;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
/**
* Unit tests for {@link ProjectingLuceneTemplate}.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.Spy
* @see org.mockito.runners.MockitoJUnitRunner
* @since 1.1.0
*/
@RunWith(MockitoJUnitRunner.class)
public class ProjectingLuceneTemplateUnitTests {
@Mock
private LuceneQueryProvider mockQueryProvider;
// SUT
private ProjectingLuceneTemplate luceneTemplate;
@Mock
private ProjectionFactory mockProjectionFactory;
@Before
public void setup() {
luceneTemplate = spy(new ProjectingLuceneTemplate());
luceneTemplate.setProjectionFactory(mockProjectionFactory);
}
protected LuceneResultStruct<Long, String> mockLuceneResultStruct(Book book) {
return mockLuceneResultStruct(book.getIsbn(), book.getTitle());
}
@SuppressWarnings("unchecked")
protected <K, V> LuceneResultStruct<K, V> mockLuceneResultStruct(K key, V value) {
LuceneResultStruct<K, V> mockLuceneResultStruct =
mock(LuceneResultStruct.class, String.format("MockLuceneResultStruct%s", key));
when(mockLuceneResultStruct.getKey()).thenReturn(key);
when(mockLuceneResultStruct.getValue()).thenReturn(value);
return mockLuceneResultStruct;
}
@Test
public void queryWithString() {
List<Book> books = asList(
Book.newBook(4L, "Star Wars - Episode IV New Hope"),
Book.newBook(5L, "Star Wars - Episode V Empire Strikes Back"),
Book.newBook(6L, "Star Wars - Episode VI Return of the Jedi")
);
List<LuceneResultStruct<Long, String>> queryResults = asList(
mockLuceneResultStruct(books.get(0)),
mockLuceneResultStruct(books.get(1)),
mockLuceneResultStruct(books.get(2))
);
doReturn(queryResults).when(luceneTemplate).query(anyString(), anyString(), anyInt());
when(mockProjectionFactory.createProjection(eq(Book.class), anyString())).thenAnswer( invocationOnMock ->
books.stream().filter(book ->
book.getTitle().equals(invocationOnMock.getArgumentAt(1, String.class)))
.findFirst().orElse(null)
);
assertThat(luceneTemplate.query("title : Star Wars Episode *V*", "title", Book.class))
.containsAll(books);
verify(luceneTemplate, times(1))
.query(eq("title : Star Wars Episode *V*"), eq("title"),
eq(ProjectingLuceneTemplate.DEFAULT_RESULT_LIMIT), eq(Book.class));
verify(mockProjectionFactory, times(1))
.createProjection(eq(Book.class), eq(books.get(0).getTitle()));
verify(mockProjectionFactory, times(1))
.createProjection(eq(Book.class), eq(books.get(1).getTitle()));
verify(mockProjectionFactory, times(1))
.createProjection(eq(Book.class), eq(books.get(2).getTitle()));
}
@Test
public void queryWithQueryProvider() {
List<Book> books = asList(
Book.newBook(1L, "Star Wars - Episode I Phantom Menace"),
Book.newBook(2L, "Star Wars - Episode II Attack of the Clones"),
Book.newBook(3L, "Star Wars - Episode III Revenge of the Sith")
);
List<LuceneResultStruct<Long, String>> queryResults = asList(
mockLuceneResultStruct(books.get(0)),
mockLuceneResultStruct(books.get(1)),
mockLuceneResultStruct(books.get(2))
);
doReturn(queryResults).when(luceneTemplate).query(any(LuceneQueryProvider.class), anyInt());
when(mockProjectionFactory.createProjection(eq(Book.class), anyString())).thenAnswer( invocationOnMock ->
books.stream().filter(book ->
book.getTitle().equals(invocationOnMock.getArgumentAt(1, String.class)))
.findFirst().orElse(null)
);
assertThat(luceneTemplate.query(mockQueryProvider, Book.class)).containsAll(books);
verify(luceneTemplate, times(1))
.query(eq(mockQueryProvider), eq(ProjectingLuceneTemplate.DEFAULT_RESULT_LIMIT), eq(Book.class));
verify(mockProjectionFactory, times(1))
.createProjection(eq(Book.class), eq(books.get(0).getTitle()));
verify(mockProjectionFactory, times(1))
.createProjection(eq(Book.class), eq(books.get(1).getTitle()));
verify(mockProjectionFactory, times(1))
.createProjection(eq(Book.class), eq(books.get(2).getTitle()));
}
@Data
@Region("Books")
@RequiredArgsConstructor(staticName = "newBook")
static class Book {
@NonNull @Id Long isbn;
@NonNull String title;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 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.gemfire.search.lucene.support;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import org.aopalliance.intercept.MethodInterceptor;
import org.apache.geode.pdx.PdxInstance;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
/**
* Unit tests for {@link PdxInstanceMethodInterceptorFactory}.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.runners.MockitoJUnitRunner
* @see org.aopalliance.intercept.MethodInterceptor
* @see org.apache.geode.pdx.PdxInstance
* @see org.springframework.data.gemfire.search.lucene.support.PdxInstanceMethodInterceptorFactory
* @since 1.1.0
*/
@RunWith(MockitoJUnitRunner.class)
public class PdxInstanceMethodInterceptorFactoryUnitTests {
@Mock
private PdxInstance mockSource;
@Test
public void createMethodInterceptorIsSuccessful() {
MethodInterceptor methodInterceptor =
PdxInstanceMethodInterceptorFactory.INSTANCE.createMethodInterceptor(mockSource, Object.class);
assertThat(methodInterceptor).isInstanceOf(PdxInstanceMethodInterceptor.class);
assertThat(((PdxInstanceMethodInterceptor) methodInterceptor).getSource()).isSameAs(mockSource);
}
@Test
public void supportsPdxInstances() {
assertThat(PdxInstanceMethodInterceptorFactory.INSTANCE.supports(mockSource, Object.class)).isTrue();
}
@Test
public void doesNotSupportNonPdxInstances() {
assertThat(PdxInstanceMethodInterceptorFactory.INSTANCE.supports(new Object(), Object.class)).isFalse();
assertThat(PdxInstanceMethodInterceptorFactory.INSTANCE.supports(Boolean.TRUE, Object.class)).isFalse();
assertThat(PdxInstanceMethodInterceptorFactory.INSTANCE.supports('X', Object.class)).isFalse();
assertThat(PdxInstanceMethodInterceptorFactory.INSTANCE.supports(2, Object.class)).isFalse();
assertThat(PdxInstanceMethodInterceptorFactory.INSTANCE.supports(Math.PI, Object.class)).isFalse();
assertThat(PdxInstanceMethodInterceptorFactory.INSTANCE.supports("test", Object.class)).isFalse();
}
}

View File

@@ -0,0 +1,271 @@
/*
* Copyright 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.gemfire.search.lucene.support;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.springframework.data.gemfire.search.lucene.support.PdxInstanceMethodInterceptor.newPdxInstanceMethodInterceptor;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.geode.pdx.PdxInstance;
import org.apache.geode.pdx.WritablePdxInstance;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
/**
* Unit tests for {@link PdxInstanceMethodInterceptor}.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.runners.MockitoJUnitRunner
* @see org.springframework.data.gemfire.search.lucene.support.PdxInstanceMethodInterceptor
* @since 1.1.0
*/
@RunWith(MockitoJUnitRunner.class)
public class PdxInstanceMethodInterceptorUnitTests {
@Mock
private MethodInvocation mockMethodInvocation;
@Mock
private PdxInstance mockSource;
@Mock
private WritablePdxInstance mockNewSource;
@SafeVarargs
protected static <T> T[] asArray(T... array) {
return array;
}
@Test
public void newPdxInstanceMethodInterceptorWithValidObjectSourceIsSuccessful() {
PdxInstanceMethodInterceptor methodInterceptor = newPdxInstanceMethodInterceptor((Object) mockSource);
assertThat(methodInterceptor).isNotNull();
assertThat(methodInterceptor.getSource()).isSameAs(mockSource);
}
@Test(expected = IllegalArgumentException.class)
public void newPdxInstanceMethodInterceptorWithInvalidObjectSourceThrowsIllegalArgumentException() {
try {
newPdxInstanceMethodInterceptor(new Object());
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageStartingWith(String.format(
"Source [java.lang.Object] is not an instance of [%s]", PdxInstance.class.getName()));
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test
public void newPdxInstanceMethodInterceptorWithPdxInstanceIsSuccessful() {
PdxInstanceMethodInterceptor methodInterceptor = newPdxInstanceMethodInterceptor(mockSource);
assertThat(methodInterceptor).isNotNull();
assertThat(methodInterceptor.getSource()).isSameAs(mockSource);
}
@Test(expected = IllegalArgumentException.class)
public void constructPdxInstanceMethodInterceptorWithNullThrowsIllegalArgumentException() {
try {
new PdxInstanceMethodInterceptor(null);
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage("Source must not be null");
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test
public void invokeObjectMethodIsHandled() throws Throwable {
Person jonDoe = Person.newPerson("Jon", "Doe");
Method toString = jonDoe.getClass().getMethod("toString");
when(mockMethodInvocation.getMethod()).thenReturn(toString);
when(mockMethodInvocation.proceed()).thenAnswer(invocationOnMock -> toString.invoke(jonDoe));
assertThat(newPdxInstanceMethodInterceptor(mockSource).invoke(mockMethodInvocation)).isEqualTo("Jon Doe");
verify(mockMethodInvocation, times(1)).getMethod();
verify(mockMethodInvocation, times(1)).proceed();
verifyZeroInteractions(mockSource);
}
@Test
public void invokeGetterOnSourceIsHandled() throws Throwable {
Person jonDoe = Person.newPerson("Jon", "Doe");
Method getFirstName = jonDoe.getClass().getMethod("getFirstName");
when(mockMethodInvocation.getMethod()).thenReturn(getFirstName);
when(mockSource.hasField(eq("firstName"))).thenReturn(true);
when(mockSource.getField(eq("firstName"))).thenReturn(jonDoe.getFirstName());
assertThat(newPdxInstanceMethodInterceptor(mockSource).invoke(mockMethodInvocation)).isEqualTo("Jon");
verify(mockMethodInvocation, never()).proceed();
verify(mockSource, times(1)).hasField(eq("firstName"));
verify(mockSource, times(1)).getField(eq("firstName"));
verifyNoMoreInteractions(mockSource);
}
@Test
public void invokeSetterOnSourceIsHandled() throws Throwable {
Person jonDoe = Person.newPerson("Jon", "Doe");
Method setLastName = jonDoe.getClass().getMethod("setLastName", String.class);
when(mockMethodInvocation.getMethod()).thenReturn(setLastName);
when(mockMethodInvocation.getArguments()).thenReturn(asArray("Smith"));
when(mockSource.hasField(eq("lastName"))).thenReturn(true);
when(mockSource.createWriter()).thenReturn(mockNewSource);
PdxInstanceMethodInterceptor methodInterceptor = newPdxInstanceMethodInterceptor(mockSource);
assertThat(methodInterceptor.getSource()).isSameAs(mockSource);
assertThat(methodInterceptor.invoke(mockMethodInvocation)).isNull();
assertThat(methodInterceptor.getSource()).isEqualTo(mockNewSource);
verify(mockMethodInvocation, times(1)).getMethod();
verify(mockMethodInvocation, times(2)).getArguments();
verify(mockSource, times(1)).hasField(eq("lastName"));
verify(mockNewSource, times(1)).setField(eq("lastName"), eq("Smith"));
}
@Test(expected = IllegalStateException.class)
public void invokeThrowsIllegalStateExceptionWhenPdxInstanceDoesNotHaveField() throws Throwable {
Method getGender = Person.class.getMethod("getGender");
when(mockMethodInvocation.getMethod()).thenReturn(getGender);
when(mockSource.hasField(anyString())).thenReturn(false);
try {
newPdxInstanceMethodInterceptor(mockSource).invoke(mockMethodInvocation);
}
catch (IllegalStateException expected) {
assertThat(expected).hasMessage("Source [%s] does not contain field with name [gender]", mockSource);
assertThat(expected).hasNoCause();
throw expected;
}
finally {
verify(mockMethodInvocation, times(1)).getMethod();
verify(mockSource, times(1)).hasField(eq("gender"));
verifyNoMoreInteractions(mockSource);
}
}
@Test(expected = IllegalArgumentException.class)
public void invokeThrowsIllegalArgumentExceptionWhenMethodInvocationHasIncorrectNumberOfArguments()
throws Throwable {
Method setGender = Person.class.getMethod("setGender", Gender.class);
when(mockMethodInvocation.getMethod()).thenReturn(setGender);
when(mockSource.hasField(eq("gender"))).thenReturn(true);
when(mockMethodInvocation.getArguments()).thenReturn(asArray(Gender.FEMALE, Gender.MALE));
try {
newPdxInstanceMethodInterceptor(mockSource).invoke(mockMethodInvocation);
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage(
"Invoked setter method [setGender] must expect exactly 1 argument; Arguments were [[FEMALE, MALE]]");
assertThat(expected).hasNoCause();
throw expected;
}
finally {
verify(mockMethodInvocation, times(1)).getMethod();
verify(mockMethodInvocation, times(2)).getArguments();
verify(mockSource, times(1)).hasField(eq("gender"));
}
}
@Test(expected = IllegalStateException.class)
public void invokeThrowsIllegalArgumentExceptionWhenPdxInstanceHasNoWriter() throws Throwable {
Method setGender = Person.class.getMethod("setGender", Gender.class);
when(mockMethodInvocation.getMethod()).thenReturn(setGender);
when(mockMethodInvocation.getArguments()).thenReturn(asArray(Gender.FEMALE));
when(mockSource.hasField(eq("gender"))).thenReturn(true);
when(mockSource.createWriter()).thenReturn(null);
try {
newPdxInstanceMethodInterceptor(mockSource).invoke(mockMethodInvocation);
}
catch (IllegalStateException expected) {
assertThat(expected).hasMessage(
"No writer for PdxInstance [%s] was found for setting field [gender] to value [FEMALE]", mockSource);
assertThat(expected).hasNoCause();
throw expected;
}
finally {
verify(mockMethodInvocation, times(1)).getMethod();
verify(mockMethodInvocation, times(2)).getArguments();
verify(mockSource, times(1)).hasField(eq("gender"));
verify(mockSource, times(1)).createWriter();
}
}
enum Gender {
FEMALE,
MALE
}
@Data
@RequiredArgsConstructor(staticName = "newPerson")
@SuppressWarnings("unused")
static class Person {
Gender gender;
@NonNull String firstName;
@NonNull String lastName;
/**
* @inheritDoc
*/
@Override
public String toString() {
return String.format("%1$s %2$s", getFirstName(), getLastName());
}
}
}