/** * Copyright (c) Codice Foundation * <p> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package org.codice.ddf.migration.util; import static org.apache.commons.lang.Validate.notEmpty; import static org.apache.commons.lang.Validate.notNull; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; import java.util.Properties; import java.util.function.BooleanSupplier; import java.util.function.Function; import javax.validation.constraints.NotNull; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.codice.ddf.migration.ExportMigrationException; import org.codice.ddf.migration.MigrationException; import org.codice.ddf.migration.MigrationWarning; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class for services that implement {@link org.codice.ddf.migration.Migratable}. * <p/> * <p> * <b>This code is experimental. While this interface is functional and tested, it may change or be * removed in a future version of the library.</b> * </p> */ public class MigratableUtil { private static final Logger LOGGER = LoggerFactory.getLogger(MigratableUtil.class); private static final String DDF_HOME_SYSTEM_PROP = "ddf.home"; private final Path ddfHome; /** * Constructor. * * @throws MigrationException if the utility class fails to initialize */ public MigratableUtil() throws MigrationException { this.ddfHome = Paths.get(getSystemProperty(DDF_HOME_SYSTEM_PROP)); } /** * Copies a file to a destination directory. The file to copy must be a relative path under * {@code ddf.home}, and its path must not contain any symbolic link, otherwise the file will * not be copied and a {@link MigrationWarning} will be returned. * * @param sourceFile relative path to the file to copy * @param exportDirectory root directory where the file will be copied. If the file' * relative path included any directories, those will be re-created * under this directory. * @param warnings any warnings generated during this operation (e.g., source file * outside of {@code ddf.home}) will be added to this collection * @throws MigrationException thrown if a file system error prevents the file to be copied */ public void copyFile(@NotNull Path sourceFile, @NotNull Path exportDirectory, @NotNull Collection<MigrationWarning> warnings) throws MigrationException { copy(sourceFile, exportDirectory, warnings, () -> isSourceMigratable(sourceFile, (reason) -> new PathMigrationWarning(sourceFile, reason), warnings)); } /** * Recursively copies a directory to a destination directory. The directory to * copy must be a relative path under {@code ddf.home}, and its path must not contain any * symbolic link, otherwise the directory will not be copied and a {@link MigrationWarning} * will be returned. * * @param source relative path to the directory to copy * @param exportDirectory root directory where the file will be copied. If the file' * relative path included any directories, those will be re-created * under this directory. * @param warnings any warnings generated during this operation (e.g., source file outside * of {@code ddf.home}) will be added to this collection * @throws MigrationException thrown if file system error prevents the directory to be copied */ public void copyDirectory(@NotNull Path source, @NotNull Path exportDirectory, @NotNull Collection<MigrationWarning> warnings) throws MigrationException { notNull(source, "Source cannot be null"); notNull(exportDirectory, "Destination cannot be null"); notNull(warnings, "Warning collection cannot be null"); try { if (isSourceMigratable(source, (reason) -> new PathMigrationWarning(source, reason), warnings)) { FileUtils.copyDirectory(source.toFile(), exportDirectory.resolve(source) .toFile()); } } catch (IOException e) { String message = String.format("Unable to copy [%s] to [%s].", source.toString(), exportDirectory.toString()); LOGGER.info(message, e); throw new ExportMigrationException(message, e); } } /** * Copies a file, whose path is taken from a {@link System} property value, to a destination * directory. The file to copy must be a relative path under {@code ddf.home}, and its path * must not contain any symbolic link, otherwise the file will not be copied and a * {@link MigrationWarning} will be returned. * * @param systemProperty name of the {@link System} property that contains the path to the * source file * @param exportDirectory root directory where the file will be copied. If the file' * relative path included any directories, those will be re-created * under this directory. * @param warnings any warnings generated during this operation (e.g., source file outside * of {@code ddf.home}) will be added to this collection */ public void copyFileFromSystemPropertyValue(@NotNull String systemProperty, @NotNull Path exportDirectory, @NotNull Collection<MigrationWarning> warnings) throws MigrationException { String source = System.getProperty(systemProperty); notEmpty(source, String.format("Source path property [%s] is invalid: [%s]", systemProperty, source)); Path sourcePath = Paths.get(source); copy(sourcePath, exportDirectory, warnings, () -> isSourceMigratable(sourcePath, (reason) -> new PathMigrationWarning(systemProperty, sourcePath, reason), warnings)); } /** * Copies a file, whose path is taken from the value a Java properties file, to a destination. * directory. The file to copy must be a relative path under {@code ddf.home}, and its path * must not contain any symbolic link, otherwise the file will not be copied and a * {@link MigrationWarning} will be returned. * * @param propertyFilePath path to the Java properties file that contains the path to the * source file to copy * @param javaProperty name of the property inside the Java properties file that contains * the path to the source file * @param exportDirectory path to the destination * @param warnings any warnings generated during this operation (e.g., source file * outside of {@code ddf.home}) will be added to this collection */ public void copyFileFromJavaPropertyValue(@NotNull Path propertyFilePath, @NotNull String javaProperty, @NotNull Path exportDirectory, @NotNull Collection<MigrationWarning> warnings) throws MigrationException { notNull(propertyFilePath, "Java properties file cannot be null"); Properties properties = readPropertiesFile(ddfHome.resolve(propertyFilePath)); String source = (String) properties.get(javaProperty); notEmpty(source, String.format("Source path property [%s] is invalid: [%s]", javaProperty, source)); Path sourcePath = Paths.get(source); copy(sourcePath, exportDirectory, warnings, () -> isSourceMigratable(sourcePath, (reason) -> new PathMigrationWarning(propertyFilePath, javaProperty, sourcePath, reason), warnings)); } /** * Reads a java properties file and returns the value of the specified property. * * @param propertyFilePath path to the Java properties file that contains the path to the * source file to copy * @param javaProperty name of the property inside the Java properties file that contains * the path to the source file * @return the value of the property if found, null otherwise * @throws MigrationException thrown when the supplied properties file cannot be read */ public String getJavaPropertyValue(@NotNull Path propertyFilePath, @NotNull String javaProperty) throws MigrationException { notNull(propertyFilePath, "Java properties file cannot be null"); notNull(javaProperty, "Property cannot be null"); Properties properties = readPropertiesFile(ddfHome.resolve(propertyFilePath)); String value = (String) properties.get(javaProperty); return value; } private String getSystemProperty(String property) throws MigrationException { String prop = System.getProperty(property); if (StringUtils.isBlank(prop)) { String message = String.format("System property [%s] is not set", property); LOGGER.info(message); throw new ExportMigrationException(message); } return prop; } private void copy(Path sourceFile, Path exportDirectory, Collection<MigrationWarning> warnings, BooleanSupplier isSourceMigratable) throws MigrationException { notNull(sourceFile, "Source file cannot be null"); notNull(exportDirectory, "Destination cannot be null"); notNull(warnings, "Warning collection cannot be null"); try { if (isSourceMigratable.getAsBoolean()) { FileUtils.copyFile(sourceFile.toFile(), exportDirectory.resolve(sourceFile) .toFile()); } } catch (IOException e) { String message = String.format("Unable to copy [%s] to [%s]", sourceFile.toString(), exportDirectory.toString()); LOGGER.info(message, e); throw new ExportMigrationException(message, e); } } private Properties readPropertiesFile(Path propertiesFile) throws MigrationException { Properties properties = new Properties(); try (InputStream inputStream = new FileInputStream(propertiesFile.toString())) { properties.load(inputStream); return properties; } catch (IOException e) { String message = String.format("Unable to read properties file [%s]", propertiesFile.toString()); LOGGER.info(message, e); throw new ExportMigrationException(message, e); } } private boolean isSourceMigratable(Path source, Function<String, PathMigrationWarning> pathWarningBuilder, Collection<MigrationWarning> warnings) { if (source.isAbsolute()) { warnings.add(pathWarningBuilder.apply("is absolute")); return false; } if (Files.isSymbolicLink(source)) { warnings.add(pathWarningBuilder.apply("contains a symbolic link")); return false; } try { if (!ddfHome.resolve(source) .toRealPath() .startsWith(ddfHome.toRealPath())) { warnings.add(pathWarningBuilder.apply(String.format("is outside [%s]", ddfHome))); return false; } } catch (IOException e) { warnings.add(pathWarningBuilder.apply("does not exist or cannot be read")); return false; } return true; } }