diff --git a/src/main/java/org/springframework/data/gemfire/mapping/BeanPropertyAccessor.java b/src/main/java/org/springframework/data/gemfire/mapping/BeanPropertyAccessor.java new file mode 100644 index 00000000..5356bb68 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/BeanPropertyAccessor.java @@ -0,0 +1,103 @@ +/* + * Copyright 2010-2013 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.mapping; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import org.springframework.core.convert.ConversionService; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.model.ConvertingPropertyAccessor; +import org.springframework.data.mapping.model.MappingException; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * The BeanPropertyAccessor class is an implementation of Spring Data Common's PersistentPropertyAccessor for accessing + * persistent properties on beans. + * + * @author John Blum + * @see java.lang.reflect.Field + * @see java.lang.reflect.Method + * @see org.springframework.data.mapping.PersistentProperty + * @see org.springframework.data.mapping.PersistentPropertyAccessor + * @see org.springframework.data.mapping.model.ConvertingPropertyAccessor + * @since 1.7.0 + */ +class BeanPropertyAccessor implements PersistentPropertyAccessor { + + private final Object bean; + + private BeanPropertyAccessor(final Object bean) { + Assert.notNull(bean, "Bean must not be null"); + this.bean = bean; + } + + public static PersistentPropertyAccessor create(Object bean) { + return new BeanPropertyAccessor(bean); + } + + public static PersistentPropertyAccessor create(Object bean, ConversionService conversionService) { + return new ConvertingPropertyAccessor(new BeanPropertyAccessor(bean), conversionService); + } + + @Override + public void setProperty(PersistentProperty property, Object value) { + try { + if (property.usePropertyAccess() && property.getSetter() != null) { + Method setter = property.getSetter(); + ReflectionUtils.makeAccessible(setter); + ReflectionUtils.invokeMethod(setter, getBean(), value); + } + else { + Field field = property.getField(); + ReflectionUtils.makeAccessible(field); + ReflectionUtils.setField(field, getBean(), value); + } + } + catch (Throwable t) { + throw new MappingException(String.format("Failed to set property (%1$s) to value (%2$s)!", + property, value), t); + } + } + + @Override + public Object getProperty(PersistentProperty property) { + try { + if (property.usePropertyAccess() && property.getGetter() != null) { + Method getter = property.getGetter(); + ReflectionUtils.makeAccessible(getter); + return ReflectionUtils.invokeMethod(getter, getBean()); + } + else { + Field field = property.getField(); + ReflectionUtils.makeAccessible(field); + return ReflectionUtils.getField(field, getBean()); + } + } + catch (Throwable t) { + throw new MappingException(String.format("Failed to get value of property (%1$s)!", property), t); + } + } + + @Override + public Object getBean() { + return bean; + } + +} diff --git a/src/main/java/org/springframework/data/gemfire/mapping/MappingPdxSerializer.java b/src/main/java/org/springframework/data/gemfire/mapping/MappingPdxSerializer.java index c48e3926..bfd6f431 100644 --- a/src/main/java/org/springframework/data/gemfire/mapping/MappingPdxSerializer.java +++ b/src/main/java/org/springframework/data/gemfire/mapping/MappingPdxSerializer.java @@ -26,8 +26,8 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PropertyHandler; -import org.springframework.data.mapping.model.BeanWrapper; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; import org.springframework.data.mapping.model.SpELContext; @@ -149,7 +149,7 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw new PersistentEntityParameterValueProvider(entity, new GemfirePropertyValueProvider(reader), null)); - final BeanWrapper wrapper = BeanWrapper.create(instance, getConversionService()); + final PersistentPropertyAccessor wrapper = BeanPropertyAccessor.create(instance, getConversionService()); entity.doWithProperties(new PropertyHandler() { @Override @@ -190,7 +190,7 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw public boolean toData(Object value, final PdxWriter writer) { GemfirePersistentEntity entity = getPersistentEntity(value.getClass()); - final BeanWrapper wrapper = BeanWrapper.create(value, getConversionService()); + final PersistentPropertyAccessor wrapper = BeanPropertyAccessor.create(value, getConversionService()); entity.doWithProperties(new PropertyHandler() { @Override diff --git a/src/test/java/org/springframework/data/gemfire/mapping/BeanPropertyAccessorTest.java b/src/test/java/org/springframework/data/gemfire/mapping/BeanPropertyAccessorTest.java new file mode 100644 index 00000000..212650de --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/mapping/BeanPropertyAccessorTest.java @@ -0,0 +1,220 @@ +/* + * Copyright 2010-2013 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.mapping; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hamcrest.Matchers; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.model.MappingException; + +/** + * The BeanPropertyAccessorTest class is a test suite of test cases testing the contract and functionality + * of the BeanPropertyAccessor class. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.mapping.BeanPropertyAccessor + * @see org.springframework.data.mapping.PersistentProperty + * @see org.springframework.data.mapping.PersistentPropertyAccessor + * @since 1.7.0 + */ +public class BeanPropertyAccessorTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void createWithNullBean() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectCause(is(nullValue(Throwable.class))); + expectedException.expectMessage("Bean must not be null"); + BeanPropertyAccessor.create(null); + } + + @Test + public void setAndGetPropertyValueUsingFieldAccess() throws Exception { + ExampleBean bean = new ExampleBean(); + + PersistentProperty mockPersistentProperty = mock(PersistentProperty.class, "MockPersistentProperty"); + + when(mockPersistentProperty.usePropertyAccess()).thenReturn(false); + when(mockPersistentProperty.getField()).thenReturn(ExampleBean.class.getDeclaredField("value")); + when(mockPersistentProperty.getGetter()).thenReturn(null); + when(mockPersistentProperty.getSetter()).thenReturn(null); + + PersistentPropertyAccessor propertyAccessor = BeanPropertyAccessor.create(bean); + + assertSame(bean, propertyAccessor.getBean()); + assertThat(bean.getValue(), is(nullValue())); + + propertyAccessor.setProperty(mockPersistentProperty, "mock"); + + assertThat(bean.getValue(), is(equalTo("mock"))); + assertThat(String.valueOf(propertyAccessor.getProperty(mockPersistentProperty)), is(equalTo("mock"))); + + when(mockPersistentProperty.usePropertyAccess()).thenReturn(true); + propertyAccessor.setProperty(mockPersistentProperty, null); + + assertThat(bean.getValue(), is(nullValue())); + assertThat(propertyAccessor.getProperty(mockPersistentProperty), is(nullValue())); + + verify(mockPersistentProperty, times(4)).usePropertyAccess(); + verify(mockPersistentProperty, times(4)).getField(); + verify(mockPersistentProperty, times(1)).getGetter(); + verify(mockPersistentProperty, times(1)).getSetter(); + } + + @Test + @SuppressWarnings("unchecked") + public void setAndGetPropertyValueUsingPropertyAccess() throws Exception { + ExampleBean bean = new ExampleBean(); + + PersistentProperty mockPersistentProperty = mock(PersistentProperty.class, "MockPersistentProperty"); + + when(mockPersistentProperty.usePropertyAccess()).thenReturn(true); + when(mockPersistentProperty.getSetter()).thenReturn(ExampleBean.class.getMethod("setValue", Object.class)); + when(mockPersistentProperty.getGetter()).thenReturn(ExampleBean.class.getMethod("getValue")); + + PersistentPropertyAccessor propertyAccessor = BeanPropertyAccessor.create(bean); + + assertSame(bean, propertyAccessor.getBean()); + assertThat(bean.getValue(), is(nullValue())); + + propertyAccessor.setProperty(mockPersistentProperty, "test"); + + assertThat(bean.getValue(), is(equalTo("test"))); + assertThat(String.valueOf(propertyAccessor.getProperty(mockPersistentProperty)), is(equalTo("test"))); + + propertyAccessor.setProperty(mockPersistentProperty, null); + + assertThat(bean.getValue(), is(nullValue())); + assertThat(propertyAccessor.getProperty(mockPersistentProperty), is(nullValue())); + + verify(mockPersistentProperty, times(4)).usePropertyAccess(); + verify(mockPersistentProperty, never()).getField(); + verify(mockPersistentProperty, times(4)).getGetter(); + verify(mockPersistentProperty, times(4)).getSetter(); + } + + @Test + public void setPropertyWithNonExistingField() throws Exception { + ExampleBean bean = new ExampleBean(); + + PersistentProperty mockPersistentProperty = mock(PersistentProperty.class, "MockPersistentProperty"); + + when(mockPersistentProperty.usePropertyAccess()).thenReturn(false); + when(mockPersistentProperty.getField()).thenReturn(InvalidBean.class.getDeclaredField("temp")); + + PersistentPropertyAccessor propertyAccessor = BeanPropertyAccessor.create(bean); + + assertSame(bean, propertyAccessor.getBean()); + assertThat(bean.getValue(), is(nullValue())); + + expectedException.expect(MappingException.class); + expectedException.expectCause(is(notNullValue(Throwable.class))); + expectedException.expectMessage(Matchers.startsWith(String.format( + "Failed to get value of property (%1$s)!", mockPersistentProperty))); + + try { + propertyAccessor.getProperty(mockPersistentProperty); + } + finally { + verify(mockPersistentProperty, times(1)).usePropertyAccess(); + verify(mockPersistentProperty, times(1)).getField(); + verify(mockPersistentProperty, never()).getGetter(); + } + } + + @Test + public void setPropertyWithNonExistingSetterMethod() throws Exception { + ExampleBean bean = new ExampleBean(); + + PersistentProperty mockPersistentProperty = mock(PersistentProperty.class, "MockPersistentProperty"); + + when(mockPersistentProperty.usePropertyAccess()).thenReturn(true); + when(mockPersistentProperty.getSetter()).thenReturn(InvalidBean.class.getMethod("getTemp")); + + PersistentPropertyAccessor propertyAccessor = BeanPropertyAccessor.create(bean); + + assertSame(bean, propertyAccessor.getBean()); + assertThat(bean.getValue(), is(nullValue())); + + expectedException.expect(MappingException.class); + expectedException.expectCause(is(notNullValue(Throwable.class))); + expectedException.expectMessage(Matchers.startsWith(String.format( + "Failed to set property (%1$s) to value (test)!", mockPersistentProperty))); + + try { + propertyAccessor.setProperty(mockPersistentProperty, "test"); + } + finally { + assertThat(bean.getValue(), is(nullValue())); + verify(mockPersistentProperty, times(1)).usePropertyAccess(); + verify(mockPersistentProperty, never()).getField(); + verify(mockPersistentProperty, times(2)).getSetter(); + } + } + + public static class ExampleBean { + + private T value; + + public T getValue() { + return value; + } + + public void setValue(final T value) { + this.value = value; + } + + @Override + public String toString() { + return String.format("{ @type = %1$s, value = %2$s }", getClass().getName(), getValue()); + } + } + + @SuppressWarnings("unused") + public static class InvalidBean { + + Object temp; + + public Object getTemp() { + return temp; + } + + public void setTemp(final Object temp) { + this.temp = temp; + } + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerUnitTests.java b/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerUnitTests.java index b95ace9c..a84564c9 100644 --- a/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerUnitTests.java @@ -15,8 +15,11 @@ */ package org.springframework.data.gemfire.mapping; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; +import static org.mockito.Matchers.any; +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 java.util.HashMap; import java.util.Map; @@ -44,12 +47,15 @@ import com.gemstone.gemfire.pdx.PdxSerializer; @RunWith(MockitoJUnitRunner.class) public class MappingPdxSerializerUnitTests { - GemfireMappingContext context; ConversionService conversionService; + + GemfireMappingContext context; + MappingPdxSerializer serializer; @Mock EntityInstantiator instantiator; + @Mock PdxReader reader; @@ -58,11 +64,12 @@ public class MappingPdxSerializerUnitTests { @Before public void setUp() { - - context = new GemfireMappingContext(); conversionService = new GenericConversionService(); + context = new GemfireMappingContext(); serializer = new MappingPdxSerializer(context, conversionService); + Map,PdxSerializer> customSerializers = new HashMap, PdxSerializer>(); + customSerializers.put(Address.class, addressSerializer); serializer.setCustomSerializers(customSerializers); } @@ -71,17 +78,20 @@ public class MappingPdxSerializerUnitTests { @SuppressWarnings("unchecked") public void usesRegisteredInstantiator() { Address address = new Address(); - address.zipCode = "01234"; address.city = "London"; + address.zipCode = "01234"; Person person = new Person(1L, "Oliver", "Gierke"); person.address = address; - - ParameterValueProvider provider = any(ParameterValueProvider.class); + GemfirePersistentEntity entity = any(GemfirePersistentEntity.class); + + ParameterValueProvider provider = any(ParameterValueProvider.class); + when(instantiator.createInstance(entity, provider)).thenReturn(person); Map, EntityInstantiator> instantiators = new HashMap, EntityInstantiator>(); + instantiators.put(Person.class, instantiator); serializer.setGemfireInstantiators(instantiators); @@ -91,4 +101,5 @@ public class MappingPdxSerializerUnitTests { any(ParameterValueProvider.class)); verify(addressSerializer,times(1)).fromData(eq(Address.class), any(PdxReader.class)); } + }