Java tutorial
/* * Copyright 2010-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.snapshot; import static java.util.Arrays.stream; import static org.apache.geode.cache.snapshot.SnapshotOptions.SnapshotFormat; import static org.springframework.data.gemfire.snapshot.SnapshotServiceFactoryBean.SnapshotServiceAdapter; import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; 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.Optional; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.geode.cache.Cache; import org.apache.geode.cache.Region; import org.apache.geode.cache.snapshot.CacheSnapshotService; import org.apache.geode.cache.snapshot.RegionSnapshotService; import org.apache.geode.cache.snapshot.SnapshotFilter; import org.apache.geode.cache.snapshot.SnapshotOptions; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationListener; import org.springframework.data.gemfire.snapshot.event.ExportSnapshotApplicationEvent; import org.springframework.data.gemfire.snapshot.event.SnapshotApplicationEvent; import org.springframework.data.gemfire.support.AbstractFactoryBeanSupport; 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The SnapshotServiceFactoryBean class is a Spring FactoryBean used to configure and create an instance * of an appropriate Pivotal GemFire Snapshot Service to perform data import and exports. A CacheSnapshotService is created * if the Region is not specified, otherwise a RegionSnapshotService is used based on the configured Region. * * @author John Blum * @see org.springframework.beans.factory.DisposableBean * @see org.springframework.beans.factory.FactoryBean * @see org.springframework.beans.factory.InitializingBean * @see org.springframework.context.ApplicationListener * @see org.springframework.data.gemfire.snapshot.SnapshotServiceFactoryBean.SnapshotServiceAdapter * @see org.apache.geode.cache.snapshot.CacheSnapshotService * @see org.apache.geode.cache.snapshot.RegionSnapshotService * @since 1.7.0 */ @SuppressWarnings("unused") public class SnapshotServiceFactoryBean<K, V> extends AbstractFactoryBeanSupport<SnapshotServiceAdapter<K, V>> implements InitializingBean, DisposableBean, ApplicationListener<SnapshotApplicationEvent<K, V>> { protected static final SnapshotMetadata[] EMPTY_ARRAY = new SnapshotMetadata[0]; private Boolean suppressImportOnInit; private Cache cache; private Region<K, V> region; private SnapshotMetadata<K, V>[] exports; private SnapshotMetadata<K, V>[] imports; private SnapshotServiceAdapter<K, V> snapshotServiceAdapter; static boolean nullSafeIsDirectory(File file) { return file != null && file.isDirectory(); } static boolean nullSafeIsFile(File file) { return file != null && file.isFile(); } /** * Constructs and initializes the Pivotal GemFire Snapshot Service used to take a snapshot of the configured Cache * or Region if initialized. In addition, this initialization method will perform the actual import. * * @throws Exception if the construction and initialization of the Pivotal GemFire Snapshot Service fails. * @see org.springframework.data.gemfire.snapshot.SnapshotServiceFactoryBean.SnapshotServiceAdapter * @see #getSuppressImportOnInit() * @see #getImports() * @see #create() */ @Override @SuppressWarnings("unchecked") public void afterPropertiesSet() throws Exception { this.snapshotServiceAdapter = create(); if (!getSuppressImportOnInit()) { this.snapshotServiceAdapter.doImport(getImports()); } } /** * Constructs an appropriate instance of the SnapshotServiceAdapter based on the FactoryBean configuration. If * a Region has not been specified, then a Pivotal GemFire Snapshot Service for the Cache is constructed, otherwise * the Pivotal GemFire Snapshot Service for the configured Region is used. * * @return a SnapshotServiceAdapter wrapping the appropriate Pivotal GemFire Snapshot Service (either Cache or Region) * depending on the FactoryBean configuration. * @see #wrap(CacheSnapshotService) * @see #wrap(RegionSnapshotService) * @see #getRegion() */ protected SnapshotServiceAdapter create() { return Optional.ofNullable(getRegion()) .<SnapshotServiceAdapter>map(region -> wrap(region.getSnapshotService())) .orElseGet(() -> wrap(getCache().getSnapshotService())); } /** * Wraps the Pivotal GemFire CacheSnapshotService into an appropriate Adapter to uniformly access snapshot operations * on the Cache and Regions alike. * * @param cacheSnapshotService the Pivotal GemFire CacheSnapshotService to wrap. * @return a SnapshotServiceAdapter wrapping the Pivotal GemFire CacheSnapshotService. * @see SnapshotServiceFactoryBean.SnapshotServiceAdapter * @see SnapshotServiceFactoryBean.CacheSnapshotServiceAdapter * @see org.apache.geode.cache.snapshot.CacheSnapshotService */ protected SnapshotServiceAdapter<Object, Object> wrap(CacheSnapshotService cacheSnapshotService) { return new CacheSnapshotServiceAdapter(cacheSnapshotService); } /** * Wraps Pivotal GemFire's RegionSnapshotService into an appropriate Adapter to uniformly access snapshot operations * on the Cache and Regions alike. * * @param regionSnapshotService the Pivotal GemFire RegionSnapshotService to wrap. * @return a SnapshotServiceAdapter wrapping the Pivotal GemFire RegionSnapshotService. * @see SnapshotServiceFactoryBean.SnapshotServiceAdapter * @see SnapshotServiceFactoryBean.RegionSnapshotServiceAdapter * @see org.apache.geode.cache.snapshot.RegionSnapshotService */ protected SnapshotServiceAdapter<K, V> wrap(RegionSnapshotService<K, V> regionSnapshotService) { return new RegionSnapshotServiceAdapter<>(regionSnapshotService); } /** * Sets a reference to the Pivotal GemFire Cache for which the snapshot will be taken. * * @param cache the Pivotal GemFire Cache used to create an instance of CacheSnapshotService. * @throws IllegalArgumentException if the Cache reference is null. * @see org.apache.geode.cache.Cache * @see #getCache() */ public void setCache(Cache cache) { this.cache = Optional.ofNullable(cache) .orElseThrow(() -> newIllegalArgumentException("Cache must not be null")); } /** * Gets a reference to the Pivotal GemFire Cache for which the snapshot will be taken. * * @return the Pivotal GemFire Cache used to create an instance of CacheSnapshotService. * @throws IllegalStateException if the Cache argument is null. * @see org.apache.geode.cache.Cache * @see #setCache(Cache) */ protected Cache getCache() { return Optional.ofNullable(this.cache) .orElseThrow(() -> newIllegalStateException("The cache was not properly initialized")); } /** * Sets the meta-data (location, filter and format) used to create a snapshot from the Cache or Region data. * * @param exports an array of snapshot meta-data used for each export. * @see SnapshotServiceFactoryBean.SnapshotMetadata */ public void setExports(SnapshotMetadata<K, V>[] exports) { this.exports = exports; } /** * Sets the meta-data (location, filter and format) used to create a snapshot from the Cache or Region data. * * @return an array of snapshot meta-data used for each export. * @see SnapshotServiceFactoryBean.SnapshotMetadata */ @SuppressWarnings("unchecked") protected SnapshotMetadata<K, V>[] getExports() { return nullSafeArray(exports, SnapshotMetadata.class); } /** * Sets the meta-data (location, filter and format) used to read a data snapshot into an entire Cache * or individual Region. * * @param imports an array of snapshot meta-data used for each import. * @see SnapshotServiceFactoryBean.SnapshotMetadata */ public void setImports(SnapshotMetadata<K, V>[] imports) { this.imports = imports; } /** * Gets the meta-data (location, filter and format) used to read a data snapshot into an entire Cache * or individual Region. * * @return an array of snapshot meta-data used for each import. * @see SnapshotServiceFactoryBean.SnapshotMetadata */ @SuppressWarnings("unchecked") protected SnapshotMetadata<K, V>[] getImports() { return nullSafeArray(imports, SnapshotMetadata.class); } /** * Sets a reference to the Pivotal GemFire Region for which the snapshot will be taken. * * @param region the Pivotal GemFire Region used to create an instance of the RegionSnapshotService. * @see org.apache.geode.cache.Region * @see #getRegion() */ public void setRegion(Region<K, V> region) { this.region = region; } /** * Gets a reference to the Pivotal GemFire Region for which the snapshot will be taken. * * @return the Pivotal GemFire Region used to create an instance of the RegionSnapshotService. * @see org.apache.geode.cache.Region * @see #getRegion() */ protected Region<K, V> getRegion() { return this.region; } /** * Sets a boolean condition to indicate whether importing on initialization should be suppressed. * * @param suppressImportOnInit a Boolean value to indicate whether importing on initialization should be suppressed. * @see #getSuppressImportOnInit() */ public void setSuppressImportOnInit(Boolean suppressImportOnInit) { this.suppressImportOnInit = suppressImportOnInit; } /** * Determines whether importing on initialization should be suppressed. * * @return a boolean value indicating whether import on initialization should be suppressed. * @see #setSuppressImportOnInit(Boolean) * @see #afterPropertiesSet() */ protected boolean getSuppressImportOnInit() { return Boolean.TRUE.equals(suppressImportOnInit); } /** * Gets the reference to the Pivotal GemFire Snapshot Service created by this FactoryBean. * * @return the Pivotal GemFire Snapshot Service created by this FactoryBean. * @throws Exception if the Pivotal GemFire Snapshot Service failed to be created. * @see SnapshotServiceFactoryBean.SnapshotServiceAdapter */ @Override public SnapshotServiceAdapter<K, V> getObject() throws Exception { return this.snapshotServiceAdapter; } /** * Gets the type of Snapshot Service created by this FactoryBean. * * @return a Class object representing the type of Snapshot Service created by this FactoryBean. * @see SnapshotServiceFactoryBean.SnapshotServiceAdapter * @see SnapshotServiceFactoryBean.CacheSnapshotServiceAdapter * @see SnapshotServiceFactoryBean.RegionSnapshotServiceAdapter */ @Override @SuppressWarnings("unchecked") public Class<?> getObjectType() { return Optional.ofNullable(this.snapshotServiceAdapter).map(Object::getClass) .orElse((Class) SnapshotServiceAdapter.class); } /** * Determines this this FactoryBean creates single Pivotal GemFire Snapshot Service instances. * * @return true. */ @Override public boolean isSingleton() { return true; } /** * Performs an export of the Pivotal GemFire Cache or Region if configured. * * @throws Exception if the Cache/Region data export operation fails. * @see org.springframework.data.gemfire.snapshot.SnapshotServiceFactoryBean.SnapshotServiceAdapter * @see #getExports() * @see #getObject() */ @Override @SuppressWarnings("all") public void destroy() throws Exception { getObject().doExport(getExports()); } /** * Listens for SnapshotApplicationEvents triggering a Pivotal GemFire Cache-wide or Region data snapshot import/export * when details of the event match the criteria of this factory's constructed Pivotal GemFire SnapshotService. * * @param event the SnapshotApplicationEvent triggering a Pivotal GemFire Cache or Region data import/export. * @see org.springframework.data.gemfire.snapshot.SnapshotServiceFactoryBean.SnapshotServiceAdapter * @see org.springframework.data.gemfire.snapshot.event.ExportSnapshotApplicationEvent * @see org.springframework.data.gemfire.snapshot.event.ImportSnapshotApplicationEvent * @see org.springframework.data.gemfire.snapshot.event.SnapshotApplicationEvent * @see #isMatch(SnapshotApplicationEvent) * @see #resolveSnapshotMetadata(SnapshotApplicationEvent) * @see #getObject() */ @Override @SuppressWarnings("all") public void onApplicationEvent(SnapshotApplicationEvent<K, V> event) { try { if (isMatch(event)) { if (event instanceof ExportSnapshotApplicationEvent) { getObject().doExport(resolveSnapshotMetadata(event)); } else { getObject().doImport(resolveSnapshotMetadata(event)); } } } catch (Exception ignore) { } } /** * Determines whether the details of the given SnapshotApplicationEvent match the criteria of this factory * to trigger a Pivotal GemFire Cache or Region data export. * * @param event the SnapshotApplicationEvent containing details of the application requested data export. * @return a boolean value indicating whether the application requested snapshot event details match * the criteria required by this factory to trigger a Pivotal GemFire Cache or Region data export. * @see SnapshotApplicationEvent */ protected boolean isMatch(SnapshotApplicationEvent event) { return event.isCacheSnapshotEvent() || event.matches(getRegion()); } /** * Resolves the SnapshotMetadata used to perform the Pivotal GemFire Cache or Region data snapshot import/export. * If the event contains specific SnapshotMetadata, then this is preferred over the factory's own * "import" or "export" SnapshotMetadata. * * @param event the SnapshotApplicationEvent from which to resolve the SnapshotMetadata. * @return the resolved SnapshotMetadata, either from the event or this factory's configured imports/exports. * @see SnapshotApplicationEvent#getSnapshotMetadata() * @see #getExports() * @see #getImports() */ protected SnapshotMetadata<K, V>[] resolveSnapshotMetadata(SnapshotApplicationEvent<K, V> event) { SnapshotMetadata<K, V>[] eventSnapshotMetadata = event.getSnapshotMetadata(); return !ObjectUtils.isEmpty(eventSnapshotMetadata) ? eventSnapshotMetadata : (event instanceof ExportSnapshotApplicationEvent ? getExports() : getImports()); } /** * The SnapshotServiceAdapter interface is an Adapter adapting both Pivotal GemFire CacheSnapshotService * and RegionSnapshotService to treat them uniformly. * * @param <K> the class type of the Region key. * @param <V> the class type of the Region value. */ public interface SnapshotServiceAdapter<K, V> { SnapshotOptions<K, V> createOptions(); @SuppressWarnings("unchecked") void doExport(SnapshotMetadata<K, V>... configurations); @SuppressWarnings("unchecked") void doImport(SnapshotMetadata<K, V>... configurations); void load(File directory, SnapshotFormat format); void load(SnapshotFormat format, SnapshotOptions<K, V> options, File... snapshots); void save(File location, SnapshotFormat format); void save(File location, SnapshotFormat format, SnapshotOptions<K, V> options); } /** * SnapshotServiceAdapterSupport is an abstract base class for all SnapshotServiceAdapter implementations * encapsulating common reusable functionality. * * @param <K> the class type of the Cache Region key. * @param <V> the class type of the Cache Region value. * @see SnapshotServiceFactoryBean.SnapshotServiceAdapter */ protected static abstract class SnapshotServiceAdapterSupport<K, V> implements SnapshotServiceAdapter<K, V> { protected static final File TEMPORARY_DIRECTORY = new File(System.getProperty("java.io.tmpdir")); protected final Logger logger = createLog(); Logger createLog() { return LoggerFactory.getLogger(getClass()); } @Override public SnapshotOptions<K, V> createOptions() { throw new UnsupportedOperationException("not implemented"); } protected SnapshotOptions<K, V> createOptions(SnapshotMetadata<K, V> metadata) { return createOptions().invokeCallbacks(metadata.isInvokeCallbacks()).setFilter(metadata.getFilter()) .setParallelMode(metadata.isParallel()); } @Override @SuppressWarnings("unchecked") public void doExport(SnapshotMetadata<K, V>... configurations) { stream(nullSafeArray(configurations, SnapshotMetadata.class)) .forEach(configuration -> save(configuration.getLocation(), configuration.getFormat(), createOptions(configuration))); } @Override @SuppressWarnings("unchecked") public void doImport(SnapshotMetadata<K, V>... configurations) { stream(nullSafeArray(configurations, SnapshotMetadata.class)) .forEach(configuration -> load(configuration.getFormat(), createOptions(configuration), handleLocation(configuration))); } protected abstract File[] handleLocation(SnapshotMetadata<K, V> configuration); protected File[] handleDirectoryLocation(File directory) { return directory.listFiles(pathname -> nullSafeIsFile(pathname)); } protected File[] handleFileLocation(File file) { if (ArchiveFileFilter.INSTANCE.accept(file)) { try { File extractedArchiveDirectory = new File(TEMPORARY_DIRECTORY, 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 cause) { throw new ImportSnapshotException( String.format("Failed to extract archive [%1$s] to import", file), cause); } } return new File[] { file }; } protected boolean exceptionSuppressingClose(Closeable closeable) { try { closeable.close(); return true; } catch (IOException cause) { logDebug(cause, "Failed to close [%s]", closeable); return false; } } protected void logDebug(Throwable cause, String message, Object... arguments) { if (logger.isDebugEnabled()) { logger.debug(String.format(message, arguments), cause); } } @Override public void load(File directory, SnapshotFormat format) { throw new UnsupportedOperationException("not implemented"); } @Override public void load(SnapshotFormat format, SnapshotOptions<K, V> options, File... snapshots) { throw new UnsupportedOperationException("not implemented"); } @Override public void save(File location, SnapshotFormat format) { throw new UnsupportedOperationException("not implemented"); } @Override public void save(File location, SnapshotFormat format, SnapshotOptions<K, V> options) { throw new UnsupportedOperationException("not implemented"); } 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 Pivotal GemFire's CacheSnapshotService. * * @see SnapshotServiceFactoryBean.SnapshotServiceAdapterSupport */ protected static class CacheSnapshotServiceAdapter extends SnapshotServiceAdapterSupport<Object, Object> { private final CacheSnapshotService snapshotService; public CacheSnapshotServiceAdapter(CacheSnapshotService snapshotService) { Assert.notNull(snapshotService, "The backing CacheSnapshotService must not be null"); this.snapshotService = snapshotService; } protected CacheSnapshotService getSnapshotService() { return this.snapshotService; } @Override public SnapshotOptions<Object, Object> createOptions() { return getSnapshotService().createOptions(); } @Override protected File[] handleLocation(SnapshotMetadata<Object, Object> configuration) { return configuration.isFile() ? handleFileLocation(configuration.getLocation()) : handleDirectoryLocation(configuration.getLocation()); } @Override public void load(File directory, SnapshotFormat format) { try { getSnapshotService().load(directory, format); } catch (Throwable cause) { throw new ImportSnapshotException( String.format("Failed to load snapshots from directory [%1$s] in format [%2$s]", directory, format), cause); } } @Override public void load(SnapshotFormat format, SnapshotOptions<Object, Object> options, File... snapshots) { try { getSnapshotService().load(snapshots, format, options); } catch (Throwable cause) { throw new ImportSnapshotException( String.format("Failed to load snapshots [%1$s] in format [%2$s] using options [%3$s]", Arrays.toString(snapshots), format, options), cause); } } @Override public void save(File directory, SnapshotFormat format) { try { getSnapshotService().save(directory, format); } catch (Throwable cause) { throw new ExportSnapshotException(String.format( "Failed to save snapshots to directory [%1$s] in format [%2$s]", directory, format), cause); } } @Override public void save(File directory, SnapshotFormat format, SnapshotOptions<Object, Object> options) { try { getSnapshotService().save(directory, format, options); } catch (Throwable cause) { throw new ExportSnapshotException(String.format( "Failed to save snapshots to directory [%1$s] in format [%2$s] using options [%3$s]", directory, format, options), cause); } } } /** * The RegionSnapshotServiceAdapter is a SnapshotServiceAdapter adapting Pivotal GemFire's RegionSnapshotService. * * @see SnapshotServiceFactoryBean.SnapshotServiceAdapterSupport */ protected static class RegionSnapshotServiceAdapter<K, V> extends SnapshotServiceAdapterSupport<K, V> { private final RegionSnapshotService<K, V> snapshotService; public RegionSnapshotServiceAdapter(RegionSnapshotService<K, V> snapshotService) { Assert.notNull(snapshotService, "The backing RegionSnapshotService must not be null"); this.snapshotService = snapshotService; } protected RegionSnapshotService<K, V> getSnapshotService() { return this.snapshotService; } @Override public SnapshotOptions<K, V> createOptions() { return getSnapshotService().createOptions(); } @Override protected File[] handleLocation(SnapshotMetadata<K, V> configuration) { return new File[] { configuration.getLocation() }; } @Override public void load(File snapshot, SnapshotFormat format) { try { getSnapshotService().load(snapshot, format); } catch (Throwable cause) { throw new ImportSnapshotException(String.format( "Failed to load snapshot from file [%1$s] in format [%2$s]", snapshot, format), cause); } } @Override public void load(SnapshotFormat format, SnapshotOptions<K, V> options, File... snapshots) { try { for (File snapshot : snapshots) { getSnapshotService().load(snapshot, format, options); } } catch (Throwable cause) { throw new ImportSnapshotException( String.format("Failed to load snapshots [%1$s] in format [%2$s] using options [%3$s]", Arrays.toString(snapshots), format, options), cause); } } @Override public void save(File snapshot, SnapshotFormat format) { try { getSnapshotService().save(snapshot, format); } catch (Throwable cause) { throw new ExportSnapshotException( String.format("Failed to save snapshot to file [%1$s] in format [%2$s]", snapshot, format), cause); } } @Override public void save(File snapshot, SnapshotFormat format, SnapshotOptions<K, V> options) { try { getSnapshotService().save(snapshot, format, options); } catch (Throwable cause) { throw new ExportSnapshotException(String.format( "Failed to save snapshot to file [%1$s] in format [%2$s] using options [%3$s]", snapshot, format, options), cause); } } } /** * The SnapshotMetadata class encapsulates details of the Pivotal GemFire Cache or Region data snapshot * on either import or export. * * @param <K> the class type of the Region key. * @param <V> the class type of the Region value. */ public static class SnapshotMetadata<K, V> { protected static final boolean DEFAULT_INVOKE_CALLBACKS = false; protected static final boolean DEFAULT_PARALLEL = false; protected static final SnapshotFormat DEFAULT_SNAPSHOT_FORMAT = SnapshotFormat.GEMFIRE; private boolean invokeCallbacks = DEFAULT_INVOKE_CALLBACKS; private boolean parallel = DEFAULT_PARALLEL; private final File location; private final SnapshotFilter<K, V> filter; private final SnapshotFormat format; public SnapshotMetadata(File location) { this(location, DEFAULT_SNAPSHOT_FORMAT, null); } public SnapshotMetadata(File location, SnapshotFormat format) { this(location, format, null); } public SnapshotMetadata(File location, SnapshotFormat format, SnapshotFilter<K, V> filter) { Assert.notNull(location, "Location is required"); this.location = location; this.format = format; this.filter = filter; } public boolean isDirectory() { return nullSafeIsDirectory(getLocation()); } public boolean isFile() { return nullSafeIsFile(getLocation()); } public File getLocation() { return this.location; } public SnapshotFormat getFormat() { return Optional.ofNullable(this.format).orElse(DEFAULT_SNAPSHOT_FORMAT); } public boolean isFilterPresent() { return (getFilter() != null); } public SnapshotFilter<K, V> getFilter() { return this.filter; } public void setInvokeCallbacks(boolean invokeCallbacks) { this.invokeCallbacks = invokeCallbacks; } public boolean isInvokeCallbacks() { return this.invokeCallbacks; } public void setParallel(boolean parallel) { this.parallel = parallel; } public boolean isParallel() { return this.parallel; } @Override public String toString() { return String.format( "{ @type = %1$s, location = %2$s, format = %3$s, filter = %4$s, invokeCallbacks = %5$s, parallel = %6$s }", getClass().getName(), getLocation().getAbsolutePath(), getFormat(), getFilter(), isInvokeCallbacks(), isParallel()); } } /** * The ArchiveFileFilter class is a Java FileFilter implementation accepting any File that is either * a JAR file or ZIP file. * * @see java.io.File * @see java.io.FileFilter */ protected static final class ArchiveFileFilter implements FileFilter { protected static final ArchiveFileFilter INSTANCE = new ArchiveFileFilter(); protected static final List<String> 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)); } } }