org.codice.ddf.configuration.migration.ExportMigrationContextImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.configuration.migration.ExportMigrationContextImpl.java

Source

/**
 * 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.configuration.migration;

import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableMap;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.function.BiPredicate;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.crypto.CipherOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.io.output.ClosedOutputStream;
import org.apache.commons.io.output.ProxyOutputStream;
import org.apache.commons.lang.Validate;
import org.codice.ddf.configuration.migration.util.AccessUtils;
import org.codice.ddf.migration.ExportMigrationContext;
import org.codice.ddf.migration.ExportMigrationEntry;
import org.codice.ddf.migration.Migratable;
import org.codice.ddf.migration.MigrationException;
import org.codice.ddf.migration.MigrationReport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The export migration context keeps track of exported migration entries for a given migratable
 * while processing an export migration operation.
 */
public class ExportMigrationContextImpl extends MigrationContextImpl<ExportMigrationReportImpl>
        implements ExportMigrationContext, Closeable {

    private static final Logger LOGGER = LoggerFactory.getLogger(ExportMigrationContextImpl.class);

    /** Holds exported migration entries keyed by the exported path. */
    private final Map<Path, ExportMigrationEntryImpl> entries = new TreeMap<>();

    /** Holds migration entries referenced from system properties keyed by the property name. */
    private final Map<String, ExportMigrationEntry> systemPropertiesEntries = new TreeMap<>();

    private final ZipOutputStream zipOutputStream;

    private final CipherUtils cipherUtils;

    private volatile OutputStream currentOutputStream;

    /**
     * Creates a new migration context for an export operation.
     *
     * @param report the migration report where to record warnings and errors
     * @param migratable the migratable this context is for
     * @param zos the output stream for the zip file being generated
     * @throws IllegalArgumentException if <code>report</code>, <code>migratable</code>, <code>zos
     * </code> is <code>null</code>
     * @throws java.io.IOError if unable to determine ${ddf.home}
     */
    ExportMigrationContextImpl(MigrationReport report, Migratable migratable, ZipOutputStream zos,
            CipherUtils cipherUtils) {
        super(new ExportMigrationReportImpl(report, migratable), migratable,
                ExportMigrationContextImpl.validateNotNull(migratable, "invalid null migratable").getVersion());
        Validate.notNull(zos, "invalid null zip output stream");
        Validate.notNull(cipherUtils, "invalid null cipher utils");
        this.zipOutputStream = zos;
        this.cipherUtils = cipherUtils;
    }

    private static <T> T validateNotNull(T t, String msg) {
        Validate.notNull(t, msg);
        return t;
    }

    @Override
    public Optional<ExportMigrationEntry> getSystemPropertyReferencedEntry(String name) {
        return AccessUtils.doPrivileged(() -> getSystemPropertyReferencedEntry(name, (r, v) -> true));
    }

    @Override
    public Optional<ExportMigrationEntry> getSystemPropertyReferencedEntry(String name,
            BiPredicate<MigrationReport, String> validator) {
        Validate.notNull(name, "invalid null system property name");
        Validate.notNull(validator, "invalid null validator");
        final ExportMigrationEntry me = systemPropertiesEntries.get(name);

        if (me != null) {
            return Optional.of(me);
        }
        final String val = System.getProperty(name);

        // must first let the validator deal with it before we check for the value
        // do not check for null first!!!
        if (!validator.test(report, val)) {
            return Optional.empty();
        } else if (val == null) {
            report.record(new MigrationException(Messages.EXPORT_SYSTEM_PROPERTY_NOT_DEFINED_ERROR, name));
            return Optional.empty();
        } else if (val.isEmpty()) {
            report.record(new MigrationException(Messages.EXPORT_SYSTEM_PROPERTY_IS_EMPTY_ERROR, name));
            return Optional.empty();
        }
        final ExportMigrationSystemPropertyReferencedEntryImpl sprop = new ExportMigrationSystemPropertyReferencedEntryImpl(
                this, name, val);

        systemPropertiesEntries.put(name, sprop);
        return Optional.of(sprop);
    }

    @Override
    public ExportMigrationEntry getEntry(Path path) {
        final ExportMigrationEntryImpl e = new ExportMigrationEntryImpl(this, path);

        return entries.computeIfAbsent(e.getPath(), p -> e);
    }

    @Override
    public Stream<ExportMigrationEntry> entries(Path path, boolean recurse) {
        final ExportMigrationEntryImpl entry = new ExportMigrationEntryImpl(this, path);

        if (!isDirectory(entry)) {
            return Stream.empty();
        }
        return FileUtils
                .listFiles(entry.getFile(), TrueFileFilter.INSTANCE, recurse ? TrueFileFilter.INSTANCE : null)
                .stream().map(File::toPath).map(this::getEntry);
    }

    @Override
    public Stream<ExportMigrationEntry> entries(Path path, boolean recurse, PathMatcher filter) {
        Validate.notNull(filter, "invalid null path filter");
        final ExportMigrationEntryImpl entry = new ExportMigrationEntryImpl(this, path);

        if (!isDirectory(entry)) {
            return Stream.empty();
        }
        return FileUtils
                .listFiles(entry.getFile(), TrueFileFilter.INSTANCE, recurse ? TrueFileFilter.INSTANCE : null)
                .stream().map(File::toPath).map(p -> new ExportMigrationEntryImpl(this, p))
                .filter(e -> filter.matches(e.getPath())).map(e -> entries.computeIfAbsent(e.getPath(), p -> e));
    }

    @Override
    public void close() throws IOException {
        final OutputStream oos = this.currentOutputStream;

        if (oos != null) {
            this.currentOutputStream = null;
            oos.close();
        }
    }

    /**
     * Performs an export using the context's migratable.
     *
     * @return metadata to export for the corresponding migratable keyed by the migratable's id
     * @throws org.codice.ddf.migration.MigrationException to stop the export operation
     */
    @SuppressWarnings("PMD.DefaultPackage" /* designed to be called from ExportMigrationManagerImpl within this package */)
    Map<String, Map<String, Object>> doExport() {
        LOGGER.debug("Exporting [{}] with version [{}]...", id, getMigratableVersion()); // version will never be empty
        Stopwatch stopwatch = null;

        if (LOGGER.isDebugEnabled()) {
            stopwatch = Stopwatch.createStarted();
        }
        migratable.doExport(this);
        if (LOGGER.isDebugEnabled() && (stopwatch != null)) {
            LOGGER.debug("Exported time for {}: {}", id, stopwatch.stop());
        }
        final Map<String, Map<String, Object>> metadata = ImmutableMap.of(id, report.getMetadata());

        LOGGER.debug("Exported metadata for {}: {}", id, metadata);
        return metadata;
    }

    @SuppressWarnings("PMD.DefaultPackage" /* designed to be called from ExportMigrationEntryImpl within this package */)
    OutputStream getOutputStreamFor(ExportMigrationEntryImpl entry) {
        try {
            close();
            // zip entries are always Unix style based on our convention
            final ZipEntry ze = new ZipEntry(id + '/' + entry.getName());

            ze.setTime(entry.getLastModifiedTime()); // save the current modified time
            zipOutputStream.putNextEntry(ze);
            final OutputStream oos = new ProxyOutputStream(zipOutputStream) {
                @Override
                public void close() throws IOException {
                    if (!(super.out instanceof ClosedOutputStream)) {
                        super.out = ClosedOutputStream.CLOSED_OUTPUT_STREAM;
                        zipOutputStream.closeEntry();
                    }
                }

                @Override
                protected void handleIOException(IOException e) throws IOException {
                    super.handleIOException(new ExportIOException(e));
                }
            };

            CipherOutputStream cos = cipherUtils.getCipherOutputStream(oos);
            this.currentOutputStream = cos;
            return cos;
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    private boolean isDirectory(ExportMigrationEntryImpl entry) {
        final File file = entry.getFile();

        if (!file.exists()) {
            report.record(new MigrationException(Messages.EXPORT_PATH_DOES_NOT_EXIST_ERROR, entry.getPath()));
            return false;
        } else if (!file.isDirectory()) {
            report.record(new MigrationException(Messages.EXPORT_PATH_NOT_A_DIRECTORY_ERROR, entry.getPath()));
            return false;
        }
        return true;
    }

    /**
     * The superclass implementation is sufficient for our needs.
     *
     * @param o the object to check
     * @return true if equal
     */
    @Override
    public boolean equals(Object o) {
        return super.equals(o);
    }

    /**
     * The superclass implementation is sufficient for our needs.
     *
     * @return the hashcode
     */
    @Override
    public int hashCode() {
        return super.hashCode();
    }
}

/**
 * Special wrapper I/O exception used to internally determine if an I/O error occurred from the
 * export output stream processing versus from reading processed entries during export. This allows
 * us to determine if we can safely continue the export in order to gather as many errors as
 * possible or if we are forced to stop the export.
 *
 * <p>Any attempts to continue exporting to a zip file when an I/O exception occurs while writing to
 * it will simply result in another exception being generated thus loosing its value. In such case,
 * we shall simply stop processing the export operation.
 */
class ExportIOException extends IOException {

    private final IOException cause;

    ExportIOException(IOException e) {
        super(e);
        this.cause = e;
    }

    public IOException getIOException() {
        return cause;
    }
}