From 5b5e2e5db81d634f965660397bcb6fedc423146e Mon Sep 17 00:00:00 2001 From: John Blum Date: Thu, 7 May 2020 21:26:18 -0700 Subject: [PATCH] Add abstract base class implementing the ObjectArrayToJsonConverter interface to encapsulate operation common to all implementations. The abstract base class provides convert methods to convert arrays, Iterables and Map.values(). Additionally it contains support to configure the ObjectToJsonConverter used to convert individual Objects into JSON. Resolves gh-67. --- .../AbstractObjectArrayToJsonConverter.java | 107 +++++++++++++ ...ctObjectArrayToJsonConverterUnitTests.java | 147 ++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 spring-geode/src/main/java/org/springframework/geode/data/json/converter/AbstractObjectArrayToJsonConverter.java create mode 100644 spring-geode/src/test/java/org/springframework/geode/data/json/converter/AbstractObjectArrayToJsonConverterUnitTests.java 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)); + } +}