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.
This commit is contained in:
John Blum
2020-06-15 16:45:41 -07:00
parent 56dc4ffec6
commit 6a6cd7c136
3 changed files with 612 additions and 0 deletions

View File

@@ -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<? extends ApplicationEvent>[] 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;
}

View File

@@ -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<? extends ApplicationEvent>[] gemfireGarbageCollectorEventTypes = new Class[0];
@Override
protected Class<? extends Annotation> 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<? extends ApplicationEvent>[])
enableGemFireGarbageCollectorAttributes.getClassArray("collectOnEvents");
this.tryCleanDiskStoreFiles =
enableGemFireGarbageCollectorAttributes.getBoolean("tryCleanDiskStoreFiles");
});
}
@Bean
ApplicationListener<ApplicationEvent> gemfireGarbageCollectorApplicationListener() {
return GemFireGarbageCollectorApplicationListener.create(this.gemfireGarbageCollectorEventTypes)
.tryCleanDiskStoreFiles(this.tryCleanDiskStoreFiles);
}
}

View File

@@ -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<ApplicationEvent> {
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<? extends ApplicationEvent>... 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<? extends ApplicationEvent>... 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<Class<? extends ApplicationEvent>> 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<Class<? extends ApplicationEvent>> 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<Class<? extends ApplicationEvent>> 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<Class<? extends ApplicationEvent>> 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<Class<? extends ApplicationEvent>> gemfireGarbageCollectorEventTypes) {
this.searchDirectory = searchDirectory != null ? searchDirectory : DEFAULT_SEARCH_DIRECTORY;
Set<Class<? extends ApplicationEvent>> 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<ApplicationContext> 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<Class<? extends ApplicationEvent>> 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<? extends ApplicationEvent> 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<String> GEMFIRE_FILE_NAMES =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"backup",
"cache",
"configdiskdir",
"default",
"drlk_if",
"gfsecurity",
"gemfire",
"geode",
"locator",
"overflow"
)));
private static final Predicate<File> 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<File> 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);
}
}
}