From 2d1cc281881bdf9cfcd08c3c139c5882efb1581d Mon Sep 17 00:00:00 2001 From: John Blum Date: Mon, 20 Jul 2020 13:25:26 -0700 Subject: [PATCH] Refactor JsonCacheDataImporterExporter to extend ResourceCapableCacheDataImporterExporter. Change doImportInto(:Region) logic to make use of the ImportResourceResolver and ResourceReader on import. Change doExportFrom(:Region) logic to make use of the ExportResourceResolver and ResourceWriter on export. Resolves gh-92. --- .../json/JsonCacheDataImporterExporter.java | 219 +++----- ...eDataImporterExporterIntegrationTests.java | 296 ++++++----- ...sonCacheDataImporterExporterUnitTests.java | 500 ++++++------------ ...eDataImporterExporterIntegrationTests.java | 21 +- 4 files changed, 404 insertions(+), 632 deletions(-) diff --git a/spring-geode/src/main/java/org/springframework/geode/data/json/JsonCacheDataImporterExporter.java b/spring-geode/src/main/java/org/springframework/geode/data/json/JsonCacheDataImporterExporter.java index 66d4d2c4..de5f9a36 100644 --- a/spring-geode/src/main/java/org/springframework/geode/data/json/JsonCacheDataImporterExporter.java +++ b/spring-geode/src/main/java/org/springframework/geode/data/json/JsonCacheDataImporterExporter.java @@ -15,29 +15,21 @@ */ package org.springframework.geode.data.json; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.Writer; import java.util.Arrays; -import java.util.Optional; import org.apache.geode.cache.Region; import org.apache.geode.pdx.PdxInstance; -import org.springframework.core.io.ClassPathResource; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; -import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.data.gemfire.util.ArrayUtils; import org.springframework.data.gemfire.util.CollectionUtils; -import org.springframework.geode.data.AbstractCacheDataImporterExporter; import org.springframework.geode.data.CacheDataExporter; import org.springframework.geode.data.CacheDataImporter; import org.springframework.geode.data.json.converter.AbstractObjectArrayToJsonConverter; import org.springframework.geode.data.json.converter.JsonToPdxArrayConverter; import org.springframework.geode.data.json.converter.support.JacksonJsonToPdxConverter; +import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter; import org.springframework.geode.pdx.ObjectPdxInstanceAdapter; import org.springframework.geode.pdx.PdxInstanceWrapper; import org.springframework.geode.util.CacheUtils; @@ -45,44 +37,65 @@ import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * The {@link JsonCacheDataImporterExporter} class is a {@link CacheDataImporter} and {@link CacheDataExporter} * implementation that can export/import JSON data to/from a {@link Resource} given a target {@link Region}. * * @author John Blum - * @see java.io.File * @see org.apache.geode.cache.Region * @see org.apache.geode.pdx.PdxInstance - * @see org.springframework.core.io.ClassPathResource * @see org.springframework.core.io.Resource - * @see org.springframework.core.io.ResourceLoader - * @see org.springframework.geode.data.AbstractCacheDataImporterExporter * @see org.springframework.geode.data.CacheDataExporter * @see org.springframework.geode.data.CacheDataImporter * @see org.springframework.geode.data.json.converter.JsonToPdxArrayConverter - * @see org.springframework.geode.data.json.converter.ObjectToJsonConverter - * @see org.springframework.geode.data.json.converter.support.JSONFormatterPdxToJsonConverter + * @see org.springframework.geode.data.json.converter.ObjectArrayToJsonConverter * @see org.springframework.geode.data.json.converter.support.JacksonJsonToPdxConverter + * @see org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter + * @see org.springframework.geode.pdx.ObjectPdxInstanceAdapter * @see org.springframework.geode.pdx.PdxInstanceWrapper * @see org.springframework.stereotype.Component * @since 1.3.0 */ @Component @SuppressWarnings("rawtypes") -public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExporter { +public class JsonCacheDataImporterExporter extends ResourceCapableCacheDataImporterExporter { - private static final boolean APPEND_TO_FILE = false; + protected static final PdxInstance[] EMPTY_PDX_INSTANCE_ARRAY = {}; - private static final int CONTENT_PREVIEW_LENGTH = 50; - private static final int DEFAULT_BUFFER_SIZE = 32768; - - private JsonToPdxArrayConverter jsonToPdxArrayConverter = newJsonToPdxArrayConverter(); + @Autowired(required = false) + private JsonToPdxArrayConverter jsonToPdxArrayConverter; private final RegionValuesToJsonConverter regionValuesToJsonConverter = new RegionValuesToJsonConverter(); - // TODO configure via an SPI or DI + /** + * Determines whether the given array is empty or not. An array is not empty if the array reference + * is not {@literal null} and contains at least 1 element. + * + * @param {@link Class type} of the array elements. + * @param array {@link Object} array to evaluate. + * @return a boolean value indicating whether the array is empty or not. + */ + @SuppressWarnings("unchecked") + private static boolean isNotEmpty(T... array) { + return array != null && array.length > 0; + } + + /** + * Initializes the JSON to PDX (array) converter. + * + * @see #newJsonToPdxArrayConverter() + */ + @Override + public void afterPropertiesSet() { + + super.afterPropertiesSet(); + + this.jsonToPdxArrayConverter = this.jsonToPdxArrayConverter != null + ? this.jsonToPdxArrayConverter + : newJsonToPdxArrayConverter(); + } + private @NonNull JsonToPdxArrayConverter newJsonToPdxArrayConverter() { return new JacksonJsonToPdxConverter(); } @@ -105,150 +118,52 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo Assert.notNull(region, "Region must not be null"); - String json = toJson(region); + getExportResourceResolver() + .resolve(region) + .ifPresent(resource -> { - getLogger().debug("Saving JSON [{}] from Region [{}]", json, region.getName()); + String json = toJson(region); - getResource(region, getResourceLocation()) - .ifPresent(resource -> save(json, resource)); + getLogger().debug("Saving JSON [{}] from Region [{}]", json, region.getName()); + + getResourceWriter().write(resource, json.getBytes()); + }); return region; } - /** - * Saves the given {@link String content} (e.g. JSON) to the specified {@link Resource}. - * - * @param content {@link String} containing the content to save. - * @param resource {@link Resource} to save the {@link String content} to; must not be {@literal null}. - * @throws IllegalArgumentException if {@link Resource} is {@literal null}. - * @see org.springframework.core.io.Resource - */ - protected void save(@Nullable String content, @NonNull Resource resource) { - - Assert.notNull(resource, "Resource must not be null"); - - if (StringUtils.hasText(content)) { - try (Writer writer = newWriter(resource)) { - writer.write(content, 0, content.length()); - writer.flush(); - } - catch (IOException cause) { - - String message = String.format("Failed to save content '%s' to Resource [%s]", - formatContentForPreview(content), resource.getDescription()); - - throw new DataAccessResourceFailureException(message, cause); - } - } - } - - @NonNull Writer newWriter(@NonNull Resource resource) throws IOException { - return new BufferedWriter(new FileWriter(resource.getFile(), APPEND_TO_FILE)); - } - - @Nullable String formatContentForPreview(@Nullable String content) { - - if (StringUtils.hasText(content)) { - - int length = Math.min(content.length(), CONTENT_PREVIEW_LENGTH); - - content = content.substring(0, length).concat(length < content.length() ? "..." : ""); - } - - return content; - } - /** * @inheritDoc */ - @NonNull @Override @SuppressWarnings("unchecked") + @NonNull @Override public Region doImportInto(@NonNull Region region) { Assert.notNull(region, "Region must not be null"); - getResource(region, CLASSPATH_RESOURCE_PREFIX) - .filter(Resource::exists) - .map(this::getContent) + getImportResourceResolver() + .resolve(region) + .map(this.getResourceReader()::read) .map(this::toPdx) - .map(Arrays::stream) - .ifPresent(pdxInstances -> pdxInstances.forEach(pdxInstance -> - region.put(resolveKey(pdxInstance), resolveValue(pdxInstance)))); + .ifPresent(pdxInstances -> regionPutPdx(region, pdxInstances)); return region; } /** - * Gets the contents of the given {@link Resource} as an array of {@literal Byte#TYPE bytes}. + * Puts all PDX data from the {@link PdxInstance} array into the target {@link Region} mapped to + * the PDX {@link PdxInstance#isIdentityField(String) identifier} as the {@literal key}. * - * @param resource {@link Resource} from which to load the content; must not be {@literal null}. - * @return an array of {@link Byte#TYPE bytes} containing the contents of the given {@link Resource}. - * @throws IllegalArgumentException if {@link Resource} is {@literal null}. - * @throws DataAccessResourceFailureException if an {@link IOException} occurs while reading from - * and loading the contents of the given {@link Resource}. - * @see org.springframework.core.io.Resource - */ - protected @NonNull byte[] getContent(@NonNull Resource resource) { - - Assert.notNull(resource, "Resource must not be null"); - - try (InputStream in = resource.getInputStream()){ - - ByteArrayOutputStream out = new ByteArrayOutputStream(in.available()); - - byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; - - for (int bytesRead = in.read(buffer); bytesRead != -1; bytesRead = in.read(buffer)) { - out.write(buffer, 0, bytesRead); - out.flush(); - } - - out.flush(); - out.close(); - - return out.toByteArray(); - } - catch (IOException cause) { - throw new DataAccessResourceFailureException(String.format("Failed to read from Resource [%s]", - resource.getDescription()), cause); - } - } - - /** - * Returns a computed JSON {@link Resource} containing JSON data to be loaded into the given {@link Region}. - * - * @param region target {@link Region} used to compute the JSON {@link Resource} to load. - * @param resourcePrefix {@link String} containing a path prefix (e.g. {@literal classpath:} used to indicate - * the location of the {@link Resource} to load. - * @return an {@link Optional} JSON {@link Resource} containing JSON data to load into the given {@link Region}. - * @see org.springframework.core.io.Resource + * @param region target {@link Region} to store the PDX data; must not be {@literal null} + * @param pdx {@link PdxInstance} array containing the PDX data to store in the target {@link Region}. * @see org.apache.geode.cache.Region - * @see java.util.Optional + * @see org.apache.geode.cache.Region#put(Object, Object) + * @see org.apache.geode.pdx.PdxInstance */ - protected Optional getResource(@NonNull Region region, @Nullable String resourcePrefix) { + @SuppressWarnings("unchecked") + void regionPutPdx(@NonNull Region region, @Nullable PdxInstance[] pdx) { - Assert.notNull(region, "Region must not be null"); - - String regionName = region.getName().toLowerCase(); - String resourceName = String.format(RESOURCE_NAME_PATTERN, regionName); - String resolvedResourcePrefix = StringUtils.hasText(resourcePrefix) - ? resourcePrefix - : CLASSPATH_RESOURCE_PREFIX; - - Resource resource = getApplicationContext() - .map(it -> it.getResource(String.format("%1$s%2$s", resolvedResourcePrefix, resourceName))) - .orElseGet((() -> new ClassPathResource(resourceName))); - - return Optional.of(resource); - } - - /** - * Returns a {@link String file system path} specifying the location for where to export the {@link Resource}. - * - * @return a {@link String file system path} specifying the location for where to export the {@link Resource}. - */ - protected @NonNull String getResourceLocation() { - return String.format("%1$s%2$s%2$s%3$s%4$s", FILESYSTEM_RESOURCE_PREFIX, RESOURCE_PATH_SEPARATOR, - System.getProperty("user.dir"), File.separator); + Arrays.stream(ArrayUtils.nullSafeArray(pdx, PdxInstance.class)).forEach(pdxInstance -> + region.put(resolveKey(pdxInstance), resolveValue(pdxInstance))); } /** @@ -319,9 +234,19 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo * @see #getJsonToPdxArrayConverter() */ protected @NonNull PdxInstance[] toPdx(@NonNull byte[] json) { - return getJsonToPdxArrayConverter().convert(json); + + return isNotEmpty(json) + ? getJsonToPdxArrayConverter().convert(json) + : EMPTY_PDX_INSTANCE_ARRAY; } + /** + * Converts all {@link Region#values() values} in the targeted {@link Region} into {@literal JSON}. + * + * The converter is capable of handling both {@link Object Objects} and PDX. + * + * @see org.springframework.geode.data.json.converter.AbstractObjectArrayToJsonConverter + */ static class RegionValuesToJsonConverter extends AbstractObjectArrayToJsonConverter { @NonNull String convert(@NonNull Region region) { diff --git a/spring-geode/src/test/java/org/springframework/geode/data/json/JsonCacheDataImporterExporterIntegrationTests.java b/spring-geode/src/test/java/org/springframework/geode/data/json/JsonCacheDataImporterExporterIntegrationTests.java index 63074326..94e96699 100644 --- a/spring-geode/src/test/java/org/springframework/geode/data/json/JsonCacheDataImporterExporterIntegrationTests.java +++ b/spring-geode/src/test/java/org/springframework/geode/data/json/JsonCacheDataImporterExporterIntegrationTests.java @@ -42,7 +42,6 @@ import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -58,17 +57,21 @@ import org.apache.geode.pdx.PdxInstanceFactory; import org.apache.geode.pdx.PdxSerializationException; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.MutablePropertySources; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.data.gemfire.LocalRegionFactoryBean; import org.springframework.data.gemfire.config.annotation.EnablePdx; import org.springframework.data.gemfire.config.annotation.PeerCacheApplication; -import org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport; +import org.springframework.data.gemfire.tests.integration.SpringApplicationContextIntegrationTestsSupport; +import org.springframework.geode.core.io.ResourceWriteException; +import org.springframework.geode.core.io.ResourceWriter; import org.springframework.geode.core.util.ObjectUtils; +import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ImportResourceResolver; import org.springframework.geode.pdx.PdxInstanceBuilder; -import org.springframework.lang.NonNull; +import org.springframework.mock.env.MockPropertySource; import org.springframework.util.FileCopyUtils; import org.springframework.util.StringUtils; @@ -78,11 +81,12 @@ import example.app.pos.model.Product; import example.app.pos.model.PurchaseOrder; /** - * Integration Test for {@link JsonCacheDataImporterExporter}. + * Integration Tests for {@link JsonCacheDataImporterExporter}. * * @author John Blum * @see org.junit.Test * @see com.fasterxml.jackson.databind.ObjectMapper + * @see com.fasterxml.jackson.databind.node.ObjectNode * @see org.apache.geode.cache.Cache * @see org.apache.geode.cache.Region * @see org.apache.geode.cache.RegionService @@ -90,65 +94,58 @@ import example.app.pos.model.PurchaseOrder; * @see org.apache.geode.pdx.PdxInstance * @see org.apache.geode.pdx.PdxInstanceFactory * @see org.springframework.context.ConfigurableApplicationContext - * @see org.springframework.context.annotation.AnnotationConfigApplicationContext * @see org.springframework.context.annotation.Bean * @see org.springframework.core.io.ClassPathResource * @see org.springframework.core.io.Resource * @see org.springframework.data.gemfire.LocalRegionFactoryBean * @see org.springframework.data.gemfire.config.annotation.EnablePdx * @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication - * @see org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport + * @see org.springframework.data.gemfire.tests.integration.SpringApplicationContextIntegrationTestsSupport * @see org.springframework.geode.pdx.PdxInstanceBuilder * @since 1.3.0 */ @SuppressWarnings("unused") -public class JsonCacheDataImporterExporterIntegrationTests extends IntegrationTestsSupport { +public class JsonCacheDataImporterExporterIntegrationTests extends SpringApplicationContextIntegrationTestsSupport { private static final String EXPORT_ENABLED_PROPERTY = "spring.boot.data.gemfire.cache.data.export.enabled"; + private static final String EXPORT_RESOURCE_LOCATION_PROPERTY = "spring.boot.data.gemfire.cache.data.export.resource.location"; + private static final String IMPORT_ENABLED_PROPERTY = "spring.boot.data.gemfire.cache.data.import.enabled"; + private static final String IMPORT_RESOURCE_LOCATION_PROPERTY = "spring.boot.data.gemfire.cache.data.import.resource.location"; - private static volatile Supplier resourceSupplier; - - private static volatile Supplier writerSupplier; - - private ConfigurableApplicationContext applicationContext; + private static volatile Supplier exportEnabledSupplier; + private static volatile Supplier importEnabledSupplier; + private static volatile Supplier importResourceResolverSupplier; + private static volatile Supplier resourceLocationSupplier; + private static volatile Supplier resourceWriterSupplier; @Before public void initializeSuppliers() { - resourceSupplier = () -> null; - writerSupplier = StringWriter::new; + + exportEnabledSupplier = () -> false; + importEnabledSupplier = () -> true; + importResourceResolverSupplier = () -> null; + resourceLocationSupplier = () -> ""; + resourceWriterSupplier = StringWriter::new; } - @After - public void closeApplicationContext() { + @Override + protected ConfigurableApplicationContext processBeforeRefresh(ConfigurableApplicationContext applicationContext) { - Optional.ofNullable(this.applicationContext) - .ifPresent(ConfigurableApplicationContext::close); + applicationContext = super.processBeforeRefresh(applicationContext); - this.applicationContext = null; - } + MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources(); - private ConfigurableApplicationContext newApplicationContext(Class... componentClasses) { + MockPropertySource mockPropertySource = new MockPropertySource(getClass().getName().concat(".MockPropertySource")) + .withProperty(EXPORT_ENABLED_PROPERTY, exportEnabledSupplier.get()) + .withProperty(EXPORT_RESOURCE_LOCATION_PROPERTY, resourceLocationSupplier.get()) + .withProperty(IMPORT_ENABLED_PROPERTY, importEnabledSupplier.get()) + .withProperty(IMPORT_RESOURCE_LOCATION_PROPERTY, resourceLocationSupplier.get()); - AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); - - applicationContext.register(componentClasses); - applicationContext.registerShutdownHook(); - applicationContext.refresh(); - - this.applicationContext = applicationContext; + propertySources.addFirst(mockPropertySource); return applicationContext; } - private Class[] withResource(String path) { - - if (StringUtils.hasText(path)) { - resourceSupplier = () -> new ClassPathResource(path); - } - - return new Class[] { TestGeodeConfiguration.class }; - } - private Region assertExampleRegion(Region example) { assertThat(example).isNotNull(); @@ -168,7 +165,9 @@ public class JsonCacheDataImporterExporterIntegrationTests extends IntegrationTe @Test public void exampleRegionContainsJonDoe() { - Region example = getExampleRegion(newApplicationContext(withResource("data-example-jondoe.json"))); + resourceLocationSupplier = () -> "classpath:data-#{#regionName}-jondoe.json"; + + Region example = getExampleRegion(newApplicationContext(TestGeodeConfiguration.class)); assertThat(example).hasSize(1); @@ -194,8 +193,9 @@ public class JsonCacheDataImporterExporterIntegrationTests extends IntegrationTe @Test public void exampleRegionContainsDoeFamily() { - Region example = - getExampleRegion(newApplicationContext(withResource("data-example-doefamily.json"))); + resourceLocationSupplier = () -> "classpath:data-#{#regionName}-doefamily.json"; + + Region example = getExampleRegion(newApplicationContext(TestGeodeConfiguration.class)); assertThat(example).hasSize(9); @@ -319,8 +319,9 @@ public class JsonCacheDataImporterExporterIntegrationTests extends IntegrationTe @Test public void exampleRegionContainsComplexPurchaseOrderType() { - Region example = - getExampleRegion(newApplicationContext(withResource("data-example-purchaseorder.json"))); + resourceLocationSupplier = () -> "classpath:data-#{#regionName}-purchaseorder.json"; + + Region example = getExampleRegion(newApplicationContext(TestGeodeConfiguration.class)); assertThat(example).hasSize(1); @@ -345,14 +346,14 @@ public class JsonCacheDataImporterExporterIntegrationTests extends IntegrationTe public void exportFromExampleRegionToJson() { try { - System.setProperty(EXPORT_ENABLED_PROPERTY, Boolean.TRUE.toString()); StringWriter writer = new StringWriter(); - writerSupplier = () -> writer; + exportEnabledSupplier = () -> true; + importEnabledSupplier = () -> false; + resourceWriterSupplier = () -> writer; - Region example = - getExampleRegion(newApplicationContext(withResource("data-example.json"))); + Region example = getExampleRegion(newApplicationContext(TestGeodeConfiguration.class)); assertExampleRegion(example); assertThat(example).isEmpty(); @@ -381,137 +382,125 @@ public class JsonCacheDataImporterExporterIntegrationTests extends IntegrationTe @Test public void exportFromExampleRegionImportsIntoExampleRegion() throws IOException { - try { - // EXPORT - System.setProperty(EXPORT_ENABLED_PROPERTY, Boolean.TRUE.toString()); + // EXPORT + StringWriter writer = new StringWriter(); - StringWriter writer = new StringWriter(); + exportEnabledSupplier = () -> true; + importEnabledSupplier = () -> false; + resourceWriterSupplier = () -> writer; - writerSupplier = () -> writer; + Region example = getExampleRegion(newApplicationContext(TestGeodeConfiguration.class)); - Region example = - getExampleRegion(newApplicationContext(withResource("data-example.json"))); + assertExampleRegion(example); + assertThat(example).isEmpty(); - assertExampleRegion(example); - assertThat(example).isEmpty(); + Product golfBalls = Product.newProduct("Titliest ProV1x Golf Balls") + .havingPrice(BigDecimal.valueOf(34.99d)) + .in(Product.Category.SPECIALTY); - Product golfBalls = Product.newProduct("Titliest ProV1x Golf Balls") - .havingPrice(BigDecimal.valueOf(34.99d)) - .in(Product.Category.SPECIALTY); + LineItem lineItem = LineItem.newLineItem(golfBalls) + .withQuantity(1); - LineItem lineItem = LineItem.newLineItem(golfBalls) - .withQuantity(1); + PurchaseOrder purchaseOrder = new PurchaseOrder() + .identifiedAs(72L) + .add(lineItem); - PurchaseOrder purchaseOrder = new PurchaseOrder() - .identifiedAs(72L) - .add(lineItem); + assertThat(example.put(purchaseOrder.getId(), purchaseOrder)).isNull(); + assertThat(example).hasSize(1); + assertPurchaseOrder(example.get(purchaseOrder.getId()), purchaseOrder.getId(), + Collections.singletonList(lineItem), golfBalls.getPrice()); - assertThat(example.put(purchaseOrder.getId(), purchaseOrder)).isNull(); - assertThat(example).hasSize(1); - assertPurchaseOrder(example.get(purchaseOrder.getId()), purchaseOrder.getId(), - Collections.singletonList(lineItem), golfBalls.getPrice()); + //log("JSON from JSONFormatter '%s'%n", + // JSONFormatter.toJSON(serializeToPdx(example.getRegionService(), purchaseOrder))); - //log("JSON from JSONFormatter '%s'%n", - // JSONFormatter.toJSON(serializeToPdx(example.getRegionService(), purchaseOrder))); + closeApplicationContext(); - closeApplicationContext(); + String json = writer.toString(); - String json = writer.toString(); + assertThat(json).isNotEmpty(); - assertThat(json).isNotEmpty(); + //log("JSON '%s'%n", json); + //log("PurchaseOrder from JSON [%s]%n", + // newObjectMapper().readValue(json.substring(1, json.length() - 1), PurchaseOrder.class)); - //log("JSON '%s'%n", json); - //log("PurchaseOrder from JSON [%s]%n", - // newObjectMapper().readValue(json.substring(1, json.length() - 1), PurchaseOrder.class)); + // IMPORT + Resource mockResource = mock(Resource.class, withSettings().lenient()); - // IMPORT - System.clearProperty(EXPORT_ENABLED_PROPERTY); + doReturn("MOCK").when(mockResource).getDescription(); + doReturn(new ByteArrayInputStream(json.getBytes())).when(mockResource).getInputStream(); - Resource mockResource = mock(Resource.class, withSettings().lenient()); + exportEnabledSupplier = () -> false; + importEnabledSupplier = () -> true; + importResourceResolverSupplier = () -> (ImportResourceResolver) region -> Optional.of(mockResource); - doReturn(true).when(mockResource).exists(); - doReturn("MOCK").when(mockResource).getDescription(); - doReturn(new ByteArrayInputStream(json.getBytes())).when(mockResource).getInputStream(); + example = getExampleRegion(newApplicationContext(TestGeodeConfiguration.class, + TestImportResourceResolverGeodeConfiguration.class)); - resourceSupplier = () -> mockResource; + assertExampleRegion(example); + assertThat(example).hasSize(1); - example = getExampleRegion(newApplicationContext(withResource(null))); + Object value = example.values().stream().findFirst().orElse(null); - assertExampleRegion(example); - assertThat(example).hasSize(1); + assertThat(value).isInstanceOf(PdxInstance.class); - Object value = example.values().stream().findFirst().orElse(null); - - assertThat(value).isInstanceOf(PdxInstance.class); - - assertPurchaseOrder(ObjectUtils.asType(value, PurchaseOrder.class), - purchaseOrder.getId(), Collections.singletonList(lineItem), golfBalls.getPrice()); - } - finally { - System.clearProperty(EXPORT_ENABLED_PROPERTY); - } + assertPurchaseOrder(ObjectUtils.asType(value, PurchaseOrder.class), + purchaseOrder.getId(), Collections.singletonList(lineItem), golfBalls.getPrice()); } @Test public void exportImportWithRegionContainingObjectsAndPdxInstances() throws IOException { - try { - // EXPORT - System.setProperty(EXPORT_ENABLED_PROPERTY, Boolean.TRUE.toString()); + // EXPORT + StringWriter writer = new StringWriter(); - StringWriter writer = new StringWriter(); + exportEnabledSupplier = () -> true; + importEnabledSupplier = () -> false; + resourceWriterSupplier = () -> writer; - writerSupplier = () -> writer; + Region example = getExampleRegion(newApplicationContext(TestGeodeConfiguration.class)); - Region example = - getExampleRegion(newApplicationContext(withResource("data-example.json"))); + assertExampleRegion(example); + assertThat(example).isEmpty(); - assertExampleRegion(example); - assertThat(example).isEmpty(); + Customer jonDoe = Customer.newCustomer(1L, "Jon Doe"); + Customer janeDoe = Customer.newCustomer(2L, "JaneDoe"); - Customer jonDoe = Customer.newCustomer(1L, "Jon Doe"); - Customer janeDoe = Customer.newCustomer(2L, "JaneDoe"); + PdxInstance janeDoePdx = serializeToPdx(example.getRegionService(), janeDoe); - PdxInstance janeDoePdx = serializeToPdx(example.getRegionService(), janeDoe); + assertThat(example.put(jonDoe.getId(), jonDoe)).isNull(); + assertThat(example.put(janeDoe.getId(), janeDoePdx)).isNull(); + assertThat(example).hasSize(2); + assertThat(example.get(jonDoe.getId())).isEqualTo(jonDoe); + assertThat(example.get(janeDoe.getId())).isInstanceOf(PdxInstance.class); - assertThat(example.put(jonDoe.getId(), jonDoe)).isNull(); - assertThat(example.put(janeDoe.getId(), janeDoePdx)).isNull(); - assertThat(example).hasSize(2); - assertThat(example.get(jonDoe.getId())).isEqualTo(jonDoe); - assertThat(example.get(janeDoe.getId())).isInstanceOf(PdxInstance.class); + closeApplicationContext(); - closeApplicationContext(); + String json = writer.toString(); - String json = writer.toString(); + assertThat(json).isNotEmpty(); - assertThat(json).isNotEmpty(); + // IMPORT + Resource mockResource = mock(Resource.class, withSettings().lenient()); - // IMPORT - System.clearProperty(EXPORT_ENABLED_PROPERTY); + doReturn("MOCK").when(mockResource).getDescription(); + doReturn(new ByteArrayInputStream(json.getBytes())).when(mockResource).getInputStream(); - Resource mockResource = mock(Resource.class, withSettings().lenient()); + exportEnabledSupplier = () -> false; + importEnabledSupplier = () -> true; + importResourceResolverSupplier = () -> region -> Optional.of(mockResource); - doReturn(true).when(mockResource).exists(); - doReturn("MOCK").when(mockResource).getDescription(); - doReturn(new ByteArrayInputStream(json.getBytes())).when(mockResource).getInputStream(); + example = getExampleRegion(newApplicationContext(TestGeodeConfiguration.class, - resourceSupplier = () -> mockResource; + TestImportResourceResolverGeodeConfiguration.class)); + assertExampleRegion(example); + assertThat(example).hasSize(2); - example = getExampleRegion(newApplicationContext(withResource(null))); + for (Customer doe : Arrays.asList(jonDoe, janeDoe)) { - assertExampleRegion(example); - assertThat(example).hasSize(2); + Object value = example.get(doe.getId().byteValue()); - for (Customer doe : Arrays.asList(jonDoe, janeDoe)) { - - Object value = example.get(doe.getId().byteValue()); - - assertThat(value).isInstanceOf(PdxInstance.class); - assertThat(((PdxInstance) value).getObject()).isEqualTo(doe); - } - } - finally { - System.clearProperty(EXPORT_ENABLED_PROPERTY); + assertThat(value).isInstanceOf(PdxInstance.class); + assertThat(((PdxInstance) value).getObject()).isEqualTo(doe); } } @@ -677,23 +666,40 @@ public class JsonCacheDataImporterExporterIntegrationTests extends IntegrationTe } @Bean - JsonCacheDataImporterExporter exampleRegionDataImporter() { + JsonCacheDataImporterExporter exampleRegionDataImporterExporter() { + return new JsonCacheDataImporterExporter(); + } - return new JsonCacheDataImporterExporter() { + @Bean + ResourceWriter stringWriterResourceWriter() { - @Override @SuppressWarnings("rawtypes") - protected Optional getResource(@NonNull Region region, String resourcePrefix) { - return Optional.ofNullable(resourceSupplier.get()); + return (resource, data) -> { + + String json = new String(data); + + Writer writer = resourceWriterSupplier.get(); + + try { + writer.write(json, 0, data.length); + writer.flush(); } - - @NonNull @Override - Writer newWriter(@NonNull Resource resource) { - return writerSupplier.get(); + catch (IOException cause) { + throw new ResourceWriteException(String.format("Failed to write data [%s] to Resource [%s]", + json, resource.getDescription()), cause); } }; } } + @Configuration + static class TestImportResourceResolverGeodeConfiguration { + + @Bean + ImportResourceResolver testImportResourceResolver() { + return importResourceResolverSupplier.get(); + } + } + public static class TimedType { public static TimedType create() { diff --git a/spring-geode/src/test/java/org/springframework/geode/data/json/JsonCacheDataImporterExporterUnitTests.java b/spring-geode/src/test/java/org/springframework/geode/data/json/JsonCacheDataImporterExporterUnitTests.java index 5dc743b6..7fbc44fd 100644 --- a/spring-geode/src/test/java/org/springframework/geode/data/json/JsonCacheDataImporterExporterUnitTests.java +++ b/spring-geode/src/test/java/org/springframework/geode/data/json/JsonCacheDataImporterExporterUnitTests.java @@ -17,42 +17,34 @@ package org.springframework.geode.data.json; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import org.apache.geode.cache.Region; import org.apache.geode.pdx.PdxInstance; -import org.springframework.context.ApplicationContext; -import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; -import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.data.gemfire.util.ArrayUtils; +import org.springframework.geode.core.io.ResourceReader; +import org.springframework.geode.core.io.ResourceWriter; import org.springframework.geode.data.json.converter.JsonToPdxArrayConverter; +import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ExportResourceResolver; +import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ImportResourceResolver; +import org.springframework.lang.NonNull; /** * Unit Tests for {@link JsonCacheDataImporterExporter}. @@ -63,19 +55,19 @@ import org.springframework.geode.data.json.converter.JsonToPdxArrayConverter; * @see org.mockito.junit.MockitoJUnitRunner * @see org.apache.geode.cache.Region * @see org.apache.geode.pdx.PdxInstance - * @see org.springframework.context.ApplicationContext - * @see org.springframework.core.io.ClassPathResource * @see org.springframework.core.io.Resource + * @see org.springframework.geode.core.io.ResourceReader + * @see org.springframework.geode.core.io.ResourceWriter * @see org.springframework.geode.data.json.JsonCacheDataImporterExporter - * @see org.springframework.geode.data.json.converter.JsonToPdxArrayConverter - * @see org.springframework.geode.data.json.converter.ObjectToJsonConverter + * @see org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ExportResourceResolver + * @see org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ImportResourceResolver * @since 1.3.0 */ @RunWith(MockitoJUnitRunner.class) public class JsonCacheDataImporterExporterUnitTests { @Spy - private JsonCacheDataImporterExporter importer; + private TestJsonCacheDataImporterExporter importerExporter; @Test @SuppressWarnings("unchecked") @@ -85,28 +77,56 @@ public class JsonCacheDataImporterExporterUnitTests { Resource mockResource = mock(Resource.class); + ResourceWriter mockResourceWriter = mock(ResourceWriter.class); + Region mockRegion = mock(Region.class); + ExportResourceResolver mockExportResourceResolver = mock(ExportResourceResolver.class); + doReturn("TestRegion").when(mockRegion).getName(); - doReturn(Optional.of(mockResource)).when(this.importer).getResource(eq(mockRegion), anyString()); - doReturn( "file:///path/to/data.json").when(importer).getResourceLocation(); - doNothing().when(this.importer).save(anyString(), any(Resource.class)); - doReturn(json).when(this.importer).toJson(any()); + doReturn(mockExportResourceResolver).when(this.importerExporter).getExportResourceResolver(); + doReturn(mockResourceWriter).when(this.importerExporter).getResourceWriter(); + doReturn(Optional.of(mockResource)).when(mockExportResourceResolver).resolve(eq(mockRegion)); + doReturn(json).when(this.importerExporter).toJson(eq(mockRegion)); - assertThat(this.importer.doExportFrom(mockRegion)).isEqualTo(mockRegion); + assertThat(this.importerExporter.doExportFrom(mockRegion)).isEqualTo(mockRegion); - verify(this.importer, times(1)).toJson(eq(mockRegion)); - verify(this.importer, times(1)).getResource(eq(mockRegion), - eq("file:///path/to/data.json")); - verify(this.importer, times(1)).getResourceLocation(); - verify(this.importer, times(1)).save(eq(json), eq(mockResource)); + InOrder order = inOrder(this.importerExporter, mockRegion, mockExportResourceResolver, mockResourceWriter); + + order.verify(this.importerExporter, times(1)).getExportResourceResolver(); + order.verify(mockExportResourceResolver, times(1)).resolve(eq(mockRegion)); + order.verify(this.importerExporter, times(1)).toJson(eq(mockRegion)); + order.verify(mockRegion, times(1)).getName(); + order.verify(this.importerExporter, times(1)).getResourceWriter(); + order.verify(mockResourceWriter, times(1)).write(eq(mockResource), eq(json.getBytes())); + verifyNoMoreInteractions(mockRegion, mockExportResourceResolver, mockResourceWriter); + verifyNoInteractions(mockResource); + } + + @Test + @SuppressWarnings("unchecked") + public void doExportFromWithNoResource() { + + ExportResourceResolver mockExportResourceResolver = mock(ExportResourceResolver.class); + + Region mockRegion = mock(Region.class); + + doReturn(mockExportResourceResolver).when(this.importerExporter).getExportResourceResolver(); + doReturn(Optional.empty()).when(mockExportResourceResolver).resolve(eq(mockRegion)); + + assertThat(this.importerExporter.doExportFrom(mockRegion)).isEqualTo(mockRegion); + + verify(this.importerExporter, times(1)).getExportResourceResolver(); + verify(mockExportResourceResolver, times(1)).resolve(eq(mockRegion)); + verifyNoMoreInteractions(mockExportResourceResolver, mockRegion); + verifyNoInteractions(mockRegion); } @Test(expected = IllegalArgumentException.class) public void doExportFromNullRegion() { try { - this.importer.doExportFrom(null); + this.importerExporter.doExportFrom(null); } catch (IllegalArgumentException expected) { @@ -117,222 +137,151 @@ public class JsonCacheDataImporterExporterUnitTests { } } - @Test - public void saveJsonToResource() throws IOException { - - String json = "[{ \"name\": \"Jon Doe\"}, { \"name\": \"Jane Doe\"}]"; - - StringWriter writer = new StringWriter(json.length()); - - Resource mockResource = mock(Resource.class); - - doReturn(writer).when(this.importer).newWriter(eq(mockResource)); - - this.importer.save(json, mockResource); - - assertThat(writer.toString()).isEqualTo(json); - - verify(this.importer, times(1)).newWriter(eq(mockResource)); - verifyNoInteractions(mockResource); - } - - @Test(expected = DataAccessResourceFailureException.class) - public void saveThrowsIoException() throws IOException { - - Writer mockWriter = mock(Writer.class); - - String json = "[{ \"name\": \"Jon Doe\"}, { \"name\": \"Jane Doe\"}" - + ", { \"name\": \"Pie Doe\"}, { \"name\": \"Sour Doe\"}]"; - - Resource mockResource = mock(Resource.class); - - doReturn("/path/to/data.json").when(mockResource).getDescription(); - doReturn(mockWriter).when(this.importer).newWriter(eq(mockResource)); - doThrow(new IOException("TEST")).when(mockWriter).write(anyString(), anyInt(), anyInt()); - - try { - this.importer.save(json, mockResource); - } - catch (DataAccessResourceFailureException expected) { - - assertThat(expected) - .hasMessageStartingWith("Failed to save content '%s' to Resource [/path/to/data.json]", - this.importer.formatContentForPreview(json)); - assertThat(expected).hasCauseInstanceOf(IOException.class); - assertThat(expected.getCause()).hasMessage("TEST"); - assertThat(expected.getCause()).hasNoCause(); - - throw expected; - } - finally { - verify(this.importer, times(1)).newWriter(eq(mockResource)); - verify(mockWriter, times(1)).write(eq(json), eq(0), eq(json.length())); - verify(mockWriter, never()).flush(); - verify(mockWriter, times(1)).close(); - } - } - @Test - public void saveWithNoContent() { - - Resource mockResource = mock(Resource.class); - - try { - this.importer.save(" ", mockResource); - } - finally { - verify(this.importer, times(1)).save(eq(" "), eq(mockResource)); - verifyNoMoreInteractions(this.importer); - verifyNoInteractions(mockResource); - } - } - - @Test(expected = IllegalArgumentException.class) - public void saveWithNullResource() { - - try { - this.importer.save("{}", null); - } - catch (IllegalArgumentException expected) { - - assertThat(expected).hasMessage("Resource must not be null"); - assertThat(expected).hasNoCause(); - - throw expected; - } - finally { - verify(this.importer, times(1)).save(eq("{}"), isNull()); - verifyNoMoreInteractions(this.importer); - } - } - @Test @SuppressWarnings("unchecked") public void doImportIntoPutsPdxIntoRegionForJson() { Resource mockResource = mock(Resource.class); + ResourceReader mockResourceReader = mock(ResourceReader.class); + Region mockRegion = mock(Region.class); PdxInstance mockPdxInstanceOne = mock(PdxInstance.class); PdxInstance mockPdxInstanceTwo = mock(PdxInstance.class); + ImportResourceResolver mockImportResourceResolver = mock(ImportResourceResolver.class); + byte[] json = "[{ \"name\": \"Jon Doe\"}, { \"name\": \"Jane Doe\" }]".getBytes(); - doReturn(Optional.of(mockResource)).when(this.importer).getResource(eq(mockRegion),eq("classpath:")); - doReturn(true).when(mockResource).exists(); - doReturn(json).when(this.importer).getContent(eq(mockResource)); - doReturn(ArrayUtils.asArray(mockPdxInstanceOne, mockPdxInstanceTwo)).when(this.importer).toPdx(eq(json)); - doReturn(1).when(this.importer).resolveKey(eq(mockPdxInstanceOne)); - doReturn(2).when(this.importer).resolveKey(eq(mockPdxInstanceTwo)); + doReturn(mockImportResourceResolver).when(this.importerExporter).getImportResourceResolver(); + doReturn(mockResourceReader).when(this.importerExporter).getResourceReader(); + doReturn(Optional.of(mockResource)).when(mockImportResourceResolver).resolve(eq(mockRegion)); + doReturn(json).when(mockResourceReader).read(eq(mockResource)); + doReturn(ArrayUtils.asArray(mockPdxInstanceOne, mockPdxInstanceTwo)).when(this.importerExporter).toPdx(eq(json)); + doReturn(1).when(this.importerExporter).resolveKey(eq(mockPdxInstanceOne)); + doReturn(2).when(this.importerExporter).resolveKey(eq(mockPdxInstanceTwo)); - assertThat(this.importer.doImportInto(mockRegion)).isEqualTo(mockRegion); + assertThat(this.importerExporter.doImportInto(mockRegion)).isEqualTo(mockRegion); - verify(this.importer, times(1)).getResource(eq(mockRegion), eq("classpath:")); - verify(this.importer, times(1)).getContent(eq(mockResource)); - verify(this.importer, times(1)).toPdx(eq(json)); - verify(this.importer, times(1)).resolveKey(eq(mockPdxInstanceOne)); - verify(this.importer, times(1)).postProcess(eq(mockPdxInstanceOne)); - verify(this.importer, times(1)).resolveValue(eq(mockPdxInstanceOne)); - verify(this.importer, times(1)).resolveKey(eq(mockPdxInstanceTwo)); - verify(this.importer, times(1)).postProcess(eq(mockPdxInstanceTwo)); - verify(this.importer, times(1)).resolveValue(eq(mockPdxInstanceTwo)); - verify(mockResource, times(1)).exists(); - verify(mockRegion, times(1)).put(eq(1), eq(mockPdxInstanceOne)); - verify(mockRegion, times(1)).put(eq(2), eq(mockPdxInstanceTwo)); + InOrder order = + inOrder(this.importerExporter, mockRegion, mockResource, mockResourceReader, mockImportResourceResolver); + + order.verify(this.importerExporter, times(1)).getImportResourceResolver(); + order.verify(mockImportResourceResolver, times(1)).resolve(eq(mockRegion)); + order.verify(this.importerExporter, times(1)).getResourceReader(); + order.verify(mockResourceReader, times(1)).read(eq(mockResource)); + order.verify(this.importerExporter, times(1)).toPdx(eq(json)); + order.verify(this.importerExporter, times(1)).resolveKey(eq(mockPdxInstanceOne)); + order.verify(this.importerExporter, times(1)).resolveValue(eq(mockPdxInstanceOne)); + order.verify(this.importerExporter, times(1)).postProcess(eq(mockPdxInstanceOne)); + order.verify(mockRegion, times(1)).put(eq(1), eq(mockPdxInstanceOne)); + order.verify(this.importerExporter, times(1)).resolveKey(eq(mockPdxInstanceTwo)); + order.verify(this.importerExporter, times(1)).resolveValue(eq(mockPdxInstanceTwo)); + order.verify(this.importerExporter, times(1)).postProcess(eq(mockPdxInstanceTwo)); + order.verify(mockRegion, times(1)).put(eq(2), eq(mockPdxInstanceTwo)); + + verifyNoMoreInteractions(mockRegion, mockImportResourceResolver, mockResourceReader); + verifyNoInteractions(mockResource, mockPdxInstanceOne, mockPdxInstanceTwo); } @Test @SuppressWarnings("unchecked") public void doImportIntoWithNoResource() { - Region mockRegion = mock(Region.class); - - doReturn(Optional.empty()).when(this.importer).getResource(eq(mockRegion), eq("classpath:")); - - assertThat(this.importer.doImportInto(mockRegion)).isEqualTo(mockRegion); - - verify(this.importer, times(1)).doImportInto(eq(mockRegion)); - verify(this.importer, times(1)).getResource(eq(mockRegion), eq("classpath:")); - verifyNoMoreInteractions(this.importer); - verifyNoInteractions(mockRegion); - } - - @Test - @SuppressWarnings("unchecked") - public void doImportIntoWithNonExistingResource() { - - Resource mockResource = mock(Resource.class); + ImportResourceResolver mockImportResourceResolver = mock(ImportResourceResolver.class); Region mockRegion = mock(Region.class); - doReturn(Optional.of(mockResource)).when(this.importer).getResource(eq(mockRegion), eq("classpath:")); - doReturn(false).when(mockResource).exists(); + ResourceReader mockResourceReader = mock(ResourceReader.class); - assertThat(this.importer.doImportInto(mockRegion)).isEqualTo(mockRegion); + doReturn(mockImportResourceResolver).when(this.importerExporter).getImportResourceResolver(); + doReturn(mockResourceReader).when(this.importerExporter).getResourceReader(); + doReturn(Optional.empty()).when(mockImportResourceResolver).resolve(eq(mockRegion)); - verify(this.importer, times(1)).doImportInto(eq(mockRegion)); - verify(this.importer, times(1)).getResource(eq(mockRegion), eq("classpath:")); - verify(mockResource, times(1)).exists(); - verifyNoMoreInteractions(this.importer); - verifyNoMoreInteractions(mockResource); - verifyNoInteractions(mockRegion); + assertThat(this.importerExporter.doImportInto(mockRegion)).isEqualTo(mockRegion); + + verify(this.importerExporter, times(1)).doImportInto(eq(mockRegion)); + verify(this.importerExporter, times(1)).getImportResourceResolver(); + verify(this.importerExporter, times(1)).getResourceReader(); + verify(mockImportResourceResolver, times(1)).resolve(eq(mockRegion)); + verifyNoMoreInteractions(this.importerExporter, mockImportResourceResolver); + verifyNoInteractions(mockRegion, mockResourceReader); } @Test @SuppressWarnings("unchecked") public void doImportIntoWithResourceContainingNoContent() { - Resource mockResource = mock(Resource.class); + byte[] json = {}; + + ImportResourceResolver mockImportResourceResolver = mock(ImportResourceResolver.class); Region mockRegion = mock(Region.class); - doReturn(Optional.of(mockResource)).when(this.importer).getResource(eq(mockRegion), eq("classpath:")); - doReturn(true).when(mockResource).exists(); - doReturn(null).when(this.importer).getContent(eq(mockResource)); + Resource mockResource = mock(Resource.class); - assertThat(this.importer.doImportInto(mockRegion)).isEqualTo(mockRegion); + ResourceReader mockResourceReader = mock(ResourceReader.class); - verify(this.importer, times(1)).doImportInto(eq(mockRegion)); - verify(this.importer, times(1)).getResource(eq(mockRegion), eq("classpath:")); - verify(this.importer, times(1)).getContent(eq(mockResource)); - verify(mockResource, times(1)).exists(); - verifyNoMoreInteractions(this.importer); - verifyNoMoreInteractions(mockResource); - verifyNoInteractions(mockRegion); + doReturn(mockImportResourceResolver).when(this.importerExporter).getImportResourceResolver(); + doReturn(mockResourceReader).when(this.importerExporter).getResourceReader(); + doReturn(JsonCacheDataImporterExporter.EMPTY_PDX_INSTANCE_ARRAY).when(this.importerExporter).toPdx(any()); + doReturn(Optional.of(mockResource)).when(mockImportResourceResolver).resolve(eq(mockRegion)); + doReturn(json).when(mockResourceReader).read(eq(mockResource)); + + assertThat(this.importerExporter.doImportInto(mockRegion)).isEqualTo(mockRegion); + + verify(this.importerExporter, times(1)).doImportInto(eq(mockRegion)); + verify(this.importerExporter, times(1)).getImportResourceResolver(); + verify(this.importerExporter, times(1)).getResourceReader(); + verify(this.importerExporter, times(1)).toPdx(eq(json)); + verify(this.importerExporter, times(1)) + .regionPutPdx(eq(mockRegion), eq(JsonCacheDataImporterExporter.EMPTY_PDX_INSTANCE_ARRAY)); + verify(mockImportResourceResolver, times(1)).resolve(eq(mockRegion)); + verify(mockResourceReader, times(1)).read(eq(mockResource)); + verifyNoMoreInteractions(this.importerExporter, mockImportResourceResolver, mockResourceReader); + verifyNoInteractions(mockRegion, mockResource); } @Test @SuppressWarnings("unchecked") - public void doImportIntoWithNoPdxInstance() { + public void doImportIntoWithNoPdx() { - Resource mockResource = mock(Resource.class); + byte[] json = "[{ \"name\":\"Jon Doe\" }]".getBytes(); + + ImportResourceResolver mockImportResourceResolver = mock(ImportResourceResolver.class); Region mockRegion = mock(Region.class); - byte[] json = "[]".getBytes(); + Resource mockResource = mock(Resource.class); - doReturn(Optional.of(mockResource)).when(this.importer).getResource(eq(mockRegion), eq("classpath:")); - doReturn(true).when(mockResource).exists(); - doReturn(json).when(this.importer).getContent(eq(mockResource)); - doReturn(new PdxInstance[0]).when(this.importer).toPdx(eq(json)); + ResourceReader mockResourceReader = mock(ResourceReader.class); - assertThat(this.importer.doImportInto(mockRegion)).isEqualTo(mockRegion); + doReturn(mockImportResourceResolver).when(this.importerExporter).getImportResourceResolver(); + doReturn(mockResourceReader).when(this.importerExporter).getResourceReader(); + doReturn(JsonCacheDataImporterExporter.EMPTY_PDX_INSTANCE_ARRAY).when(this.importerExporter).toPdx(eq(json)); + doReturn(Optional.of(mockResource)).when(mockImportResourceResolver).resolve(eq(mockRegion)); + doReturn(json).when(mockResourceReader).read(eq(mockResource)); - verify(this.importer, times(1)).doImportInto(eq(mockRegion)); - verify(this.importer, times(1)).getResource(eq(mockRegion), eq("classpath:")); - verify(this.importer, times(1)).getContent(eq(mockResource)); - verify(this.importer, times(1)).toPdx(eq(json)); - verify(mockResource, times(1)).exists(); - verifyNoMoreInteractions(this.importer); - verifyNoMoreInteractions(mockResource); - verifyNoInteractions(mockRegion); + assertThat(this.importerExporter.doImportInto(mockRegion)).isEqualTo(mockRegion); + + verify(this.importerExporter, times(1)).doImportInto(eq(mockRegion)); + verify(this.importerExporter, times(1)).getImportResourceResolver(); + verify(this.importerExporter, times(1)).getResourceReader(); + verify(this.importerExporter, times(1)).toPdx(eq(json)); + verify(this.importerExporter, times(1)).regionPutPdx(eq(mockRegion), + eq(JsonCacheDataImporterExporter.EMPTY_PDX_INSTANCE_ARRAY)); + verify(mockImportResourceResolver, times(1)).resolve(eq(mockRegion)); + verify(mockResourceReader, times(1)).read(eq(mockResource)); + verifyNoMoreInteractions(this.importerExporter, mockImportResourceResolver, mockResourceReader); + verifyNoInteractions(mockRegion, mockResource); } @Test(expected = IllegalArgumentException.class) public void doImportIntoNullRegion() { try { - this.importer.doImportInto(null); + this.importerExporter.doImportInto(null); } catch (IllegalArgumentException expected) { @@ -343,144 +292,12 @@ public class JsonCacheDataImporterExporterUnitTests { } } - @Test - public void getContentFromResource() throws IOException { - - byte[] json = "{ \"name\": \"Jon Doe\" }".getBytes(); - - ByteArrayInputStream in = spy(new ByteArrayInputStream(json)); - - Resource mockResource = mock(Resource.class); - - doReturn(in).when(mockResource).getInputStream(); - - assertThat(this.importer.getContent(mockResource)).isEqualTo(json); - - verify(mockResource, times(1)).getInputStream(); - verify(in, times(1)).close(); - } - - @Test(expected = IllegalArgumentException.class) - public void getContentFromNullResource() { - - try { - this.importer.getContent(null); - } - catch (IllegalArgumentException expected) { - - assertThat(expected).hasMessage("Resource must not be null"); - assertThat(expected).hasNoCause(); - - throw expected; - } - } - - @Test(expected = DataAccessResourceFailureException.class) - public void getContentThrowsIoException() throws IOException { - - Resource mockResource = mock(Resource.class); - - doReturn("Test Resource").when(mockResource).getDescription(); - doThrow(new IOException("TEST")).when(mockResource).getInputStream(); - - try { - this.importer.getContent(mockResource); - } - catch (DataAccessResourceFailureException expected) { - - assertThat(expected).hasMessageStartingWith("Failed to read from Resource [Test Resource]"); - assertThat(expected).hasCauseInstanceOf(IOException.class); - assertThat(expected.getCause()).hasMessage("TEST"); - assertThat(expected.getCause()).hasNoCause(); - - throw expected; - } - } - - @Test - public void getResourceFromRegionAndResourcePrefix() { - - ApplicationContext mockApplicationContext = mock(ApplicationContext.class); - - Region mockRegion = mock(Region.class); - - Resource mockResource = mock(Resource.class); - - doReturn(mockResource).when(mockApplicationContext).getResource(anyString()); - doReturn("Example").when(mockRegion).getName(); - doReturn(Optional.of(mockApplicationContext)).when(this.importer).getApplicationContext(); - - assertThat(this.importer.getResource(mockRegion, "sftp://host/resources/").orElse(null)) - .isEqualTo(mockResource); - - verify(mockApplicationContext, times(1)) - .getResource(eq("sftp://host/resources/data-example.json")); - verify(mockRegion, times(1)).getName(); - } - - @Test - public void getResourceWhenApplicationContextIsNotPresent() { - - Region mockRegion = mock(Region.class); - - doReturn("TEST").when(mockRegion).getName(); - - Resource resource = this.importer.getResource(mockRegion, "file://").orElse(null); - - assertThat(resource).isInstanceOf(ClassPathResource.class); - assertThat(((ClassPathResource) resource).getPath()).isEqualTo("data-test.json"); - - verify(mockRegion, times(1)).getName(); - } - - @Test - public void getResourceWhenApplicationContextReturnsNoResource() { - - ApplicationContext mockApplicationContext = mock(ApplicationContext.class); - - Region mockRegion = mock(Region.class); - - doReturn(null).when(mockApplicationContext).getResource(anyString()); - doReturn("MocK").when(mockRegion).getName(); - doReturn(Optional.ofNullable(mockApplicationContext)).when(this.importer).getApplicationContext(); - - Resource resource = this.importer.getResource(mockRegion, null).orElse(null); - - assertThat(resource).isInstanceOf(ClassPathResource.class); - assertThat(((ClassPathResource) resource).getPath()).isEqualTo("data-mock.json"); - - verify(mockApplicationContext, times(1)) - .getResource(eq("classpath:data-mock.json")); - verify(mockRegion, times(1)).getName(); - } - - @Test(expected = IllegalArgumentException.class) - public void getResourceFromNullRegion() { - - try { - this.importer.getResource(null, "classpath:"); - } - catch (IllegalArgumentException expected) { - - assertThat(expected).hasMessage("Region must not be null"); - assertThat(expected).hasNoCause(); - - throw expected; - } - } - - @Test - public void getResourceLocationIsInWorkingDirectory() { - assertThat(this.importer.getResourceLocation()).isEqualTo(String.format("file://%1$s%2$s", - System.getProperty("user.dir"), File.separator)); - } - @Test public void toJsonFromEmptyRegion() { Region mockRegion = mock(Region.class); - assertThat(this.importer.toJson(mockRegion)).isEqualTo("[]"); + assertThat(this.importerExporter.toJson(mockRegion)).isEqualTo("[]"); verify(mockRegion, times(1)).values(); } @@ -489,7 +306,7 @@ public class JsonCacheDataImporterExporterUnitTests { public void toJsonFromNullRegion() { try { - this.importer.toJson(null); + this.importerExporter.toJson(null); } catch (IllegalArgumentException expected) { @@ -512,13 +329,36 @@ public class JsonCacheDataImporterExporterUnitTests { PdxInstance[] pdxArray = ArrayUtils.asArray(mockPdxInstanceOne, mockPdxInstanceTwo); - doReturn(mockConverter).when(this.importer).getJsonToPdxArrayConverter(); + doReturn(mockConverter).when(this.importerExporter).getJsonToPdxArrayConverter(); doReturn(pdxArray).when(mockConverter).convert(eq(json)); - assertThat(this.importer.toPdx(json)).isEqualTo(pdxArray); + assertThat(this.importerExporter.toPdx(json)).isEqualTo(pdxArray); - verify(this.importer, times(1)).getJsonToPdxArrayConverter(); + verify(this.importerExporter, times(1)).getJsonToPdxArrayConverter(); verify(mockConverter, times(1)).convert(eq(json)); + verifyNoMoreInteractions(mockConverter); + } + static class TestJsonCacheDataImporterExporter extends JsonCacheDataImporterExporter { + + @Override + protected @NonNull ExportResourceResolver getExportResourceResolver() { + return super.getExportResourceResolver(); + } + + @Override + protected @NonNull ImportResourceResolver getImportResourceResolver() { + return super.getImportResourceResolver(); + } + + @Override + protected @NonNull ResourceReader getResourceReader() { + return super.getResourceReader(); + } + + @Override + protected @NonNull ResourceWriter getResourceWriter() { + return super.getResourceWriter(); + } } } diff --git a/spring-geode/src/test/java/org/springframework/geode/data/json/JsonClientCacheDataImporterExporterIntegrationTests.java b/spring-geode/src/test/java/org/springframework/geode/data/json/JsonClientCacheDataImporterExporterIntegrationTests.java index ca13cedc..d36723bf 100644 --- a/spring-geode/src/test/java/org/springframework/geode/data/json/JsonClientCacheDataImporterExporterIntegrationTests.java +++ b/spring-geode/src/test/java/org/springframework/geode/data/json/JsonClientCacheDataImporterExporterIntegrationTests.java @@ -20,7 +20,6 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.io.InputStreamReader; import java.io.StringWriter; -import java.io.Writer; import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -52,7 +51,7 @@ import org.springframework.data.gemfire.config.annotation.CacheServerApplication import org.springframework.data.gemfire.config.annotation.ClientCacheApplication; import org.springframework.data.gemfire.tests.integration.ForkingClientServerIntegrationTestsSupport; import org.springframework.data.gemfire.util.PropertiesBuilder; -import org.springframework.lang.NonNull; +import org.springframework.geode.core.io.ResourceWriter; import org.springframework.util.FileCopyUtils; import example.app.crm.model.Customer; @@ -89,7 +88,6 @@ import example.app.crm.model.Customer; public class JsonClientCacheDataImporterExporterIntegrationTests extends ForkingClientServerIntegrationTestsSupport { private static final AtomicBoolean applicationContextClosed = new AtomicBoolean(false); - private static final AtomicBoolean exportFromCalled = new AtomicBoolean(false); private static ConfigurableApplicationContext applicationContext = null; @@ -101,7 +99,6 @@ public class JsonClientCacheDataImporterExporterIntegrationTests extends Forking public static void assetClientCacheDataExportWorksAsExpected() throws IOException { assertThat(applicationContextClosed.get()).isTrue(); - assertThat(exportFromCalled.get()).isTrue(); String actualJson = trimJson(writer.toString()); String expectedJson = trimJson(loadJson(CUSTOMERS_JSON_RESOURCE_PATH)); @@ -212,14 +209,18 @@ public class JsonClientCacheDataImporterExporterIntegrationTests extends Forking @Bean JsonCacheDataImporterExporter cacheDataImporterExporter() { + return new JsonCacheDataImporterExporter(); + } - return new JsonCacheDataImporterExporter() { + @Bean + ResourceWriter testResourceWriter() { - @Override - @NonNull Writer newWriter(@NonNull org.springframework.core.io.Resource resource) { - exportFromCalled.set(true); - return writer; - } + return (resource, data) -> { + + String json = new String(data); + + writer.write(json); + writer.flush(); }; }