diff --git a/spring-geode/src/main/java/org/springframework/geode/data/json/converter/AbstractObjectArrayToJsonConverter.java b/spring-geode/src/main/java/org/springframework/geode/data/json/converter/AbstractObjectArrayToJsonConverter.java new file mode 100644 index 00000000..1c488496 --- /dev/null +++ b/spring-geode/src/main/java/org/springframework/geode/data/json/converter/AbstractObjectArrayToJsonConverter.java @@ -0,0 +1,107 @@ +/* + * Copyright 2020 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 + * + * https://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.geode.data.json.converter; + +import java.util.Map; + +import org.springframework.data.gemfire.util.CollectionUtils; +import org.springframework.geode.data.json.converter.support.JacksonObjectToJsonConverter; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * An abstract base class implementing {@link ObjectArrayToJsonConverter} encapsulating functionality common + * to all implementations. + * + * @author John Blum + * @see java.lang.Iterable + * @see java.util.Map + * @see org.springframework.geode.data.json.converter.ObjectArrayToJsonConverter + * @since 1.3.0 + */ +public abstract class AbstractObjectArrayToJsonConverter implements ObjectArrayToJsonConverter { + + protected static final String BEGIN_ARRAY = "["; + protected static final String EMPTY_STRING = ""; + protected static final String END_ARRAY = "]"; + protected static final String JSON_OBJECT_SEPARATOR = ", "; + + private ObjectToJsonConverter converter = newObjectToJsonConverter(); + + // TODO configure via an SPI + private @NonNull ObjectToJsonConverter newObjectToJsonConverter() { + return new JacksonObjectToJsonConverter(); + } + + /** + * Returns a reference to the configured {@link ObjectToJsonConverter} used to convert + * individual {@link Object Objects} into {@link String JSON}. + * + * @return a reference to the configured {@link ObjectToJsonConverter}; never {@literal null}. + * @see org.springframework.geode.data.json.converter.ObjectToJsonConverter + */ + protected @NonNull ObjectToJsonConverter getObjectToJsonConverter() { + return this.converter; + } + + /** + * Converts the given {@link Iterable} of {@link Object Objects} into a {@link String JSON} array. + * + * @param iterable {@link Iterable} containing the {@link Object Objects} to convert into {@link String JSON}; + * must not be {@literal null}. + * @return the {@link String JSON} generated from the given {@link Iterable} of {@link Object Objects}; + * never {@literal null}. + * @throws IllegalArgumentException if {@link Iterable} is {@literal null}. + * @see #getObjectToJsonConverter() + * @see java.lang.Iterable + */ + @Override + public @NonNull String convert(@NonNull Iterable iterable) { + + Assert.notNull(iterable, "Iterable must not be null"); + + StringBuilder json = new StringBuilder(BEGIN_ARRAY); + + ObjectToJsonConverter converter = getObjectToJsonConverter(); + + boolean addComma = false; + + for (Object value : CollectionUtils.nullSafeIterable(iterable)) { + json.append(addComma ? JSON_OBJECT_SEPARATOR : EMPTY_STRING); + json.append(converter.convert(value)); + addComma = true; + } + + json.append(END_ARRAY); + + return json.toString(); + } + + /** + * Converts the {@link Map#values() values} from the given {@link Map} into {@link String JSON}. + * + * @param {@link Class} type of the {@link Map#keySet() keys}. + * @param {@link Class} type of the {@link Map#values() values}. + * @param map {@link Map} containing the {@link Map#values() values} to convert into {@link String JSON}. + * @return {@link String JSON} generated from the {@link Map#values() values} in the given {@link Map}. + * @see #convert(Iterable) + * @see java.util.Map + */ + public @NonNull String convert(@Nullable Map map) { + return convert(CollectionUtils.nullSafeMap(map).values()); + } +} diff --git a/spring-geode/src/test/java/org/springframework/geode/data/json/converter/AbstractObjectArrayToJsonConverterUnitTests.java b/spring-geode/src/test/java/org/springframework/geode/data/json/converter/AbstractObjectArrayToJsonConverterUnitTests.java new file mode 100644 index 00000000..0af748cc --- /dev/null +++ b/spring-geode/src/test/java/org/springframework/geode/data/json/converter/AbstractObjectArrayToJsonConverterUnitTests.java @@ -0,0 +1,147 @@ +/* + * Copyright 2020 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 + * + * https://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.geode.data.json.converter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doReturn; +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 java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import org.junit.Test; +import org.mockito.ArgumentMatchers; + +import example.app.crm.model.Customer; + +/** + * Unit Tests for {@link AbstractObjectArrayToJsonConverter}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.geode.data.json.converter.AbstractObjectArrayToJsonConverter + * @since 1.3.0 + */ +public class AbstractObjectArrayToJsonConverterUnitTests { + + @Test + public void convertIterable() { + + Customer jonDoe = Customer.newCustomer(1L, "Jon Doe"); + Customer janeDoe = Customer.newCustomer(2L, "Jane Doe"); + + Iterable iterable = Arrays.asList(jonDoe, janeDoe); + + String jonDoeJson = "{ \"name\": \"Jon Doe\" }"; + String janeDoeJson = "{ \"name\": \"Jane Doe\" }"; + String json = String.format("[%s, %s]", jonDoeJson, janeDoeJson); + + ObjectToJsonConverter mockConverter = mock(ObjectToJsonConverter.class); + + doReturn(jonDoeJson).when(mockConverter).convert(eq(jonDoe)); + doReturn(janeDoeJson).when(mockConverter).convert(eq(janeDoe)); + + AbstractObjectArrayToJsonConverter converter = mock(AbstractObjectArrayToJsonConverter.class); + + doCallRealMethod().when(converter).convert(ArgumentMatchers.>any()); + doReturn(mockConverter).when(converter).getObjectToJsonConverter(); + + assertThat(converter.convert(iterable)).isEqualTo(json); + + verify(converter, times(1)).getObjectToJsonConverter(); + verify(mockConverter, times(1)).convert(eq(jonDoe)); + verify(mockConverter, times(1)).convert(eq(janeDoe)); + } + + @Test + public void convertEmptyIterable() { + + String json = "[]"; + + AbstractObjectArrayToJsonConverter converter = mock(AbstractObjectArrayToJsonConverter.class); + + doCallRealMethod().when(converter).convert(ArgumentMatchers.>any()); + + assertThat(converter.convert(Collections.emptySet())).isEqualTo(json); + + verify(converter, times(1)).getObjectToJsonConverter(); + } + + @Test(expected = IllegalArgumentException.class) + public void convertNullIterable() { + + AbstractObjectArrayToJsonConverter converter = mock(AbstractObjectArrayToJsonConverter.class); + + doCallRealMethod().when(converter).convert(ArgumentMatchers.>any()); + + try { + converter.convert((Iterable) null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Iterable must not be null"); + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + verify(converter, never()).getObjectToJsonConverter(); + } + } + + @Test + @SuppressWarnings("unchecked") + public void convertMap() { + + String json = "[{ ... }, { ... }]"; + + Map mockMap = mock(Map.class); + + AbstractObjectArrayToJsonConverter converter = mock(AbstractObjectArrayToJsonConverter.class); + + doCallRealMethod().when(converter).convert(any(Map.class)); + doReturn(json).when(converter).convert(any(Iterable.class)); + + assertThat(converter.convert(mockMap)).isEqualTo(json); + + verify(converter, times(1)).convert(eq(Collections.emptyList())); + } + + @Test + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void convertMapIsNullSafe() { + + String json = "[]"; + + AbstractObjectArrayToJsonConverter converter = mock(AbstractObjectArrayToJsonConverter.class); + + doCallRealMethod().when(converter).convert(ArgumentMatchers.any()); + doReturn(json).when(converter).convert(any(Iterable.class)); + + assertThat(converter.convert((Map) null)).isEqualTo(json); + + verify(converter, times(1)).convert(isA(Iterable.class)); + } +}