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

Java tutorial

Introduction

Here is the source code for org.codice.ddf.configuration.migration.ImportMigrationContextImpl.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.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.Validate;
import org.codice.ddf.migration.ImportMigrationContext;
import org.codice.ddf.migration.ImportMigrationEntry;
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 import migration context keeps track of exported migration entries for a given migratable
 * while processing an import migration operation.
 */
public class ImportMigrationContextImpl extends MigrationContextImpl<MigrationReport>
        implements ImportMigrationContext {

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

    private static final String INVALID_NULL_ZIP = "invalid null zip";

    private static final String INVALID_NULL_PATH = "invalid null path";

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

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

    private final Set<String> files = new HashSet<>();

    private final MigrationZipFile zip;

    private final List<InputStream> inputStreams = new ArrayList<>();

    private final boolean skip;

    /**
     * Creates a new migration context for an import operation representing a system context.
     *
     * @param report the migration report where warnings and errors can be recorded
     * @param zip the zip file associated with the import
     * @throws IllegalArgumentException if <code>report</code> or <code>zip</code> is <code>null
     * </code>
     * @throws java.io.IOError if unable to determine ${ddf.home}
     */
    public ImportMigrationContextImpl(MigrationReport report, MigrationZipFile zip) {
        super(report);
        Validate.notNull(zip, ImportMigrationContextImpl.INVALID_NULL_ZIP);
        this.zip = zip;
        this.skip = true;
    }

    /**
     * Creates a new migration context for an import operation.
     *
     * @param report the migration report where warnings and errors can be recorded
     * @param zip the zip file associated with the import
     * @param id the migratable id
     * @throws IllegalArgumentException if <code>report</code>, <code>zip</code>, or <code>id</code>
     *     is <code>null</code>
     * @throws java.io.IOError if unable to determine ${ddf.home}
     */
    public ImportMigrationContextImpl(MigrationReport report, MigrationZipFile zip, String id) {
        super(report, id);
        Validate.notNull(zip, ImportMigrationContextImpl.INVALID_NULL_ZIP);
        this.zip = zip;
        this.skip = true;
    }

    /**
     * Creates a new migration context for an import operation.
     *
     * @param report the migration report where warnings and errors can be recorded
     * @param zip the zip file associated with the import
     * @param migratable the migratable this context is for
     * @param skip <code>true</code> to not import the migratable; <code>false</code> to import it
     *     normally
     * @throws IllegalArgumentException if <code>report</code>, <code>zip</code> or <code>migratable
     * </code> is <code>null</code>
     * @throws java.io.IOError if unable to determine ${ddf.home}
     */
    public ImportMigrationContextImpl(MigrationReport report, MigrationZipFile zip, Migratable migratable,
            boolean skip) {
        super(report, migratable);
        Validate.notNull(zip, ImportMigrationContextImpl.INVALID_NULL_ZIP);
        this.zip = zip;
        this.skip = skip;
    }

    @Override
    public Optional<ImportMigrationEntry> getSystemPropertyReferencedEntry(String name) {
        Validate.notNull(name, "invalid null system property name");
        return Optional.ofNullable(systemProperties.get(name));
    }

    @Override
    public ImportMigrationEntry getEntry(Path path) {
        Validate.notNull(path, ImportMigrationContextImpl.INVALID_NULL_PATH);
        return entries.computeIfAbsent(path, p -> new ImportMigrationEmptyEntryImpl(this, p));
    }

    @Override
    public Stream<ImportMigrationEntry> entries() {
        return entries.values().stream().map(ImportMigrationEntry.class::cast);
    }

    @Override
    public Stream<ImportMigrationEntry> entries(Path path) {
        Validate.notNull(path, ImportMigrationContextImpl.INVALID_NULL_PATH);
        return entries().filter(me -> me.getPath().startsWith(path));
    }

    @Override
    public Stream<ImportMigrationEntry> entries(Path path, PathMatcher filter) {
        Validate.notNull(filter, "invalid null path filter");
        return entries(path).filter(e -> filter.matches(e.getPath()));
    }

    @SuppressWarnings("PMD.DefaultPackage" /* designed to be called from ImportMigrationPropertyReferencedEntryImpl within this package */)
    @VisibleForTesting
    Optional<ImportMigrationEntryImpl> getOptionalEntry(Path path) {
        return Optional.ofNullable(entries.get(path));
    }

    /**
     * Performs an import using the context's migratable.
     *
     * @throws org.codice.ddf.migration.MigrationException to stop the import operation
     */
    @SuppressWarnings("PMD.DefaultPackage" /* designed to be called from ImportMigrationManagerImpl within this package */)
    void doImport() {
        doImportGivenVersionLogic(version -> {
            if (version == null) {
                migratable.doMissingImport(this);
            } else if (version.equals(migratable.getVersion())) {
                migratable.doImport(this);
            } else {
                migratable.doIncompatibleImport(this);
            }
        });
    }

    /**
     * Performs an version upgrade import using the context's migratable.
     *
     * @throws org.codice.ddf.migration.MigrationException to stop the import operation
     */
    @SuppressWarnings("PMD.DefaultPackage" /* designed to be called from ImportMigrationManagerImpl within this package */)
    void doVersionUpgradeImport() {
        doImportGivenVersionLogic(version -> {
            if (version != null) {
                migratable.doVersionUpgradeImport(this);
            }
        });
    }

    @SuppressWarnings("PMD.DefaultPackage" /* designed to be called from ImportMigrationManagerImpl within this package */)
    void addEntry(ImportMigrationEntryImpl entry) {
        entries.put(entry.getPath(), entry);
    }

    @SuppressWarnings("PMD.DefaultPackage" /* designed to be called from ImportMigrationEntryImpl within this package */)
    @VisibleForTesting
    InputStream getInputStreamFor(ImportMigrationEntryImpl entry, boolean checkAccess) throws IOException {
        if (checkAccess && files.contains(entry.getName())) {
            // we were asked to check if the migratable that is requesting this stream has read access
            // and the content was exported by the framework and not by the migratable directly, as
            // such we need to check with the security manager if one was installed
            final SecurityManager sm = System.getSecurityManager();

            if (sm != null) {
                sm.checkRead(entry.getFile().getPath());
            }
        }
        final InputStream is = zip.getInputStream(entry.getZipEntry());

        inputStreams.add(is);
        return is;
    }

    @SuppressWarnings("PMD.DefaultPackage" /* designed to be called from ImportMigrationEntryImpl within this package */)
    boolean requiresWriteAccess(ImportMigrationEntryImpl entry) {
        // if the framework didn't export that file then write access is required
        return !files.contains(entry.getName());
    }

    @VisibleForTesting
    MigrationZipFile getZip() {
        return zip;
    }

    @VisibleForTesting
    Set<String> getFiles() {
        return files;
    }

    @VisibleForTesting
    Map<Path, ImportMigrationEntryImpl> getEntries() {
        return entries;
    }

    @VisibleForTesting
    Map<String, ImportMigrationSystemPropertyReferencedEntryImpl> getSystemPropertiesReferencedEntries() {
        return systemProperties;
    }

    @Override
    protected void processMetadata(Map<String, Object> metadata) {
        LOGGER.debug("Imported metadata for {}: {}", id, metadata);
        super.processMetadata(metadata);
        // process files exported by the framework
        JsonUtils.getListFrom(metadata, MigrationContextImpl.METADATA_FILES).stream().map(JsonUtils::convertToMap)
                .map(m -> m.get(MigrationEntryImpl.METADATA_NAME)).filter(Objects::nonNull).map(Object::toString)
                .forEach(files::add);
        // process external entries first so we have a complete set of migratable data entries that
        // were exported by a migratable before we start looking at the property references
        JsonUtils.getListFrom(metadata, MigrationContextImpl.METADATA_EXTERNALS).stream()
                .map(JsonUtils::convertToMap).map(m -> new ImportMigrationExternalEntryImpl(this, m))
                .forEach(me -> entries.put(me.getPath(), me));
        // process system property references
        JsonUtils.getListFrom(metadata, MigrationContextImpl.METADATA_SYSTEM_PROPERTIES).stream()
                .map(JsonUtils::convertToMap)
                .map(m -> new ImportMigrationSystemPropertyReferencedEntryImpl(this, m))
                .forEach(me -> systemProperties.put(me.getProperty(), me));
        // process java property references
        JsonUtils.getListFrom(metadata, MigrationContextImpl.METADATA_JAVA_PROPERTIES).stream()
                .map(JsonUtils::convertToMap).map(m -> new ImportMigrationJavaPropertyReferencedEntryImpl(this, m))
                .forEach(this::addToPropertyEntry);
        // process directories exported by the framework
        // do this last so it can find all the files that were exported underneath the folders
        JsonUtils.getListFrom(metadata, MigrationContextImpl.METADATA_FOLDERS).stream().map(JsonUtils::convertToMap)
                .map(m -> new ImportMigrationDirectoryEntryImpl(this, m))
                .forEach(me -> entries.put(me.getPath(), me));
    }

    private void doImportGivenVersionLogic(Consumer<String> importVersionLogic) {
        if (migratable != null) {
            final String version = getMigratableVersion().orElse(null);

            if (skip) {
                LOGGER.debug("Skipping optional migratable [{}] with version [{}]", id, version);
                return;
            }
            LOGGER.debug("Importing migratable [{}] from version [{}]...", id, version);
            Stopwatch stopwatch = null;

            if (LOGGER.isDebugEnabled()) {
                stopwatch = Stopwatch.createStarted();
            }
            try {
                importVersionLogic.accept(version);
            } finally {
                inputStreams.forEach(IOUtils::closeQuietly); // we do not care if we failed to close them
            }
            if (LOGGER.isDebugEnabled() && (stopwatch != null)) {
                LOGGER.debug("Imported time for {}: {}", id, stopwatch.stop());
            }
        } else if (id != null) { // not a system context
            LOGGER.warn("unable to import migration data for migratable [{}]; migratable is no longer available",
                    id);
            report.record(new MigrationException(Messages.IMPORT_UNKNOWN_DATA_FOUND_ERROR));
        } // else - no errors and nothing to do for the system context
    }

    private void addToPropertyEntry(ImportMigrationJavaPropertyReferencedEntryImpl me) {
        entries.compute(me.getPropertiesPath(), (p, mpe) -> {
            if (mpe == null) {
                // create a new empty migration entry as it was not exported out (at least
                // not by this migratable)!!!!
                mpe = new ImportMigrationEmptyEntryImpl(this, p);
            }
            mpe.addPropertyReferenceEntry(me.getProperty(), me);
            return mpe;
        });
    }

    /**
     * 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();
    }
}