/** * */ package org.springframework.data.aerospike.convert; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URL; import java.util.*; import org.hamcrest.MatcherAssert; import org.hamcrest.beans.HasProperty; import org.hamcrest.beans.SamePropertyValuesAs; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.MockitoAnnotations; import org.springframework.beans.ConversionNotSupportedException; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.aerospike.convert.MappingAerospikeConverterTest.ClassWithMapUsingEnumAsKey.FooBarEnum; import org.springframework.data.aerospike.mapping.*; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.TypeAlias; import com.aerospike.client.Bin; import com.aerospike.client.Key; import com.aerospike.client.Record; /** * @author Peter Milne * @author Jean Mercier */ public class MappingAerospikeConverterTest { MappingAerospikeConverter converter; Key key; private static final String AEROSPIKE_KEY = "AerospikeKey"; private static final String AEROSPIKE_SET_NAME = "AerospikeSetName"; private static final String AEROSPIKE_NAME_SPACE = "AerospikeNameSpace"; /** * @throws java.lang.Exception */ @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); converter = new MappingAerospikeConverter(); key = new Key(AEROSPIKE_NAME_SPACE, AEROSPIKE_SET_NAME, AEROSPIKE_KEY); } /** * @throws java.lang.Exception */ @After public void tearDown() throws Exception { } /** * Test method for {@link org.springframework.data.aerospike.convert.MappingAerospikeConverter#MappingAerospikeConverter()}. */ @Test public void testMappingAerospikeConverter() { MappingAerospikeConverter mappingAerospikeConverter = new MappingAerospikeConverter(); assertNotNull(mappingAerospikeConverter.getMappingContext()); assertNotNull(mappingAerospikeConverter.getConversionService()); } /** * Test method for {@link org.springframework.data.aerospike.convert.MappingAerospikeConverter#getMappingContext()}. */ @Test public void testGetMappingContext() { MappingAerospikeConverter mappingAerospikeConverter = new MappingAerospikeConverter(); assertNotNull(mappingAerospikeConverter.getMappingContext()); assertTrue(mappingAerospikeConverter.getMappingContext() instanceof AerospikeMappingContext); } /** * Test method for {@link org.springframework.data.aerospike.convert.MappingAerospikeConverter#getConversionService()}. */ @Test public void testGetConversionService() { MappingAerospikeConverter mappingAerospikeConverter = new MappingAerospikeConverter(); assertNotNull(mappingAerospikeConverter.getConversionService()); assertTrue(mappingAerospikeConverter.getConversionService() instanceof DefaultConversionService); } /** * Test method for {@link org.springframework.data.aerospike.convert.MappingAerospikeConverter#read(java.lang.Class, org.springframework.data.aerospike.convert.AerospikeData)}. */ @Test public void convertsAddressCorrectlyToAerospikeData() { Address address = new Address(); address.city = "New York"; address.street = "Broadway"; AerospikeData dbObject = AerospikeData.forWrite(AEROSPIKE_NAME_SPACE); dbObject.setID(AEROSPIKE_KEY); converter.write(address, dbObject); assertTrue(dbObject.getBins().contains(new Bin("city", "New York"))); assertTrue(dbObject.getBins().contains(new Bin("street", "Broadway"))); } @SuppressWarnings("serial") @Test public void convertsAerospikeDataToAddressCorrectly() { Address address = new Address(); address.city = "New York"; address.street = "Broadway"; Map<String, Object> bins = new HashMap<String, Object>() { { put("city", "New York"); put("street", "Broadway"); } }; Record record = new Record(bins, 1, 1); AerospikeData dbObject = AerospikeData.forRead(key, record); Address convertedAddress = converter.read(Address.class, dbObject); assertThat(convertedAddress, SamePropertyValuesAs.samePropertyValuesAs(address)); } @Test public void convertsDateTypesCorrectly() { Person person = new Person(); person.birthDate = new Date(); AerospikeData dbObject = AerospikeData.forWrite(AEROSPIKE_NAME_SPACE); dbObject.setID(AEROSPIKE_KEY); converter.write(person, dbObject); returnBinPropertyValue(dbObject, "birthDate"); assertThat(returnBinPropertyValue(dbObject, "birthDate"), is(instanceOf(Date.class))); Record record = new Record(listToMap(dbObject.getBins()), 1, 1); AerospikeData forRead = AerospikeData.forRead(key, record); Person result = converter.read(Person.class, forRead); assertThat(result.birthDate, is(notNullValue())); } /** * Test method for {@link org.springframework.data.aerospike.convert.MappingAerospikeConverter#write(java.lang.Object, org.springframework.data.aerospike.convert.AerospikeData)}. */ @SuppressWarnings("unchecked") @Test public void writesMapTypeCorrectly() { ClassWithMapProperty foo = new ClassWithMapProperty(); foo.map = Collections.singletonMap(Locale.US, "Biff"); AerospikeData dbObject = AerospikeData.forWrite(AEROSPIKE_NAME_SPACE); dbObject.setID(AEROSPIKE_KEY); converter.write(foo, dbObject); Object object = returnBinPropertyValue(dbObject, "map"); assertThat((Map<Locale, String>) object, hasEntry(Locale.US, "Biff")); } /** * Test method for {@link org.springframework.data.aerospike.convert.MappingAerospikeConverter#write(java.lang.Object, org.springframework.data.aerospike.convert.AerospikeData)}. */ @SuppressWarnings("unchecked") @Test public void writesNullValuesForMapsCorrectly() { ClassWithMapProperty foo = new ClassWithMapProperty(); foo.map = Collections.singletonMap(Locale.US, null); AerospikeData dbObject = AerospikeData.forWrite(AEROSPIKE_NAME_SPACE); dbObject.setID(AEROSPIKE_KEY); converter.write(foo, dbObject); Object object = returnBinPropertyValue(dbObject, "map"); assertThat((Map<Locale, String>) object, hasEntry(Locale.US, null)); } @Test public void writesEnumsCorrectly() { ClassWithEnumProperty value = new ClassWithEnumProperty(); value.sampleEnum = SampleEnum.FIRST; AerospikeData result = AerospikeData.forWrite(AEROSPIKE_NAME_SPACE); result.setID(AEROSPIKE_KEY); converter.write(value, result); Object object = returnBinPropertyValue(result, "sampleEnum"); //all Enums are saved in form of String in the DB assertThat(object, is(instanceOf(String.class))); assertThat(SampleEnum.valueOf(object.toString()), is(SampleEnum.FIRST)); } @SuppressWarnings("rawtypes") @Test public void writesEnumCollectionCorrectly() { ClassWithEnumProperty value = new ClassWithEnumProperty(); value.enums = Arrays.asList(SampleEnum.FIRST); AerospikeData result = AerospikeData.forWrite(AEROSPIKE_NAME_SPACE); result.setID(AEROSPIKE_KEY); converter.write(value, result); Object object = returnBinPropertyValue(result, "enums"); assertThat(((List) object).size(), is(1)); assertThat((String) ((List) object).get(0).toString(), is("FIRST")); } @SuppressWarnings("serial") @Test public void readsEnumsCorrectly() { Map<String, Object> bins = new HashMap<String, Object>() { { put("sampleEnum", SampleEnum.FIRST); } }; Record record = new Record(bins, 1, 1); AerospikeData dbObject = AerospikeData.forRead(key, record); ClassWithEnumProperty result = converter.read(ClassWithEnumProperty.class, dbObject); assertThat(result.sampleEnum, is(SampleEnum.FIRST)); } @SuppressWarnings("serial") @Test public void readsEnumCollectionsCorrectly() { Map<String, Object> bins = new HashMap<String, Object>() { { put("sampleEnum", SampleEnum.FIRST); put("enums", Arrays.asList(SampleEnum.FIRST)); } }; Record record = new Record(bins, 1, 1); AerospikeData dbObject = AerospikeData.forRead(key, record); ClassWithEnumProperty result = converter.read(ClassWithEnumProperty.class, dbObject); assertThat(result.enums, is(instanceOf(List.class))); assertThat(result.enums.size(), is(1)); assertThat(result.enums, hasItem(SampleEnum.FIRST)); } @Test public void considersFieldNameAnnotationWhenWriting() { Person person = new Person(); person.firstname = "Oliver"; AerospikeData dbObject = AerospikeData.forWrite(AEROSPIKE_NAME_SPACE); dbObject.setID(AEROSPIKE_KEY); converter.write(person, dbObject); Object foo = returnBinPropertyValue(dbObject, "foo"); Object firstName = returnBinPropertyValue(dbObject, "firstName"); MatcherAssert.assertThat((String) foo, is(equalTo("Oliver"))); assertNull(firstName); } @SuppressWarnings("serial") @Test public void considersFieldNameAnnotationWhenReading() { Map<String, Object> bins = new HashMap<String, Object>() { { put("id", "id1"); put("birthDate", null); put("foo", "Oliver"); } }; Record record = new Record(bins, 1, 1); AerospikeData dbObject = AerospikeData.forRead(key, record); Person result = converter.read(Person.class, dbObject); assertThat(result.firstname, is("Oliver")); assertThat(result, not(HasProperty.hasProperty("foo"))); } @SuppressWarnings({"rawtypes", "unchecked"}) @Test public void resolvesNestedComplexTypeForWriteCorrectly() { Address address = new Address(); address.city = "London"; address.street = "110 Southwark Street"; Set<Address> addresses = new HashSet<MappingAerospikeConverterTest.Address>(); addresses.add(address); Person person = new Person(addresses); AerospikeData dbObject = AerospikeData.forWrite(AEROSPIKE_NAME_SPACE); dbObject.setID(AEROSPIKE_KEY); converter.write(person, dbObject); List<Object> list = (List<Object>) returnBinPropertyValue(dbObject, "addresses"); HashMap hashMap = (HashMap) list.get(0); String city = (String) hashMap.get("city"); assertThat(city, is("London")); } @SuppressWarnings("serial") @Test public void resolvesNestedComplexTypeForReadCorrectly() { final Address address = new Address(); address.city = "London"; address.street = "110 Southwark Street"; Map<String, Object> bins = new HashMap<String, Object>() { { put("id", "id1"); put("birthDate", new Date()); put("foo", "Oliver"); put("lastname", "Cromwell"); put("addresses", address); } }; Record record = new Record(bins, 1, 1); AerospikeData dbObject = AerospikeData.forRead(key, record); Person result = converter.read(Person.class, dbObject); assertThat(result.addresses, is(notNullValue())); } @SuppressWarnings("unchecked") @Test public void writesClassWithBigDecimal() { BigDecimalContainer container = new BigDecimalContainer(); container.value = BigDecimal.valueOf(2.5d); container.map = Collections.singletonMap("foo", container.value); AerospikeData dbObject = AerospikeData.forWrite(AEROSPIKE_NAME_SPACE); dbObject.setID(AEROSPIKE_KEY); converter.write(container, dbObject); returnBinPropertyValue(dbObject, "value"); Object objectMap = returnBinPropertyValue(dbObject, "map"); assertThat(((Map<String, BigDecimal>) objectMap).get("foo"), is(instanceOf(BigDecimal.class))); } @SuppressWarnings("serial") @Test public void readClassWithBigDecimal() { Map<String, Object> bins = new HashMap<String, Object>() { { put("value", BigDecimal.valueOf(2.5d)); put("map", Collections.singletonMap("foo", BigDecimal.valueOf(2.5d))); put("collection", Arrays.asList(BigDecimal.valueOf(2.5d), BigDecimal.valueOf(12.5d), BigDecimal.valueOf(22.5d))); } }; Record record = new Record(bins, 1, 1); AerospikeData dbObject = AerospikeData.forRead(key, record); BigDecimalContainer result = converter.read(BigDecimalContainer.class, dbObject); assertThat(result.value, is(BigDecimal.valueOf(2.5d))); assertThat(result.map.get("foo"), is(BigDecimal.valueOf(2.5d))); assertThat(result.collection.get(0), is(BigDecimal.valueOf(2.5d))); } @Test public void readsEmptySetsCorrectly() { Person person = new Person(); person.addresses = Collections.emptySet(); AerospikeData forWrite = AerospikeData.forWrite(AEROSPIKE_NAME_SPACE); forWrite.setID(AEROSPIKE_KEY); converter.write(person, forWrite); Record record = new Record(listToMap(forWrite.getBins()), 1, 1); AerospikeData forRead = AerospikeData.forRead(key, record); Person result = converter.read(Person.class, forRead); assertThat(result.addresses, hasSize(0)); } @Test public void convertsObjectIdStringsToObjectIdCorrectly() { PersonPojoStringId p1 = new PersonPojoStringId("1234567890", "Text-1"); AerospikeData dbObject = AerospikeData.forWrite(AEROSPIKE_NAME_SPACE); dbObject.setID(AEROSPIKE_KEY); converter.write(p1, dbObject); assertThat(returnBinPropertyValue(dbObject, MappingAerospikeConverter.SPRING_ID_BIN), is(instanceOf(String.class))); } @SuppressWarnings("serial") @Test public void convertsCustomEmptyMapCorrectly() { final Map<String, Object> map = new HashMap<String, Object>() { { put("city", "New York"); put("street", "Broadway"); } }; Map<String, Object> bins = new HashMap<String, Object>() { { put("map", map); } }; Record record = new Record(bins, 1, 1); AerospikeData dbObject = AerospikeData.forRead(key, record); ClassWithSortedMap result = converter.read(ClassWithSortedMap.class, dbObject); assertThat(result, is(instanceOf(ClassWithSortedMap.class))); assertThat(result.map, is(instanceOf(Map.class))); } @Test public void maybeConvertHandlesNullValuesCorrectly() { assertThat(converter.convertToAerospikeType(null), is(nullValue())); } @Test public void writesIntIdCorrectly() { ClassWithIntId value = new ClassWithIntId(); value.id = 5; AerospikeData result = AerospikeData.forWrite(AEROSPIKE_NAME_SPACE); result.setID(AEROSPIKE_KEY); converter.write(value, result); returnBinPropertyValue(result, "_id"); Object objectSpringValue = returnBinPropertyValue(result, MappingAerospikeConverter.SPRING_ID_BIN); assertThat(objectSpringValue, is((Object) 5)); } @SuppressWarnings("unchecked") @Test public void writesNullValuesForCollection() { CollectionWrapper wrapper = new CollectionWrapper(); wrapper.contacts = Arrays.<Contact>asList(new Person(), null); AerospikeData result = AerospikeData.forWrite(AEROSPIKE_NAME_SPACE); result.setID(AEROSPIKE_KEY); converter.write(wrapper, result); List<Object> contacts = (List<Object>) returnBinPropertyValue(result, "contacts"); assertThat(contacts, is(instanceOf(Collection.class))); assertThat(((Collection<?>) contacts).size(), is(2)); Person contactItem = (Person) contacts.get(0); assertThat(contactItem.addresses, nullValue()); } /** * @param bins * @return */ private Map<String, Object> listToMap(List<Bin> bins) { Map<String, Object> map = new HashMap<String, Object>(); if (bins != null && bins.size() > 0) { for (Bin bin : bins) map.put(bin.name, bin.value.getObject()); } return map; } /** * @param aerospikeData * @param property * @return */ @SuppressWarnings("unchecked") private Object returnBinPropertyValue(AerospikeData aerospikeData, String property) { if (aerospikeData.getBins() == null || aerospikeData.getBins().size() == 0) return null; for (Iterator<Bin> iterator = aerospikeData.getBins().iterator(); iterator.hasNext(); ) { Bin bin = (Bin) iterator.next(); if (bin.name.equals(AerospikeMetadataBin.AEROSPIKE_META_DATA)) { HashMap<String, Object> map = (HashMap<String, Object>) bin.value.getObject(); for (Map.Entry<String, Object> entry : map.entrySet()) { if (entry.getKey().equals(property)) { return entry.getValue(); } } } else if (bin.name.equals(property)) { return bin.value.getObject(); } } return null; } static class GenericType<T> { T content; } static class ClassWithEnumProperty { SampleEnum sampleEnum; List<SampleEnum> enums; EnumSet<SampleEnum> enumSet; EnumMap<SampleEnum, String> enumMap; } static enum SampleEnum { FIRST { @Override void method() { } }, SECOND { @Override void method() { } }; abstract void method(); } static interface InterfaceType { } static class Address implements InterfaceType { String street; String city; @Override public String toString() { return "Address [street=" + street + ", city=" + city + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((city == null) ? 0 : city.hashCode()); result = prime * result + ((street == null) ? 0 : street.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Address other = (Address) obj; if (city == null) { if (other.city != null) return false; } else if (!city.equals(other.city)) return false; if (street == null) { if (other.street != null) return false; } else if (!street.equals(other.street)) return false; return true; } } interface Contact { } static class Person implements Contact { @Id String id; Date birthDate; @Field("foo") String firstname; String lastname; Set<Address> addresses; public Person() { } public Person(Set<Address> addresses) { this.addresses = addresses; } } static class ClassWithSortedMap { SortedMap<String, String> map; } static class ClassWithMapProperty { Map<Locale, String> map; Map<String, List<String>> mapOfLists; Map<String, Object> mapOfObjects; Map<String, String[]> mapOfStrings; Map<String, Person> mapOfPersons; TreeMap<String, Person> treeMapOfPersons; } static class ClassWithNestedMaps { Map<String, Map<String, Map<String, String>>> nestedMaps; } static class BirthDateContainer { Date birthDate; } static class BigDecimalContainer { BigDecimal value; Map<String, BigDecimal> map; List<BigDecimal> collection; } static class CollectionWrapper { List<Contact> contacts; List<List<String>> strings; List<Map<String, Locale>> listOfMaps; Set<Contact> contactsSet; } static class LocaleWrapper { Locale locale; } static class ClassWithBigIntegerId { @Id BigInteger id; } static class A<T> { String valueType; T value; public A(T value) { this.valueType = value.getClass().getName(); this.value = value; } } static class ClassWithIntId { @Id int id; } static class DefaultedConstructorArgument { String foo; int bar; double foobar; DefaultedConstructorArgument(String foo, int bar, double foobar) { this.foo = foo; this.bar = bar; this.foobar = foobar; } } static class Item { List<Attribute> attributes; } static class Attribute { String key; Object value; } static class Outer { class Inner { String value; } Inner inner; } static class URLWrapper { URL url; } static class ClassWithComplexId { @Id ComplexId complexId; } static class ComplexId { Long innerId; } static class TypWithCollectionConstructor { List<Attribute> attributes; public TypWithCollectionConstructor(List<Attribute> attributes) { this.attributes = attributes; } } @TypeAlias("_") static class Aliased { String name; } static class ThrowableWrapper { Throwable throwable; } @Document static class PrimitiveContainer { @Field("property") private final int m_property; public PrimitiveContainer(int a_property) { m_property = a_property; } public int property() { return m_property; } } @Document static class ObjectContainer { @Field("property") private final PrimitiveContainer m_property; public ObjectContainer(PrimitiveContainer a_property) { m_property = a_property; } public PrimitiveContainer property() { return m_property; } } static class RootForClassWithExplicitlyRenamedIdField { @Id String id; ClassWithExplicitlyRenamedField nested; } static class ClassWithExplicitlyRenamedField { @Field("id") String id; } static class RootForClassWithNamedIdField { String id; ClassWithNamedIdField nested; } static class ClassWithNamedIdField { String id; } static class ClassWithAnnotatedIdField { @Id String key; } static class ClassWithMapUsingEnumAsKey { static enum FooBarEnum { FOO, BAR; } Map<FooBarEnum, String> map; } static class FooBarEnumToStringConverter implements Converter<FooBarEnum, String> { @Override public String convert(FooBarEnum source) { if (source == null) { return null; } return FooBarEnum.FOO.equals(source) ? "foo-enum-value" : "bar-enum-value"; } } static class StringToFooNumConverter implements Converter<String, FooBarEnum> { @Override public FooBarEnum convert(String source) { if (source == null) { return null; } if (source.equals("foo-enum-value")) { return FooBarEnum.FOO; } if (source.equals("bar-enum-value")) { return FooBarEnum.BAR; } throw new ConversionNotSupportedException(source, String.class, null); } } }