From e78169eda2881c2bdb04bdf752530e25ec77c50d Mon Sep 17 00:00:00 2001 From: John Blum Date: Mon, 24 Aug 2015 22:45:44 -0700 Subject: [PATCH] SGF-408 - Provide support in the SDG XML namespace to load a pre-defined data set using GemFire Snapshot Service for development and testing purposes. Refactored the ServiceSnapshotFactoryBean class to handle ZIP and JAR files as input sources on GemFire Cache data imports. Added additional unit tests for SnapshotServiceFactoryBean to improve coverage along with a basic integration test covering import and export. Fixed bugs in the SnapshotServiceParser and the XSD. Moved the iterable(:Enumeration) and iterable(:Iterator) methods from the test CollectionUtils class to the 'official' CollectionUtils class. (cherry picked from commit b47884c88c52b612aff6b8aa01fc0994ac42095c) Signed-off-by: John Blum --- .../gemfire/SnapshotServiceFactoryBean.java | 227 ++++++++++--- .../gemfire/config/SnapshotServiceParser.java | 18 +- .../data/gemfire/util/CollectionUtils.java | 61 +++- .../config/spring-data-gemfire-1.7.xsd | 7 + ...shotServiceFactoryBeanIntegrationTest.java | 28 ++ .../SnapshotServiceFactoryBeanTest.java | 304 +++++++++++++++--- ...hotServiceImportExportIntegrationTest.java | 166 ++++++++++ .../gemfire/repository/sample/Person.java | 41 ++- .../gemfire/test/support/CollectionUtils.java | 19 -- .../gemfire/util/CollectionUtilsTest.java | 89 ++++- ...iceImportExportIntegrationTest-context.xml | 29 ++ src/test/resources/people.snapshot | Bin 0 -> 2997 bytes 12 files changed, 845 insertions(+), 144 deletions(-) create mode 100644 src/test/java/org/springframework/data/gemfire/SnapshotServiceFactoryBeanIntegrationTest.java create mode 100644 src/test/java/org/springframework/data/gemfire/SnapshotServiceImportExportIntegrationTest.java create mode 100644 src/test/resources/org/springframework/data/gemfire/SnapshotServiceImportExportIntegrationTest-context.xml create mode 100644 src/test/resources/people.snapshot diff --git a/src/main/java/org/springframework/data/gemfire/SnapshotServiceFactoryBean.java b/src/main/java/org/springframework/data/gemfire/SnapshotServiceFactoryBean.java index 26011904..857f31ed 100644 --- a/src/main/java/org/springframework/data/gemfire/SnapshotServiceFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/SnapshotServiceFactoryBean.java @@ -18,16 +18,30 @@ package org.springframework.data.gemfire; import static com.gemstone.gemfire.cache.snapshot.SnapshotOptions.SnapshotFormat; +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.File; import java.io.FileFilter; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.Arrays; +import java.util.List; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationListener; +import org.springframework.data.gemfire.util.CollectionUtils; import org.springframework.util.Assert; +import org.springframework.util.FileCopyUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; import com.gemstone.gemfire.cache.Cache; import com.gemstone.gemfire.cache.Region; @@ -179,7 +193,6 @@ public class SnapshotServiceFactoryBean implements FactoryBean getObject() throws Exception { return snapshotServiceAdapter; } @@ -336,6 +349,13 @@ public class SnapshotServiceFactoryBean implements FactoryBean the class type of the Region key. + * @param the class type of the Region value. + */ public interface SnapshotServiceAdapter { SnapshotOptions createOptions(); @@ -354,7 +374,112 @@ public class SnapshotServiceFactoryBean implements FactoryBean { + protected static abstract class SnapshotServiceAdapterSupport implements SnapshotServiceAdapter { + + protected final Log log = createLog(); + + Log createLog() { + return LogFactory.getLog(getClass()); + } + + protected SnapshotOptions createOptions(SnapshotFilter filter) { + return createOptions().setFilter(filter); + } + + @Override + public void doExport(SnapshotMetadata[] configurations) { + for (SnapshotMetadata configuration : nullSafeArray(configurations)) { + save(configuration.getLocation(), configuration.getFormat(), createOptions(configuration.getFilter())); + } + } + + @Override + public void doImport(SnapshotMetadata[] configurations) { + for (SnapshotMetadata configuration : nullSafeArray(configurations)) { + load(configuration.getFormat(), createOptions(configuration.getFilter()), handleLocation(configuration)); + } + } + + protected abstract File[] handleLocation(SnapshotMetadata configuration); + + protected File[] handleDirectoryLocation(File directory) { + return directory.listFiles(new FileFilter() { + @Override public boolean accept(File pathname) { + return nullSafeIsFile(pathname); + } + }); + } + + protected File[] handleFileLocation(File file) { + if (ArchiveFileFilter.INSTANCE.accept(file)) { + try { + File extractedArchiveDirectory = new File(System.getProperty("java.io.tmpdir"), + file.getName().replaceAll("\\.", "-")); + + Assert.state(extractedArchiveDirectory.isDirectory() || extractedArchiveDirectory.mkdirs(), + String.format("Failed create directory (%1$s) in which to extract archive (%2$s)", + extractedArchiveDirectory, file)); + + ZipFile zipFile = (ArchiveFileFilter.INSTANCE.isJarFile(file) + ? new JarFile(file, false, JarFile.OPEN_READ) + : new ZipFile(file, ZipFile.OPEN_READ)); + + for (ZipEntry entry : CollectionUtils.iterable(zipFile.entries())) { + if (!entry.isDirectory()) { + DataInputStream entryInputStream = new DataInputStream(zipFile.getInputStream(entry)); + + DataOutputStream entryOutputStream = new DataOutputStream(new FileOutputStream( + new File(extractedArchiveDirectory, toSimpleFilename(entry.getName())))); + + try { + FileCopyUtils.copy(entryInputStream, entryOutputStream); + } + finally { + exceptionSuppressingClose(entryInputStream); + exceptionSuppressingClose(entryOutputStream); + } + } + } + + return handleDirectoryLocation(extractedArchiveDirectory); + } + catch (Throwable t) { + throw new ImportSnapshotException(String.format( + "Failed to extract archive (%1$s) to import", file), t); + } + } + + return new File[] { file }; + } + + protected boolean exceptionSuppressingClose(Closeable closeable) { + try { + closeable.close(); + return true; + } + catch (IOException ignore) { + logDebug(ignore, "Failed to close (%1$s)", closeable); + return false; + } + } + + protected void logDebug(Throwable t, String message, Object... arguments) { + if (log.isDebugEnabled()) { + log.debug(String.format(message, arguments), t); + } + } + + protected String toSimpleFilename(String pathname) { + int pathSeparatorIndex = String.valueOf(pathname).lastIndexOf(File.separator); + pathname = (pathSeparatorIndex > -1 ? pathname.substring(pathSeparatorIndex + 1) : pathname); + return StringUtils.trimWhitespace(pathname); + } + } + + /** + * The CacheSnapshotServiceAdapter is a SnapshotServiceAdapter adapting GemFire's CacheSnapshotService. + */ + protected static class CacheSnapshotServiceAdapter extends SnapshotServiceAdapterSupport { private final CacheSnapshotService snapshotService; @@ -372,29 +497,10 @@ public class SnapshotServiceFactoryBean implements FactoryBean createOptions(SnapshotFilter filter) { - return createOptions().setFilter(filter); - } - @Override - public void doExport(SnapshotMetadata[] configurations) { - for (SnapshotMetadata configuration : nullSafeArray(configurations)) { - save(configuration.getLocation(), configuration.getFormat(), createOptions(configuration.getFilter())); - } - } - - @Override - public void doImport(SnapshotMetadata[] configurations) { - for (SnapshotMetadata configuration : nullSafeArray(configurations)) { - File[] snapshots = (configuration.isFile() ? new File[] { configuration.getLocation() } - : configuration.getLocation().listFiles(new FileFilter() { - @Override public boolean accept(File pathname) { - return nullSafeIsFile(pathname); - } - })); - - load(configuration.getFormat(), createOptions(configuration.getFilter()), snapshots); - } + protected File[] handleLocation(SnapshotMetadata configuration) { + return (configuration.isFile() ? handleFileLocation(configuration.getLocation()) + : handleDirectoryLocation(configuration.getLocation())); } @Override @@ -446,7 +552,10 @@ public class SnapshotServiceFactoryBean implements FactoryBean implements SnapshotServiceAdapter { + /** + * The RegionSnapshotServiceAdapter is a SnapshotServiceAdapter adapting GemFire's RegionSnapshotService. + */ + protected static class RegionSnapshotServiceAdapter extends SnapshotServiceAdapterSupport { private final RegionSnapshotService snapshotService; @@ -464,22 +573,9 @@ public class SnapshotServiceFactoryBean implements FactoryBean createOptions(SnapshotFilter filter) { - return createOptions().setFilter(filter); - } - @Override - public void doExport(SnapshotMetadata[] configurations) { - for (SnapshotMetadata configuration : nullSafeArray(configurations)) { - save(configuration.getLocation(), configuration.getFormat(), createOptions(configuration.getFilter())); - } - } - - @Override - public void doImport(SnapshotMetadata[] configurations) { - for (SnapshotMetadata configuration : nullSafeArray(configurations)) { - load(configuration.getFormat(), createOptions(configuration.getFilter()), configuration.getLocation()); - } + protected File[] handleLocation(final SnapshotMetadata configuration) { + return new File[] { configuration.getLocation() }; } @Override @@ -533,6 +629,13 @@ public class SnapshotServiceFactoryBean implements FactoryBean the class type of the Region key. + * @param the class type of the Region value. + */ public static class SnapshotMetadata { private final File location; @@ -541,9 +644,12 @@ public class SnapshotServiceFactoryBean implements FactoryBean filter, SnapshotFormat format) { - Assert.isTrue(location != null && location.exists(), String.format( - "The File location (%1$s) must exist", location)); + Assert.notNull(location, "Location must not be null"); this.location = location; this.filter = filter; @@ -581,4 +687,41 @@ public class SnapshotServiceFactoryBean implements FactoryBean ACCEPTED_FILE_EXTENSIONS = Arrays.asList("jar", "zip"); + + protected static final String FILE_EXTENSION_DOT_SEPARATOR = "."; + + protected boolean isJarFile(File file) { + return "jar".equalsIgnoreCase(getFileExtension(file)); + } + + protected String getFileExtension(File file) { + String fileExtension = ""; + + if (nullSafeIsFile(file)) { + String pathname = file.getAbsolutePath(); + int fileExtensionIndex = pathname.lastIndexOf(FILE_EXTENSION_DOT_SEPARATOR); + fileExtension = (fileExtensionIndex > -1 ? pathname.substring(fileExtensionIndex + 1) : ""); + } + + return fileExtension.toLowerCase(); + } + + @Override + public boolean accept(final File pathname) { + return ACCEPTED_FILE_EXTENSIONS.contains(getFileExtension(pathname)); + } + } + } diff --git a/src/main/java/org/springframework/data/gemfire/config/SnapshotServiceParser.java b/src/main/java/org/springframework/data/gemfire/config/SnapshotServiceParser.java index 86c29199..dfb9dd8c 100644 --- a/src/main/java/org/springframework/data/gemfire/config/SnapshotServiceParser.java +++ b/src/main/java/org/springframework/data/gemfire/config/SnapshotServiceParser.java @@ -16,8 +16,6 @@ package org.springframework.data.gemfire.config; -import java.util.List; - import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.ManagedList; @@ -79,12 +77,20 @@ class SnapshotServiceParser extends AbstractSingleBeanDefinitionParser { BeanDefinitionBuilder snapshotMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition( SnapshotServiceFactoryBean.SnapshotMetadata.class); - ParsingUtils.setPropertyValue(snapshotMetadataElement, snapshotMetadataBuilder, "location"); - ParsingUtils.parseRefOrNestedBeanDeclaration(parserContext, snapshotMetadataElement, snapshotMetadataBuilder, - "filter-ref"); - ParsingUtils.setPropertyValue(snapshotMetadataElement, snapshotMetadataBuilder, "format"); + snapshotMetadataBuilder.addConstructorArgValue(snapshotMetadataElement.getAttribute("location")); + + if (isSnapshotFilterSpecified(snapshotMetadataElement)) { + snapshotMetadataBuilder.addConstructorArgValue(ParsingUtils.parseRefOrNestedBeanDeclaration( + parserContext, snapshotMetadataElement, snapshotMetadataBuilder, "filter-ref", true)); + } + + snapshotMetadataBuilder.addConstructorArgValue(snapshotMetadataElement.getAttribute("format")); return snapshotMetadataBuilder.getBeanDefinition(); } + private boolean isSnapshotFilterSpecified(final Element snapshotMetadataElement) { + return (snapshotMetadataElement.hasAttribute("filter-ref") || snapshotMetadataElement.hasChildNodes()); + } + } diff --git a/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java b/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java index b0aaa55a..f3f90c37 100644 --- a/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java +++ b/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java @@ -18,6 +18,7 @@ package org.springframework.data.gemfire.util; import java.util.Collection; import java.util.Collections; +import java.util.Enumeration; import java.util.Iterator; import java.util.NoSuchElementException; @@ -33,6 +34,53 @@ import java.util.NoSuchElementException; @SuppressWarnings("unused") public abstract class CollectionUtils extends org.springframework.util.CollectionUtils { + /** + * Adapts the given Enumeration as an Iterable object for use within a for each loop. + * + * @param the class type of the Enumeration elements. + * @param enumeration the Enumeration to adapt as an Iterable object. + * @return an Iterable instance of the Enumeration. + * @see java.lang.Iterable + * @see java.util.Enumeration + */ + public static Iterable iterable(final Enumeration enumeration) { + return new Iterable() { + @Override public Iterator iterator() { + return org.springframework.util.CollectionUtils.toIterator(enumeration); + } + }; + } + + /** + * Adapts the given Iterator as an Iterable object for use within a for each loop. + * + * @param the class type of the Iterator elements. + * @param iterator the Iterator to adapt as an Iterable object. + * @return an Iterable instance of the Iterator. + * @see java.lang.Iterable + * @see java.util.Iterator + */ + public static Iterable iterable(final Iterator iterator) { + return new Iterable() { + @Override public Iterator iterator() { + return iterator; + } + }; + } + + /** + * A null-safe operation returning the original Collection if non-null or an empty Collection + * (implemented with List) if null. + * + * @param the element class type of the Collection. + * @param collection the Collection to evaluate for being null. + * @return the given Collection if not null, otherwise return an empty Collection (List). + * @see java.util.Collections#emptyList() + */ + public static Collection nullSafeCollection(final Collection collection) { + return (collection != null ? collection : Collections.emptyList()); + } + /** * A null-safe operation returning the original Iterable object if non-null or a default, empty Iterable * implementation if null. @@ -63,17 +111,4 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio }); } - /** - * A null-safe operation returning the original Collection if non-null or an empty Collection - * (implemented with List) if null. - * - * @param the element class type of the Collection. - * @param collection the Collection to evaluate for being null. - * @return the given Collection if not null, otherwise return an empty Collection (List). - * @see java.util.Collections#emptyList() - */ - public static Collection nullSafeCollection(final Collection collection) { - return (collection != null ? collection : Collections.emptyList()); - } - } diff --git a/src/main/resources/org/springframework/data/gemfire/config/spring-data-gemfire-1.7.xsd b/src/main/resources/org/springframework/data/gemfire/config/spring-data-gemfire-1.7.xsd index 363e2e98..addd4c3d 100644 --- a/src/main/resources/org/springframework/data/gemfire/config/spring-data-gemfire-1.7.xsd +++ b/src/main/resources/org/springframework/data/gemfire/config/spring-data-gemfire-1.7.xsd @@ -198,6 +198,13 @@ Specifies meta-data for a snapshot export. + + + + + createOptions() { + return mockSnapshotOptions; + } + }; + + assertThat(snapshotService.createOptions(mockSnapshotFilter), is(equalTo(mockSnapshotOptions))); + + verify(mockSnapshotOptions, times(1)).setFilter(eq(mockSnapshotFilter)); + } + + @Test + public void invokeExceptionSuppressingCloseOnSnapshotServiceAdapterSupportIsSuccessful() throws Exception { + Closeable mockCloseable = mock(Closeable.class, "MockCloseable"); + + assertThat(new TestSnapshotServiceAdapter().exceptionSuppressingClose(mockCloseable), is(true)); + + verify(mockCloseable, times(1)).close(); + } + + @Test + public void invokeExceptionSuppressingCloseOnSnapshotServiceAdapterSupportIsUnsuccessful() throws Exception { + Closeable mockCloseable = mock(Closeable.class, "MockCloseable"); + + doThrow(new IOException("TEST")).when(mockCloseable).close(); + + assertThat(new TestSnapshotServiceAdapter().exceptionSuppressingClose(mockCloseable), is(false)); + + verify(mockCloseable, times(1)).close(); + } + + @Test + public void logDebugWhenDebugging() { + final Log mockLog = mock(Log.class, "MockLog"); + + when(mockLog.isDebugEnabled()).thenReturn(true); + + TestSnapshotServiceAdapter snapshotService = new TestSnapshotServiceAdapter() { + @Override Log createLog() { + return mockLog; + } + }; + + Exception expectedException = new Exception("test"); + + snapshotService.logDebug(expectedException, "Log message with argument (%1$s)", "test"); + + verify(mockLog, times(1)).isDebugEnabled(); + verify(mockLog, times(1)).debug(eq("Log message with argument (test)"), eq(expectedException)); + } + + @Test + public void logDebugWhenNotDebugging() { + final Log mockLog = mock(Log.class, "MockLog"); + + when(mockLog.isDebugEnabled()).thenReturn(false); + + TestSnapshotServiceAdapter snapshotService = new TestSnapshotServiceAdapter() { + @Override Log createLog() { + return mockLog; + } + }; + + snapshotService.logDebug(null, "Log message with argument (%1$s)", "test"); + + verify(mockLog, times(1)).isDebugEnabled(); + verify(mockLog, never()).debug(any(String.class), any(Throwable.class)); + } + + @Test + public void toSimpleFilenameUsingVariousPathnames() { + TestSnapshotServiceAdapter snapshotService = new TestSnapshotServiceAdapter(); + + assertThat(snapshotService.toSimpleFilename("/path/to/file.ext"), is(equalTo("file.ext"))); + assertThat(snapshotService.toSimpleFilename("/path/to/file "), is(equalTo("file"))); + assertThat(snapshotService.toSimpleFilename("/ file.ext"), is(equalTo("file.ext"))); + assertThat(snapshotService.toSimpleFilename(" file.ext "), is(equalTo("file.ext"))); + assertThat(snapshotService.toSimpleFilename("/ "), is(equalTo(""))); + assertThat(snapshotService.toSimpleFilename(" "), is(equalTo(""))); + assertThat(snapshotService.toSimpleFilename(""), is(equalTo(""))); + assertThat(snapshotService.toSimpleFilename(null), is(nullValue())); + } + @Test(expected = ImportSnapshotException.class) public void loadCacheSnapshotWithDirectoryAndFormatHandlesExceptionAppropriately() throws Exception { CacheSnapshotService mockCacheSnapshotService = mock(CacheSnapshotService.class, "MockCacheSnapshotService"); @@ -893,4 +992,117 @@ public class SnapshotServiceFactoryBeanTest { } } + @Test + public void createSnapshotMetadataWithNullLocation() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectCause(is(nullValue(Throwable.class))); + expectedException.expectMessage("Location must not be null"); + new SnapshotMetadata(null, mock(SnapshotFilter.class), SnapshotFormat.GEMFIRE); + } + + @Test + public void createSnapshotMetadataWithDirectoryFilterAndUnspecifiedFormat() { + SnapshotFilter mockSnapshotFilter = mock(SnapshotFilter.class, "MockSnapshotFilter"); + + SnapshotMetadata metadata = new SnapshotMetadata(FileSystemUtils.WORKING_DIRECTORY, mockSnapshotFilter, null); + + assertThat(metadata.getLocation(), is(equalTo(FileSystemUtils.WORKING_DIRECTORY))); + assertThat(metadata.isDirectory(), is(true)); + assertThat(metadata.isFile(), is(false)); + assertThat(metadata.getFilter(), is(equalTo(mockSnapshotFilter))); + assertThat(metadata.isFilterPresent(), is(true)); + assertThat(metadata.getFormat(), is(equalTo(SnapshotFormat.GEMFIRE))); + } + + @Test + public void createSnapshotMetadataWithFileNullFilterAndGemFireFormat() throws Exception { + SnapshotMetadata metadata = new SnapshotMetadata(snapshotDat, null, SnapshotFormat.GEMFIRE); + + assertThat(metadata.getLocation(), is(equalTo(snapshotDat))); + assertThat(metadata.isDirectory(), is(false)); + assertThat(metadata.isFile(), is(true)); + assertThat(metadata.getFilter(), is(nullValue())); + assertThat(metadata.isFilterPresent(), is(false)); + assertThat(metadata.getFormat(), is(equalTo(SnapshotFormat.GEMFIRE))); + } + + @Test + public void isJarFileIsTrue() { + // JRE + File runtimeDotJar = new File(new File(FileSystemUtils.JAVA_HOME, "lib"), "rt.jar"); + + // JDK + if (!runtimeDotJar.isFile()) { + runtimeDotJar = new File(new File(new File(FileSystemUtils.JAVA_HOME, "jre"), "lib"), "rt.jar"); + assumeThat(runtimeDotJar.isFile(), is(true)); + } + + assertThat(ArchiveFileFilter.INSTANCE.isJarFile(runtimeDotJar), is(true)); + } + + @Test + public void isJarFileIsFalse() throws Exception { + assertThat(ArchiveFileFilter.INSTANCE.isJarFile(new File("/path/to/non-existing/file.jar")), is(false)); + assertThat(ArchiveFileFilter.INSTANCE.isJarFile(new ClassPathResource("/cluster_config.zip").getFile()), is(false)); + assertThat(ArchiveFileFilter.INSTANCE.isJarFile(new File("to/file.tar")), is(false)); + assertThat(ArchiveFileFilter.INSTANCE.isJarFile(new File("jar.file")), is(false)); + assertThat(ArchiveFileFilter.INSTANCE.isJarFile(new File(" ")), is(false)); + assertThat(ArchiveFileFilter.INSTANCE.isJarFile(new File("")), is(false)); + assertThat(ArchiveFileFilter.INSTANCE.isJarFile(null), is(false)); + } + + @Test + public void getFileExtensionOfVariousFiles() throws Exception { + assertThat(ArchiveFileFilter.INSTANCE.getFileExtension(new ClassPathResource("/cluster_config.zip").getFile()), is(equalTo("zip"))); + assertThat(ArchiveFileFilter.INSTANCE.getFileExtension(new File("/path/to/non-existing/file.jar")), is(equalTo(""))); + assertThat(ArchiveFileFilter.INSTANCE.getFileExtension(new File("to/non-existing/file.tar")), is(equalTo(""))); + assertThat(ArchiveFileFilter.INSTANCE.getFileExtension(FileSystemUtils.WORKING_DIRECTORY), is(equalTo(""))); + assertThat(ArchiveFileFilter.INSTANCE.getFileExtension(new File(" ")), is(equalTo(""))); + assertThat(ArchiveFileFilter.INSTANCE.getFileExtension(new File("")), is(equalTo(""))); + assertThat(ArchiveFileFilter.INSTANCE.getFileExtension(null), is(equalTo(""))); + } + + @Test + public void archiveFileFilterAcceptsJarOrZipFile() throws Exception { + assertThat(ArchiveFileFilter.INSTANCE.accept(new ClassPathResource("/cluster_config.zip").getFile()), is(true)); + } + + @Test + public void archiveFileFilterRejectsTarFile() { + assertThat(ArchiveFileFilter.INSTANCE.accept(new File("/path/to/file.tar")), is(false)); + } + + protected static class TestSnapshotServiceAdapter extends SnapshotServiceAdapterSupport { + + @Override + public SnapshotOptions createOptions() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected File[] handleLocation(final SnapshotMetadata configuration) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void load(final File directory, final SnapshotFormat format) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void load(SnapshotFormat format, SnapshotOptions options, File... snapshots) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void save(final File location, final SnapshotFormat format) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void save(File location, SnapshotFormat format, SnapshotOptions options) { + throw new UnsupportedOperationException("not implemented"); + } + } + } diff --git a/src/test/java/org/springframework/data/gemfire/SnapshotServiceImportExportIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/SnapshotServiceImportExportIntegrationTest.java new file mode 100644 index 00000000..6fcda363 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/SnapshotServiceImportExportIntegrationTest.java @@ -0,0 +1,166 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.gemfire; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.gemfire.repository.sample.Person; +import org.springframework.data.gemfire.test.support.FileSystemUtils; +import org.springframework.data.gemfire.test.support.ThreadUtils; +import org.springframework.util.FileCopyUtils; + +import com.gemstone.gemfire.cache.Region; + +/** + * The SnapshotServiceImportExportIntegrationTest class is a test suite of test cases testing the import and export + * of GemFire Cache Region data configured with SDG's Data Namespace >gfe-data:snapshot-service< (XML) element. + * + * @author John Blum + * @see org.junit.Test + * @see org.springframework.context.ConfigurableApplicationContext + * @see org.springframework.context.support.ClassPathXmlApplicationContext + * @see org.springframework.data.gemfire.SnapshotServiceFactoryBean + * @see org.springframework.data.gemfire.repository.sample.Person + * @since 1.7.0 + */ +@SuppressWarnings("unused") +public class SnapshotServiceImportExportIntegrationTest { + + protected static final AtomicLong ID_SEQUENCE = new AtomicLong(0l); + + private static ConfigurableApplicationContext applicationContext; + + private static File importPeopleSnapshot; + + private static Region people; + + protected static void assertPerson(Person expectedPerson, Person actualPerson) { + assertThat(actualPerson, is(notNullValue())); + assertThat(actualPerson.getId(), is(equalTo(expectedPerson.getId()))); + assertThat(actualPerson.getFirstname(), is(equalTo(expectedPerson.getFirstname()))); + assertThat(actualPerson.getLastname(), is(equalTo(expectedPerson.getLastname()))); + } + + protected static void assertRegion(Region actualRegion, String expectedName, int expectedSize) { + assertThat(actualRegion, is(notNullValue())); + assertThat(actualRegion.getName(), is(equalTo("People"))); + assertThat(actualRegion.getFullPath(), is(equalTo(String.format("%1$s%2$s", Region.SEPARATOR, expectedName)))); + assertThat(actualRegion.size(), is(expectedSize)); + } + + protected static Person createPerson(String firstName, String lastName) { + return createPerson(ID_SEQUENCE.incrementAndGet(), firstName, lastName); + } + + protected static Person createPerson(Long id, String firstName, String lastName) { + return new Person(id, firstName, lastName); + } + + @BeforeClass + @SuppressWarnings("unchecked") + public static void setupBeforeClass() throws Exception { + File exportDirectory = new File("./gemfire/snapshots/export"); + File importDirectory = new File("./gemfire/snapshots/import"); + + assertThat(exportDirectory.isDirectory() || exportDirectory.mkdirs(), is(true)); + assertThat(importDirectory.isDirectory() || importDirectory.mkdirs(), is(true)); + + importPeopleSnapshot = new File(importDirectory, "people.snapshot"); + + FileCopyUtils.copy(new ClassPathResource("/people.snapshot").getFile(), importPeopleSnapshot); + + assertThat(importPeopleSnapshot.isFile(), is(true)); + assertThat(importPeopleSnapshot.length() > 0, is(true)); + + applicationContext = new ClassPathXmlApplicationContext( + SnapshotServiceImportExportIntegrationTest.class.getName().replaceAll("\\.", File.separator) + .concat("-context.xml")); + + applicationContext.registerShutdownHook(); + + people = applicationContext.getBean("People", Region.class); + } + + @AfterClass + public static void tearDownAfterClass() { + applicationContext.close(); + + File exportPeopleSnapshot = new File("gemfire/snapshots/export/people.snapshot"); + + assertThat(exportPeopleSnapshot.isFile(), is(true)); + assertThat(exportPeopleSnapshot.length(), is(equalTo(importPeopleSnapshot.length()))); + + FileSystemUtils.deleteRecursive(new File(FileSystemUtils.WORKING_DIRECTORY, "gemfire")); + } + + @Before + public void setup() { + //setupPeople(); + + ThreadUtils.timedWait(TimeUnit.SECONDS.toMillis(5), 500, new ThreadUtils.WaitCondition() { + @Override public boolean waiting() { + return !(people.size() > 0); + } + }); + } + + protected void setupPeople() { + put(createPerson("Jon", "Doe")); + put(createPerson("Jane", "Doe")); + put(createPerson("Cookie", "Doe")); + put(createPerson("Fro", "Doe")); + put(createPerson("Joe", "Doe")); + put(createPerson("Lan", "Doe")); + put(createPerson("Pie", "Doe")); + put(createPerson("Play", "Doe")); + put(createPerson("Sour", "Doe")); + } + + protected Person put(Person person) { + people.putIfAbsent(person.getId(), person); + return person; + } + + @Test + public void peopleRegionIsLoaded() { + assertRegion(people, "People", 9); + assertPerson(people.get(1l), createPerson(1l, "Jon", "Doe")); + assertPerson(people.get(2l), createPerson(2l, "Jane", "Doe")); + assertPerson(people.get(3l), createPerson(3l, "Cookie", "Doe")); + assertPerson(people.get(4l), createPerson(4l, "Fro", "Doe")); + assertPerson(people.get(5l), createPerson(5l, "Joe", "Doe")); + assertPerson(people.get(6l), createPerson(6l, "Lan", "Doe")); + assertPerson(people.get(7l), createPerson(7l, "Pie", "Doe")); + assertPerson(people.get(8l), createPerson(8l, "Play", "Doe")); + assertPerson(people.get(9l), createPerson(9l, "Sour", "Doe")); + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/Person.java b/src/test/java/org/springframework/data/gemfire/repository/sample/Person.java index 9f05ec89..551b30cc 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/Person.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/Person.java @@ -20,19 +20,27 @@ import java.io.Serializable; import org.springframework.data.annotation.Id; import org.springframework.data.gemfire.mapping.Region; +import com.gemstone.gemfire.management.internal.cli.util.spring.ObjectUtils; + /** + * The Person class models a person. + * * @author Oliver Gierke + * @author John Blum + * @see java.io.Serializable */ @Region("simple") public class Person implements Serializable { private static final long serialVersionUID = 508843183613325255L; + public Address address; + @Id public Long id; + public String firstname; public String lastname; - public Address address; public Person(Long id, String firstname, String lastname) { this.id = id; @@ -40,6 +48,15 @@ public class Person implements Serializable { this.lastname = lastname; } + /** + * Returns the identifier (ID) of this Person. + * + * @return a Long value with the ID of this Person. + */ + public Long getId() { + return id; + } + /** * @return the firstname */ @@ -54,13 +71,23 @@ public class Person implements Serializable { return lastname; } + /** + * Returns the Person's full name. + * + * @return the first and last name of the Person. + * @see #getFirstname() + * @see #getLastname() + */ + public String getName() { + return String.format("%1$s %2$s", getFirstname(), getLastname()); + } + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { - if (this == obj) { return true; } @@ -71,7 +98,7 @@ public class Person implements Serializable { Person that = (Person) obj; - return this.id == null ? false : this.id.equals(that.id); + return (this.id != null && this.id.equals(that.id)); } /* @@ -80,6 +107,12 @@ public class Person implements Serializable { */ @Override public int hashCode() { - return this.id == null ? 0 : this.id.hashCode(); + return ObjectUtils.nullSafeHashCode(this.id); } + + @Override + public String toString() { + return String.format("{ @type = %1$s, id = %2$d, name = %3$s }", getClass().getName(), id, getName()); + } + } diff --git a/src/test/java/org/springframework/data/gemfire/test/support/CollectionUtils.java b/src/test/java/org/springframework/data/gemfire/test/support/CollectionUtils.java index ebd25151..dae2b3a2 100644 --- a/src/test/java/org/springframework/data/gemfire/test/support/CollectionUtils.java +++ b/src/test/java/org/springframework/data/gemfire/test/support/CollectionUtils.java @@ -17,8 +17,6 @@ package org.springframework.data.gemfire.test.support; import java.util.ArrayList; -import java.util.Enumeration; -import java.util.Iterator; import java.util.List; /** @@ -31,25 +29,8 @@ import java.util.List; * @since 1.5.0 */ @SuppressWarnings("unused") -// TODO replace with org.springframework.data.gemfire.util.CollectionUtils public abstract class CollectionUtils extends org.springframework.data.gemfire.util.CollectionUtils { - public static Iterable iterable(final Enumeration enumeration) { - return new Iterable() { - @Override public Iterator iterator() { - return org.springframework.util.CollectionUtils.toIterator(enumeration); - } - }; - } - - public static Iterable iterable(final Iterator iterator) { - return new Iterable() { - @Override public Iterator iterator() { - return iterator; - } - }; - } - public static List subList(final List source, final int... indices) { List result = new ArrayList(indices.length); for (int index : indices) { diff --git a/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsTest.java b/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsTest.java index 4aee2368..28f07e51 100644 --- a/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsTest.java +++ b/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsTest.java @@ -19,6 +19,7 @@ package org.springframework.data.gemfire.util; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.sameInstance; import static org.junit.Assert.assertNotNull; @@ -26,8 +27,14 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; @@ -40,6 +47,7 @@ import org.junit.Test; * * @author John Blum * @see java.util.Collection + * @see java.util.Enumeration * @see java.util.Iterator * @see org.junit.Test * @see org.mockito.Mockito @@ -48,16 +56,83 @@ import org.junit.Test; */ public class CollectionUtilsTest { + @Test + @SuppressWarnings("unchecked") + public void iterableEnumeration() { + Enumeration mockEnumeration = mock(Enumeration.class, "MockEnumeration"); + + when(mockEnumeration.hasMoreElements()).thenReturn(true).thenReturn(true).thenReturn(true).thenReturn(false); + when(mockEnumeration.nextElement()).thenReturn("zero").thenReturn("one").thenReturn("two") + .thenThrow(new NoSuchElementException("Enumeration exhausted")); + + Iterable iterable = CollectionUtils.iterable(mockEnumeration); + + assertThat(iterable, is(notNullValue())); + + List actualList = new ArrayList(3); + + for (String element : iterable) { + actualList.add(element); + } + + assertThat(actualList, is(equalTo(Arrays.asList("zero", "one", "two")))); + + verify(mockEnumeration, times(4)).hasMoreElements(); + verify(mockEnumeration, times(3)).nextElement(); + } + + @Test + @SuppressWarnings("unchecked") + public void iterableIterator() { + Iterator mockIterator = mock(Iterator.class, "MockIterator"); + + when(mockIterator.hasNext()).thenReturn(true).thenReturn(true).thenReturn(true).thenReturn(false); + when(mockIterator.next()).thenReturn("zero").thenReturn("one").thenReturn("two") + .thenThrow(new NoSuchElementException("Iterator exhausted")); + + Iterable iterable = CollectionUtils.iterable(mockIterator); + + assertThat(iterable, is(notNullValue())); + + List actualList = new ArrayList(3); + + for (String element : iterable) { + actualList.add(element); + } + + assertThat(actualList, is(equalTo(Arrays.asList("zero", "one", "two")))); + + verify(mockIterator, times(4)).hasNext(); + verify(mockIterator, times(3)).next(); + } + + @Test + public void nullSafeCollectionWithNonNullCollection() { + List mockList = mock(List.class); + + assertSame(mockList, CollectionUtils.nullSafeCollection(mockList)); + } + + @Test + public void nullSafeCollectionWithNullCollection() { + Collection collection = CollectionUtils.nullSafeCollection(null); + + assertNotNull(collection); + assertTrue(collection.isEmpty()); + } + @Test @SuppressWarnings("unchecked") public void nullSafeIterableWithNonNullIterable() { Iterable mockIterable = mock(Iterable.class); + assertThat(CollectionUtils.nullSafeIterable(mockIterable), is(sameInstance(mockIterable))); } @Test public void nullSafeIterableWithNullIterable() { Iterable iterable = CollectionUtils.nullSafeIterable(null); + assertThat(iterable, is(not(nullValue()))); assertThat(iterable.iterator(), is(not(nullValue()))); } @@ -87,18 +162,4 @@ public class CollectionUtilsTest { } } - @Test - public void nullSafeCollectionWithNonNullCollection() { - List mockList = mock(List.class); - assertSame(mockList, CollectionUtils.nullSafeCollection(mockList)); - } - - @Test - public void nullSafeCollectionWithNullCollection() { - Collection collection = CollectionUtils.nullSafeCollection(null); - - assertNotNull(collection); - assertTrue(collection.isEmpty()); - } - } diff --git a/src/test/resources/org/springframework/data/gemfire/SnapshotServiceImportExportIntegrationTest-context.xml b/src/test/resources/org/springframework/data/gemfire/SnapshotServiceImportExportIntegrationTest-context.xml new file mode 100644 index 00000000..58ba76ef --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/SnapshotServiceImportExportIntegrationTest-context.xml @@ -0,0 +1,29 @@ + + + + + SnapshotServiceImportExportIntegrationTest + 0 + warning + + + + + + + + + + + + diff --git a/src/test/resources/people.snapshot b/src/test/resources/people.snapshot new file mode 100644 index 0000000000000000000000000000000000000000..458f85f4a819aa5d56fa3dadc313c20c6bf1ee98 GIT binary patch literal 2997 zcmd_sF;2rU6b9gzG-(=Df{LAq73>)(LI_l(C_<5X0;9T7Xc7n8E$za{(g6mz0#gyU zfQ382z{<#4NSdOe%--zLdH&;ka%`KYoh~d|^9dXrP?p8i-7CAmm>ccw*%#P?!!F|? z5*d%uu+K?CuNWVo9+3ovH0ej2B2F_VBFXp!36kVFD4<-h)LDPo`F?zUHNo=1A-x`_ zLP%))c_OYP;vx|*6Y(k$mx=hO9KG*@o4-@YG@prt4gZ3S2#!e_;;t;Fm=~B)PeSY8 z6Q3>m8!;)zUBWe(<1CX<^H`dH;#!e{iWIb{oqpcE-EO^`V0d7UNIaxt4y{$u*)Tb$ z{ATvB*}VUlngHYM4=&F;ZXDL6J^W^53twX9VvEnR-^ z4rfXirB#8nb%FGl+7)S6AnUq7`b6!4Q?Ecex