From 6a6cd7c13644c620d41103e8e84eea86abfebee0 Mon Sep 17 00:00:00 2001 From: John Blum Date: Mon, 15 Jun 2020 16:45:41 -0700 Subject: [PATCH] Add new @EnableGemFireGarbageCollector annotation and logic to collect and cleanup after GemFire/Geode, deleting all files and directories in the filesystem left over after a GemFire/Geode process terminates. Resolves gh-21. --- .../EnableGemFireGarbageCollector.java | 74 +++ .../GemFireGarbageCollectorConfiguration.java | 85 ++++ ...reGarbageCollectorApplicationListener.java | 453 ++++++++++++++++++ 3 files changed, 612 insertions(+) create mode 100644 spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/integration/annotation/EnableGemFireGarbageCollector.java create mode 100644 spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/integration/annotation/GemFireGarbageCollectorConfiguration.java create mode 100644 spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/integration/context/event/GemFireGarbageCollectorApplicationListener.java diff --git a/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/integration/annotation/EnableGemFireGarbageCollector.java b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/integration/annotation/EnableGemFireGarbageCollector.java new file mode 100644 index 0000000..122a555 --- /dev/null +++ b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/integration/annotation/EnableGemFireGarbageCollector.java @@ -0,0 +1,74 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package org.springframework.data.gemfire.tests.integration.annotation; + +import java.io.File; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apache.geode.cache.DiskStore; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.event.AfterTestClassEvent; + +/** + * The {@link EnableGemFireGarbageCollector} annotation enables the cleanup of resources (e.g. files) left behind + * by Apache Geode (VMware GemFire) after the GemFire/Geode process shuts down, even in a test context. + * + * @author John Blum + * @see java.lang.annotation.Documented + * @see java.lang.annotation.Inherited + * @see java.lang.annotation.Retention + * @see java.lang.annotation.Target + * @see org.apache.geode.cache.GemFireCache + * @see org.springframework.context.annotation.Import + * @since 0.0.17 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import(GemFireGarbageCollectorConfiguration.class) +@SuppressWarnings("unused") +public @interface EnableGemFireGarbageCollector { + + /** + * Determines the Spring {@link ApplicationEvent ApplicationEvents} that trigger the framework to cleanup after + * Apache Geode / VMware GemFire given the junk it leaves behind after a process (e.g. CacheServer, Locator, + * Manager, etc) terminates. + * + * @return an array of {@link ApplicationEvent ApplicationEvents} that trigger the GemFire/Geode garbage collection + * algorithm. + * @see org.springframework.context.ApplicationEvent + * @see java.lang.Class + */ + Class[] collectOnEvents() default { AfterTestClassEvent.class }; + + /** + * Tries to cleanup all the {@link File Files} left behind by GemFire/Geode {@link DiskStore DiskStores}. + * + * @return a boolean value indicating whether the GemFire Garbage Collector should cleanup all {@link File Files} + * left behind by GemFire/Geode {@link DiskStore DiskStores}, whether for persistence or overflow; + * defaults to {@literal false}. + */ + boolean tryCleanDiskStoreFiles() default GemFireGarbageCollectorConfiguration.DEFAULT_CLEAN_DISK_STORE_FILES; + +} diff --git a/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/integration/annotation/GemFireGarbageCollectorConfiguration.java b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/integration/annotation/GemFireGarbageCollectorConfiguration.java new file mode 100644 index 0000000..4f4ae23 --- /dev/null +++ b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/integration/annotation/GemFireGarbageCollectorConfiguration.java @@ -0,0 +1,85 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package org.springframework.data.gemfire.tests.integration.annotation; + +import java.lang.annotation.Annotation; +import java.util.Optional; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportAware; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport; +import org.springframework.data.gemfire.tests.mock.context.event.GemFireGarbageCollectorApplicationListener; +import org.springframework.lang.NonNull; + +/** + * Spring {@link Configuration} class used to register beans that collect garbage irresponsibly left behind by + * Apache Geode when its processes shutdown, even in a test context. + * + * @author John Blum + * @see java.lang.annotation.Annotation + * @see org.springframework.context.ApplicationEvent + * @see org.springframework.context.ApplicationListener + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.context.annotation.ImportAware + * @see org.springframework.core.type.AnnotationMetadata + * @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport + * @see org.springframework.data.gemfire.tests.mock.context.event.GemFireGarbageCollectorApplicationListener + * @since 0.0.17 + */ +@Configuration +@SuppressWarnings("unused") +public class GemFireGarbageCollectorConfiguration extends AbstractAnnotationConfigSupport implements ImportAware { + + public static final boolean DEFAULT_CLEAN_DISK_STORE_FILES = false; + + private boolean tryCleanDiskStoreFiles = DEFAULT_CLEAN_DISK_STORE_FILES; + + @SuppressWarnings("unchecked") + private Class[] gemfireGarbageCollectorEventTypes = new Class[0]; + + @Override + protected Class getAnnotationType() { + return EnableGemFireGarbageCollector.class; + } + + @Override + @SuppressWarnings("unchecked") + public void setImportMetadata(@NonNull AnnotationMetadata importMetadata) { + + Optional.of(importMetadata) + .filter(this::isAnnotationPresent) + .map(this::getAnnotationAttributes) + .ifPresent(enableGemFireGarbageCollectorAttributes -> { + + this.gemfireGarbageCollectorEventTypes = (Class[]) + enableGemFireGarbageCollectorAttributes.getClassArray("collectOnEvents"); + + this.tryCleanDiskStoreFiles = + enableGemFireGarbageCollectorAttributes.getBoolean("tryCleanDiskStoreFiles"); + }); + } + + @Bean + ApplicationListener gemfireGarbageCollectorApplicationListener() { + return GemFireGarbageCollectorApplicationListener.create(this.gemfireGarbageCollectorEventTypes) + .tryCleanDiskStoreFiles(this.tryCleanDiskStoreFiles); + } +} diff --git a/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/integration/context/event/GemFireGarbageCollectorApplicationListener.java b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/integration/context/event/GemFireGarbageCollectorApplicationListener.java new file mode 100644 index 0000000..bcc9998 --- /dev/null +++ b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/integration/context/event/GemFireGarbageCollectorApplicationListener.java @@ -0,0 +1,453 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package org.springframework.data.gemfire.tests.integration.context.event; + +import java.io.File; +import java.io.FileFilter; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.apache.geode.cache.DiskStore; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.data.gemfire.tests.integration.annotation.GemFireGarbageCollectorConfiguration; +import org.springframework.data.gemfire.tests.util.FileSystemUtils; +import org.springframework.data.gemfire.util.ArrayUtils; +import org.springframework.data.gemfire.util.CollectionUtils; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.test.context.event.AfterTestClassEvent; +import org.springframework.util.Assert; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Spring {@link ApplicationListener} implementation used to listen for and trigger the GemFire/Geode garbage collection + * process. + * + * @author John Blum + * @see java.io.File + * @see java.io.FileFilter + * @see org.apache.geode.cache.DiskStore + * @see org.springframework.context.ApplicationContext + * @see org.springframework.context.ApplicationContextAware + * @see org.springframework.context.ApplicationEvent + * @see org.springframework.context.ApplicationListener + * @see org.springframework.data.gemfire.tests.integration.annotation.GemFireGarbageCollectorConfiguration + * @see org.springframework.test.context.event.AfterTestClassEvent + * @since 0.0.17 + */ +public class GemFireGarbageCollectorApplicationListener + implements ApplicationContextAware, ApplicationListener { + + protected static final File DEFAULT_SEARCH_DIRECTORY = FileSystemUtils.WORKING_DIRECTORY; + + /** + * Factory method used to construct a new instance of {@link GemFireGarbageCollectorApplicationListener} + * initialized with the {@link ApplicationEvent} objects that trigger the Gemfire/Geode garbage collection process. + * + * By default, the search will begin in the application working directory. + * + * @param gemfireGarbageCollectorEventTypes array of {@link ApplicationEvent} objects triggering the GemFire/Geode + * garbage collection process. + * @return a new instance of {@link GemFireGarbageCollectorApplicationListener}. + * @see #GemFireGarbageCollectorApplicationListener(File, Iterable) + * @see org.springframework.context.ApplicationEvent + */ + @SuppressWarnings("unchecked") + public static GemFireGarbageCollectorApplicationListener create( + @Nullable Class... gemfireGarbageCollectorEventTypes) { + + return create(DEFAULT_SEARCH_DIRECTORY, + Arrays.asList(ArrayUtils.nullSafeArray(gemfireGarbageCollectorEventTypes, Class.class))); + } + + /** + * Factory method used to construct a new instance of {@link GemFireGarbageCollectorApplicationListener} + * initialized with the {@link File filesystem directory location} used to begin the search and collection of + * GemFire/Geode garbage along with the {@link ApplicationEvent} objects that trigger the Gemfire/Geode garbage + * collection process. + * + * @param searchDirectory {@link File} referring to the filesystem directory location to begin the search; + * defaults to the application working directory if {@link File} is {@literal null}. + * @param gemfireGarbageCollectorEventTypes array of {@link ApplicationEvent} objects triggering the GemFire/Geode + * garbage collection process. + * @return a new instance of {@link GemFireGarbageCollectorApplicationListener}. + * @see #GemFireGarbageCollectorApplicationListener(File, Iterable) + * @see org.springframework.context.ApplicationEvent + * @see java.lang.Iterable + * @see java.io.File + */ + @SuppressWarnings("unchecked") + public static GemFireGarbageCollectorApplicationListener create(@Nullable File searchDirectory, + @Nullable Class... gemfireGarbageCollectorEventTypes) { + + return create(searchDirectory, Arrays.asList(ArrayUtils.nullSafeArray(gemfireGarbageCollectorEventTypes, Class.class))); + } + + /** + * Factory method used to construct a new instance of {@link GemFireGarbageCollectorApplicationListener} + * initialized with the {@link ApplicationEvent} objects that trigger the Gemfire/Geode garbage collection process. + * + * By default, the search will begin in the application working directory. + * + * @param gemfireGarbageCollectorEventTypes {@link Iterable} of {@link ApplicationEvent} objects triggering + * the GemFire/Geode garbage collection process. + * @return a new instance of {@link GemFireGarbageCollectorApplicationListener}. + * @see #GemFireGarbageCollectorApplicationListener(File, Iterable) + * @see org.springframework.context.ApplicationEvent + * @see java.lang.Iterable + */ + public static GemFireGarbageCollectorApplicationListener create( + @Nullable Iterable> gemfireGarbageCollectorEventTypes) { + + return new GemFireGarbageCollectorApplicationListener(gemfireGarbageCollectorEventTypes); + } + + /** + * Factory method used to construct a new instance of {@link GemFireGarbageCollectorApplicationListener} + * initialized with the {@link File filesystem directory location} used to begin the search and collection of + * GemFire/Geode garbage along with the {@link ApplicationEvent} objects that trigger the Gemfire/Geode garbage + * collection process. + * + * @param searchDirectory {@link File} referring to the filesystem directory location to begin the search; + * defaults to the application working directory if {@link File} is {@literal null}. + * @param gemfireGarbageCollectorEventTypes {@link Iterable} of {@link ApplicationEvent} objects triggering + * the GemFire/Geode garbage collection process. + * @return a new instance of {@link GemFireGarbageCollectorApplicationListener}. + * @see #GemFireGarbageCollectorApplicationListener(File, Iterable) + * @see org.springframework.context.ApplicationEvent + * @see java.lang.Iterable + * @see java.io.File + */ + public static GemFireGarbageCollectorApplicationListener create(File searchDirectory, + @Nullable Iterable> gemfireGarbageCollectorEventTypes) { + + return new GemFireGarbageCollectorApplicationListener(searchDirectory, gemfireGarbageCollectorEventTypes); + } + + private boolean tryCleanDiskStoreFilesEnabled = GemFireGarbageCollectorConfiguration.DEFAULT_CLEAN_DISK_STORE_FILES; + + private ApplicationContext applicationContext; + + private final File searchDirectory; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final Set> gemfireGarbageCollectorEventTypes; + + /** + * Constructs a new instance of {@link GemFireGarbageCollectorApplicationListener} initialized with + * an {@link Iterable} of {@link ApplicationEvent} objects that trigger a GemFire/Geode garbage collection. + * + * The search will begin in the application working directory. + * + * @param gemfireGarbageCollectorEventTypes {@link Iterable} of {@link ApplicationEvent} objects that trigger + * GemFire/Geode garbage collection. + * @see org.springframework.context.ApplicationEvent + * @see java.lang.Iterable + */ + public GemFireGarbageCollectorApplicationListener( + @Nullable Iterable> gemfireGarbageCollectorEventTypes) { + + this(FileSystemUtils.WORKING_DIRECTORY, gemfireGarbageCollectorEventTypes); + } + + /** + * Constructs a new instance of {@link GemFireGarbageCollectorApplicationListener} initialized with + * a {@link File} referring to the directory to begin the search for GemFire/Geode garbage along with + * an {@link Iterable} of {@link ApplicationEvent} objects that trigger a GemFire/Geode garbage collection. + * + * @param searchDirectory {@link File} referring to the directory begin collecting GemFire/Geode garbage. + * @param gemfireGarbageCollectorEventTypes {@link Iterable} of {@link ApplicationEvent} objects that trigger + * GemFire/Geode garbage collection. + * @see org.springframework.context.ApplicationEvent + * @see java.lang.Iterable + */ + public GemFireGarbageCollectorApplicationListener(@Nullable File searchDirectory, + @Nullable Iterable> gemfireGarbageCollectorEventTypes) { + + this.searchDirectory = searchDirectory != null ? searchDirectory : DEFAULT_SEARCH_DIRECTORY; + + Set> resolvedGemFireGarbageCollectorEventTypes = + StreamSupport.stream(CollectionUtils.nullSafeIterable(gemfireGarbageCollectorEventTypes).spliterator(), false) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + resolvedGemFireGarbageCollectorEventTypes = !resolvedGemFireGarbageCollectorEventTypes.isEmpty() + ? resolvedGemFireGarbageCollectorEventTypes + : Collections.singleton(AfterTestClassEvent.class); + + this.gemfireGarbageCollectorEventTypes = Collections.unmodifiableSet(resolvedGemFireGarbageCollectorEventTypes); + } + + /** + * Configures a reference to the Spring {@link ApplicationContext} in which this {@link ApplicationListener} + * is registered. + * + * @param applicationContext reference to the Spring {@link ApplicationContext}. + * @see org.springframework.context.ApplicationContext + */ + @Override + public void setApplicationContext(@NonNull ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + /** + * Returns an {@link Optional} reference to the configured Spring {@link ApplicationContext}. + * + * @return an {@link Optional} reference to the configured Spring {@link ApplicationContext}. + * @see org.springframework.context.ApplicationContext + * @see #setApplicationContext(ApplicationContext) + * @see java.util.Optional + */ + protected Optional getApplicationContext() { + return Optional.ofNullable(this.applicationContext); + } + + /** + * Returns a configured {@link Set} of {@link ApplicationEvent} {@link Class types} that trigger the GemFire/Geode + * garbage collection process. + * + * @return a configured {@link Set} of {@link ApplicationEvent} {@link Class types} that trigger the GemFire/Geode + * garbage collection process. + * @see org.springframework.context.ApplicationEvent + * @see java.util.Set + */ + protected @NonNull Set> getConfiguredGemFireGarbageCollectorEventTypes() { + return this.gemfireGarbageCollectorEventTypes; + } + + /** + * Determines whether the given {@link ApplicationEvent} is a configured event for triggering the GemFire/Geode + * garbage collection process. + * + * @param event {@link ApplicationEvent} to evaluate. + * @return a boolean value determining whether the given {@link ApplicationEvent} is a configured event + * for triggering the GemFire/Geode garbage collection process. + * @see org.springframework.context.ApplicationEvent + * @see #getConfiguredGemFireGarbageCollectorEventTypes() + */ + protected boolean isGemFireGarbageCollectorEvent(@Nullable ApplicationEvent event) { + + for (Class gemfireGarbageCollectorEventType + : getConfiguredGemFireGarbageCollectorEventTypes()) { + + if (gemfireGarbageCollectorEventType.isInstance(event)) { + return true; + } + } + + return false; + } + + /** + * Returns the configured SLF4J {@link Logger} to log events and messages originating from this + * {@link ApplicationListener} during the GemFire/Geode garbage collection process. + * + * @return the configured SLF4 {@link Logger}. + * @see org.slf4j.Logger + */ + protected @NonNull Logger getLogger() { + return this.logger; + } + + /** + * Returns the configured {@link File directory} referring to the filesystem location to begin the search for + * GemFire/Geode garbage. + * + * @return the configured {@link File directory} referring to the filesystem location to begin the search for + * GemFire/Geode garbage. + * @see java.io.File + */ + protected @NonNull File getSearchDirectory() { + return this.searchDirectory; + } + + /** + * Determines whether this {@link ApplicationListener GemFire/Geode Garbage Collector} should try to cleanup all + * {@link DiskStore} {@link File Files} as well. + * + * {@link DiskStore} {@link File Files} are maintained in {@link DiskStore#getDiskDirs()}. + * + * @return a boolean value indicating whether this {@link ApplicationListener GemFire/Geode Garbage Collector} + * should try to cleanup all {@link DiskStore} {@link File Files}. + */ + protected boolean isTryCleanDiskStoreFilesEnabled() { + return this.tryCleanDiskStoreFilesEnabled; + } + + /** + * Handles a Spring Container {@link ApplicationEvent} by determining whether the event is a configured event + * for triggering the GemFire/Geode garbage collection. + * + * @param event {@link ApplicationEvent} to evaluate. + * @see #isGemFireGarbageCollectorEvent(ApplicationEvent) + * @see #collectGemFireGarbage(File) + * @see org.springframework.context.ApplicationEvent + */ + @Override + public void onApplicationEvent(@NonNull ApplicationEvent event) { + + if (isGemFireGarbageCollectorEvent(event)) { + collectGemFireGarbage(getSearchDirectory()); + } + + if (isTryCleanDiskStoreFilesEnabled()) + collectGemFireDiskStoreFiles(); + } + + /** + * A {@link File} referring to the filesystem directory location to begin the search for and collection of + * GemFire/Geode garbage. + * + * @param directory {@link File} referring to the filesystem directory location to begin the search + * and collection process; must not be {@literal null}. + * @throws IllegalArgumentException if {@link File} is {@literal null} or not a directory. + * @see java.io.File + */ + protected void collectGemFireGarbage(@NonNull File directory) { + + Assert.isTrue(FileSystemUtils.isDirectory(directory), + () -> String.format("File [%s] must be a directory", directory)); + + for (File file : FileSystemUtils.safeListFiles(directory, GemFireGarbageFileFilter.INSTANCE)) { + if (FileSystemUtils.isDirectory(file)) { + FileSystemUtils.deleteRecursive(file); + } + else { + file.delete(); + } + } + + if (FileSystemUtils.isEmpty(directory)) { + directory.delete(); + } + } + + /** + * Collects and cleans up all {@link File Files} created from the {@link DiskStore DiskStores} created by + * the application. + */ + protected void collectGemFireDiskStoreFiles() { + + getApplicationContext() + .map(it -> it.getBeansOfType(DiskStore.class)) + .map(Map::values) + .ifPresent(diskStores -> diskStores.stream() + .filter(Objects::nonNull) + .forEach(diskStore -> { + for (File directory : ArrayUtils.nullSafeArray(diskStore.getDiskDirs(), File.class)) { + try { + FileSystemUtils.deleteRecursive(directory); + } + catch (Throwable ignore) { + getLogger().warn("Unable to delete DiskStore directory [{}]", directory); + } + } + })); + } + + /** + * Configures whether to try and cleanup all {@link File Files} generated from managed {@link DiskStore DiskStores}. + * + * @param enable boolean value dis/enabling {@link DiskStore} {@link File} cleanup. + * @return this {@link GemFireGarbageCollectorApplicationListener}. + */ + public GemFireGarbageCollectorApplicationListener tryCleanDiskStoreFiles(boolean enable) { + this.tryCleanDiskStoreFilesEnabled = enable; + return this; + } + + /** + * {@link FileFilter} implementation matching {@link File directories} or GemFire/Geode garbage. + */ + protected static class GemFireGarbageFileFilter implements FileFilter { + + protected static final GemFireGarbageFileFilter INSTANCE = new GemFireGarbageFileFilter(); + + protected static final FileFilter DIRECTORY_FILE_FILTER = FileSystemUtils.DirectoryOnlyFilter.INSTANCE; + + // https://gemfire.docs.pivotal.io/910/geode/managing/disk_storage/file_names_and_extensions.html + private static final FileFilter GEMFIRE_FILE_EXTENSION_FILTER = FileSystemUtils.CompositeFileFilter.or( + new FileSystemUtils.FileExtensionFilter(".dat"), + new FileSystemUtils.FileExtensionFilter(".gfs"), + new FileSystemUtils.FileExtensionFilter(".crf"), + new FileSystemUtils.FileExtensionFilter(".drf"), + new FileSystemUtils.FileExtensionFilter(".if"), + new FileSystemUtils.FileExtensionFilter(".krf"), + new FileSystemUtils.FileExtensionFilter(".lk"), + new FileSystemUtils.FileExtensionFilter(".log"), + new FileSystemUtils.FileExtensionFilter(".pid"), + new FileSystemUtils.FileExtensionFilter(".properties"), + new FileSystemUtils.FileExtensionFilter(".xml") + ); + + private static final Set GEMFIRE_FILE_NAMES = + Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + "backup", + "cache", + "configdiskdir", + "default", + "drlk_if", + "gfsecurity", + "gemfire", + "geode", + "locator", + "overflow" + ))); + + private static final Predicate GEMFIRE_FILE_NAME_FILTER = file -> + Objects.nonNull(file) && GEMFIRE_FILE_NAMES.stream().anyMatch(file.getName().toLowerCase()::startsWith); + + protected FileFilter getDirectoryFileFilter() { + return DIRECTORY_FILE_FILTER; + } + + protected FileFilter getGemFireFileExtensionFilter() { + return GEMFIRE_FILE_EXTENSION_FILTER; + } + + protected Predicate getGemFireFilenameFilter() { + return GEMFIRE_FILE_NAME_FILTER; + } + + @Override + public boolean accept(File pathname) { + return getDirectoryFileFilter().accept(pathname) || isGemFireGarbage(pathname); + } + + protected boolean isGemFireGarbage(@Nullable File file) { + return Objects.nonNull(file) && isFileExtensionOrFileNameMatch(file); + } + + protected boolean isFileExtensionOrFileNameMatch(@NonNull File file) { + return getGemFireFileExtensionFilter().accept(file) || getGemFireFilenameFilter().test(file); + } + } +}