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 00000000..458f85f4 Binary files /dev/null and b/src/test/resources/people.snapshot differ