org.codice.ddf.configuration.admin.ExportMigrationConfigurationAdminContext.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.configuration.admin.ExportMigrationConfigurationAdminContext.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.admin;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.Validate;
import org.apache.felix.fileinstall.internal.DirectoryWatcher;
import org.codice.ddf.migration.ExportMigrationContext;
import org.codice.ddf.migration.ExportMigrationEntry;
import org.codice.ddf.migration.MigrationWarning;
import org.codice.ddf.platform.io.internal.PersistenceStrategy;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class extends on the {@link ExportMigrationContext} interface to pre-create entries for all
 * configuration objects.
 */
public class ExportMigrationConfigurationAdminContext {
    private static final Logger LOGGER = LoggerFactory.getLogger(ExportMigrationConfigurationAdminContext.class);

    private static final Path ADMIN_PATH = Paths.get("admin");

    private static final Path ETC_DIR = Paths.get("etc");

    private final ExportMigrationContext context;

    private final String defaultFileExtension;

    private final ConfigurationAdminMigratable admin;

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

    private boolean warnedDefaultExtension = false;

    private final Set<ExportMigrationEntry> fileEntries;

    private final Map<Path, ExportMigrationConfigurationAdminEntry> memoryEntries;

    public ExportMigrationConfigurationAdminContext(ExportMigrationContext context, String defaultFileExtension,
            ConfigurationAdminMigratable admin, Configuration[] configs) {
        Validate.notNull(context, "invalid null contexts");
        Validate.notNull(defaultFileExtension, "invalid null default file extension");
        Validate.notNull(admin, "invalid null configuration admin migratable");
        Validate.notNull(configs, "invalid null configurations");
        this.context = context;
        this.defaultFileExtension = defaultFileExtension;
        this.admin = admin;
        this.fileEntries = context
                .entries(ExportMigrationConfigurationAdminContext.ETC_DIR, false, admin::isConfigFile)
                .collect(Collectors.toSet());
        this.memoryEntries = Stream.of(configs).filter(this::isValid).map(this::getEntry).filter(Objects::nonNull)
                .collect(Collectors.toMap(ExportMigrationConfigurationAdminEntry::getPath, Function.identity()));
    }

    /**
     * This method is designed to circumvent a bug in Felix where it has been seen that a config
     * object that is meant to represent a managed service factory is not properly created. Instead of
     * being created using the createFactoryConfiguration(), it is obtained using getConfiguration().
     * After that, the properties are updated with the service factory pid as a property. Although it
     * has that property, it ain't a real managed service factory as the factory or blueprint was
     * never called to instantiate a corresponding service. In such case, we end up with a dummy
     * config object in memory that is not attached to an actual instance of manager service.
     *
     * <p>If we were to export that object, we would end up re-creating it as a managed service
     * factory on the other system and we potentially could end up with multiple instances of that
     * manager service which could create problem. In addition, the system would not be a proper
     * representation of the original system which didn't have that associated managed service.
     *
     * <p>We do not intent to protect against all possible stupidity they could do (e.g. different
     * factory pid in properties than reported by the config object's interface). We only protect
     * against the object not being created as a managed service factory when it should have been.
     *
     * @param cfg the config to check its validity
     * @return <code>true</code> if the config is valid; <code>false</code> otherwise
     */
    private boolean isValid(Configuration cfg) {
        final String fpid = Objects.toString(cfg.getProperties().get(ConfigurationAdmin.SERVICE_FACTORYPID), null);

        if (fpid == null) {
            return true;
        } // else - property reports it should be a managed service factory, so it is valid only if the
          // cfg object reports it is too
        return ConfigurationAdminMigratable.isManagedServiceFactory(cfg);
    }

    public Stream<ExportMigrationEntry> fileEntries() {
        return fileEntries.stream();
    }

    public Stream<ExportMigrationConfigurationAdminEntry> memoryEntries() {
        return memoryEntries.values().stream();
    }

    @Nullable
    private ExportMigrationConfigurationAdminEntry getEntry(Configuration configuration) {
        Path path = getPathFromConfiguration(configuration);
        final String pathString = path.toString();
        final String extn = FilenameUtils.getExtension(pathString);
        PersistenceStrategy ps = admin.getPersister(extn);

        if (ps == null) {
            if (warnedExtensions.add(extn)) {
                context.getReport()
                        .record(new MigrationWarning(
                                String.format("Persistence strategy [%s] is not defined; defaulting to [%s]", extn,
                                        defaultFileExtension)));
            }
            ps = admin.getPersister(defaultFileExtension);
            if (ps == null) {
                if (!warnedDefaultExtension) {
                    this.warnedDefaultExtension = true;
                    context.getReport().record(new MigrationWarning(String
                            .format("Default persistence strategy [%s] is not defined", defaultFileExtension)));
                }
                return null;
            }
            path = Paths.get(pathString + FilenameUtils.EXTENSION_SEPARATOR + defaultFileExtension);
        }
        return new ExportMigrationConfigurationAdminEntry(context.getEntry(path), configuration, ps);
    }

    private Path getPathFromConfiguration(Configuration configuration) {
        final Object o = configuration.getProperties().get(DirectoryWatcher.FILENAME);
        Path path = null;

        if (o != null) {
            try {
                if (o instanceof URL) {
                    path = new File(((URL) o).toURI()).toPath();
                } else if (o instanceof URI) {
                    path = new File((URI) o).toPath();
                } else if (o instanceof String) {
                    path = new File(new URL((String) o).toURI()).toPath();
                } else if (o instanceof File) {
                    path = ((File) o).toPath();
                } else if (o instanceof Path) {
                    path = (Path) o;
                } else {
                    path = constructPathForBasename(configuration);
                    LOGGER.debug("unsupported {} property from '{}'", DirectoryWatcher.FILENAME, o);
                    context.getReport().record(new MigrationWarning(
                            "Path [%s] from %s property for configuration [%s] is of an unsupported format; exporting as [%s].",
                            o, DirectoryWatcher.FILENAME, configuration.getPid(), path));
                }
            } catch (MalformedURLException | URISyntaxException e) {
                path = constructPathForBasename(configuration);
                LOGGER.debug(String.format("failed to parse %s property from '%s'; ", DirectoryWatcher.FILENAME, o),
                        e);
                context.getReport().record(new MigrationWarning(
                        "Path [%s] from %s property for configuration [%s] cannot be parsed; exporting as [%s].", o,
                        DirectoryWatcher.FILENAME, configuration.getPid(), path));
            }
        } else {
            path = constructPathForBasename(configuration);
        }
        // ignore the whole path if any (there shouldn't be any other than etc) and force it to be under
        // admin in the exported file
        return ExportMigrationConfigurationAdminContext.ADMIN_PATH.resolve(path.getFileName());
    }

    private Path constructPathForBasename(Configuration configuration) {
        final String fpid = configuration.getFactoryPid();
        final String basename;

        if (fpid != null) { // it is a managed service factory!!!
            // Felix Fileinstall uses the hyphen as separator between factoryPid and alias. For
            // safety reasons, all hyphens are removed from the generated UUID.
            final String alias = UUID.randomUUID().toString().replaceAll("-", "");

            basename = fpid + '-' + alias;
        } else {
            basename = configuration.getPid();
        }
        return Paths.get(basename + FilenameUtils.EXTENSION_SEPARATOR + defaultFileExtension);
    }
}