ddf.catalog.impl.operations.SourceOperations.java Source code

Java tutorial

Introduction

Here is the source code for ddf.catalog.impl.operations.SourceOperations.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 ddf.catalog.impl.operations;

import ddf.action.Action;
import ddf.action.ActionRegistry;
import ddf.catalog.content.StorageProvider;
import ddf.catalog.data.ContentType;
import ddf.catalog.impl.FrameworkProperties;
import ddf.catalog.operation.SourceInfoRequest;
import ddf.catalog.operation.SourceInfoResponse;
import ddf.catalog.operation.impl.SourceInfoResponseImpl;
import ddf.catalog.source.CatalogProvider;
import ddf.catalog.source.FederatedSource;
import ddf.catalog.source.Source;
import ddf.catalog.source.SourceDescriptor;
import ddf.catalog.source.SourceUnavailableException;
import ddf.catalog.source.impl.SourceDescriptorImpl;
import ddf.catalog.util.Describable;
import ddf.catalog.util.impl.DescribableImpl;
import ddf.catalog.util.impl.SourceDescriptorComparator;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.apache.commons.lang.Validate;
import org.codice.ddf.catalog.sourcepoller.SourcePoller;
import org.codice.ddf.catalog.sourcepoller.SourceStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Support class for source delegate operations for the {@code CatalogFrameworkImpl}.
 *
 * <p>This class contains two delegated source methods and methods to support them. No
 * operations/support methods should be added to this class except in support of CFI source
 * operations.
 */
public class SourceOperations extends DescribableImpl {
    private static final Logger LOGGER = LoggerFactory.getLogger(SourceOperations.class);

    //
    // Injected properties
    //
    private final FrameworkProperties frameworkProperties;

    private final ActionRegistry sourceActionRegistry;

    private final SourcePoller<SourceStatus> sourceStatusCache;

    private final SourcePoller<Set<ContentType>> contentTypesCache;

    //
    // Bound objects
    //
    private CatalogProvider catalog;

    private StorageProvider storage;

    /** @throws IllegalArgumentException if any of the parameters are {@code null} */
    public SourceOperations(FrameworkProperties frameworkProperties, ActionRegistry sourceActionRegistry,
            SourcePoller<SourceStatus> sourceStatusCache, SourcePoller<Set<ContentType>> contentTypesCache) {
        Validate.notNull(frameworkProperties, "frameworkProperties must be non-null");
        Validate.notNull(sourceActionRegistry, "sourceActionRegistry must be non-null");
        Validate.notNull(sourceStatusCache, "sourceStatusSourcePoller must be non-null");
        Validate.notNull(contentTypesCache, "contentTypesSourcePoller must be non-null");

        this.frameworkProperties = frameworkProperties;
        this.sourceActionRegistry = sourceActionRegistry;
        this.sourceStatusCache = sourceStatusCache;
        this.contentTypesCache = contentTypesCache;
    }

    /**
     * Invoked by blueprint when a {@link CatalogProvider} is created and bound to this
     * CatalogFramework instance.
     *
     * <p>The local catalog provider will be set to the first item in the {@link java.util.List} of
     * {@link CatalogProvider}s bound to this CatalogFramework.
     *
     * @param catalogProvider the {@link CatalogProvider} being bound to this CatalogFramework
     *     instance
     */
    public void bind(CatalogProvider catalogProvider) {
        LOGGER.trace("ENTERING: bind");

        catalog = frameworkProperties.getCatalogProviders().stream().findFirst().orElse(null);

        LOGGER.trace("EXITING: bind with catalog = {}", catalog);
    }

    /**
     * Invoked by blueprint when a {@link CatalogProvider} is deleted and unbound from this
     * CatalogFramework instance.
     *
     * <p>The local catalog provider will be reset to the new first item in the {@link java.util.List}
     * of {@link CatalogProvider}s bound to this CatalogFramework. If this list of catalog providers
     * is currently empty, then the local catalog provider will be set to <code>null</code>.
     *
     * @param catalogProvider the {@link CatalogProvider} being unbound from this CatalogFramework
     *     instance
     */
    public void unbind(CatalogProvider catalogProvider) {
        LOGGER.trace("ENTERING: unbind");

        catalog = frameworkProperties.getCatalogProviders().stream().findFirst().orElse(null);

        LOGGER.trace("EXITING: unbind with catalog = {}", catalog);
    }

    /**
     * Invoked by blueprint when a {@link StorageProvider} is created and bound to this
     * CatalogFramework instance.
     *
     * <p>The local storage provider will be set to the first item in the {@link List} of {@link
     * StorageProvider}s bound to this CatalogFramework.
     *
     * @param storageProvider the {@link CatalogProvider} being bound to this CatalogFramework
     *     instance
     */
    public void bind(StorageProvider storageProvider) {
        List<StorageProvider> storageProviders = frameworkProperties.getStorageProviders();
        LOGGER.debug("storage providers list size = {}", storageProviders.size());

        // The list of storage providers is sorted by OSGi service ranking, hence should
        // always set the local storage provider to the first item in the list.
        this.storage = storageProviders.get(0);
    }

    /**
     * Invoked by blueprint when a {@link StorageProvider} is deleted and unbound from this
     * CatalogFramework instance.
     *
     * <p>The local storage provider will be reset to the new first item in the {@link List} of {@link
     * StorageProvider}s bound to this CatalogFramework. If this list of storage providers is
     * currently empty, then the local storage provider will be set to <code>null</code>.
     *
     * @param storageProvider the {@link StorageProvider} being unbound from this CatalogFramework
     *     instance
     */
    public void unbind(StorageProvider storageProvider) {
        List<StorageProvider> storageProviders = this.frameworkProperties.getStorageProviders();
        if (!storageProviders.isEmpty()) {
            LOGGER.debug("storage providers list size = {}", storageProviders.size());
            LOGGER.debug("Setting storage to first provider in list");

            // The list of storage providers is sorted by OSGi service ranking, hence should
            // always set the local storage provider to the first item in the list.
            this.storage = storageProviders.get(0);
        } else {
            LOGGER.debug("Setting storage = NULL");
            this.storage = null;
        }
    }

    public CatalogProvider getCatalog() {
        return catalog;
    }

    public StorageProvider getStorage() {
        return storage;
    }

    //
    // Delegate methods
    //
    public Set<String> getSourceIds(boolean fanoutEnabled) {
        Set<String> ids = new TreeSet<>();

        ids.add(getId());
        if (!fanoutEnabled) {
            frameworkProperties.getFederatedSources().stream().map(Describable::getId).forEach(e -> {
                if (!ids.add(e)) {
                    LOGGER.debug("Multiple FederatedSources found for id: {}", e);
                }
            });
        }
        return ids;
    }

    public SourceInfoResponse getSourceInfo(SourceInfoRequest sourceInfoRequest, boolean fanoutEnabled)
            throws SourceUnavailableException {
        SourceInfoResponse response;
        Set<SourceDescriptor> sourceDescriptors;

        if (fanoutEnabled) {
            return getFanoutSourceInfo(sourceInfoRequest);
        }

        boolean addCatalogProviderDescriptor = false;
        try {
            validateSourceInfoRequest(sourceInfoRequest);
            // Obtain the source information based on the sourceIds in the
            // request

            sourceDescriptors = new LinkedHashSet<>();
            Set<String> requestedSourceIds = sourceInfoRequest.getSourceIds();

            // If it is an enterprise request than add all source information for the enterprise
            if (sourceInfoRequest.isEnterprise()) {

                sourceDescriptors = getFederatedSourceDescriptors(frameworkProperties.getFederatedSources(), true);
                // If Ids are specified check if they are known sources
            } else if (requestedSourceIds != null) {
                LOGGER.debug("getSourceRequest contains requested source ids");
                Set<FederatedSource> discoveredSources = new HashSet<>();

                for (String requestedSourceId : requestedSourceIds) {
                    // Check if the requestedSourceId can be found in the known federatedSources

                    final List<FederatedSource> sources = frameworkProperties.getFederatedSources().stream()
                            .filter(e -> e.getId().equals(requestedSourceId)).collect(Collectors.toList());

                    if (!sources.isEmpty()) {
                        String logMsg = (sources.size() == 1) ? "Found federated source: {}"
                                : "Multiple FederatedSources found for id: {}";
                        LOGGER.debug(logMsg, requestedSourceId);
                        discoveredSources.add(sources.get(0));
                    } else {
                        LOGGER.debug("Unable to find source: {}", requestedSourceId);

                        // Check for the local catalog provider, DDF sourceId represents this
                        if (requestedSourceId.equals(getId())) {
                            LOGGER.debug("adding CatalogSourceDescriptor since it was in sourceId list as: {}",
                                    requestedSourceId);
                            addCatalogProviderDescriptor = true;
                        }
                    }
                }

                sourceDescriptors = getFederatedSourceDescriptors(discoveredSources, addCatalogProviderDescriptor);

            } else {
                // only add the local catalogProviderDescriptor
                addCatalogSourceDescriptor(sourceDescriptors);
            }

            response = new SourceInfoResponseImpl(sourceInfoRequest, null, sourceDescriptors);

        } catch (RuntimeException re) {
            LOGGER.debug("Exception during runtime while performing getSourceInfo", re);
            throw new SourceUnavailableException("Exception during runtime while performing getSourceInfo");
        }

        return response;
    }

    //
    // Helper methods - check visibility
    //

    /**
     * Checks that the specified source is valid and available.
     *
     * @param source the {@link Source} to check availability of
     * @return true if the {@link Source} is available, false otherwise
     */
    boolean isSourceAvailable(Source source) {
        if (source == null) {
            LOGGER.debug("source is null, therefore not available");
            return false;
        }

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("Checking if source \"{}\" is available...", source.getId());
        }

        // source is considered available unless we have checked and seen otherwise
        return sourceStatusCache.getCachedValueForSource(source).map(sourceStatus -> {
            final boolean available = sourceStatus == SourceStatus.AVAILABLE;
            if (!available) {
                LOGGER.debug("source \"{}\" is not available", source.getId());
            }
            return available;
        }).orElseGet(() -> {
            LOGGER.debug("Unknown availability for source id={}", source.getId());
            return false;
        });
    }

    /**
     * Retrieves the {@link SourceDescriptor} info for all {@link FederatedSource}s in the fanout
     * configuration, but the all of the source info, e.g., content types, for all of the available
     * {@link FederatedSource}s is packed into one {@link SourceDescriptor} for the fanout
     * configuration with the fanout's site name in it. This keeps the individual {@link
     * FederatedSource}s' source info hidden from the external client.
     */
    private SourceInfoResponse getFanoutSourceInfo(SourceInfoRequest sourceInfoRequest)
            throws SourceUnavailableException {

        SourceInfoResponse response;
        SourceDescriptorImpl sourceDescriptor;
        try {

            // request
            if (sourceInfoRequest == null) {
                throw new IllegalArgumentException("SourceInfoRequest was null");
            }

            Set<SourceDescriptor> sourceDescriptors = new LinkedHashSet<>();
            Set<String> ids = sourceInfoRequest.getSourceIds();

            // Only return source descriptor information if this sourceId is
            // specified
            if (ids != null) {
                Optional<String> notLocal = ids.stream().filter(s -> !s.equals(getId())).findFirst();
                if (notLocal.isPresent()) {
                    SourceUnavailableException sourceUnavailableException = new SourceUnavailableException(
                            "Unknown source: " + notLocal.get());
                    LOGGER.debug("Throwing SourceUnavailableException for unknown source: {}", notLocal.get(),
                            sourceUnavailableException);
                    throw sourceUnavailableException;
                }
            }

            // Fanout will only add one source descriptor with all the contents
            // Using a List here instead of a Set because we should not rely on how Sources are compared
            final List<Source> availableSources = frameworkProperties.getFederatedSources().stream()
                    .filter(this::isSourceAvailable).collect(Collectors.toList());

            final Set<ContentType> contentTypes = availableSources.stream()
                    .map(source -> contentTypesCache.getCachedValueForSource(source).orElseGet(() -> {
                        LOGGER.debug("Unknown content types for source id={}", source.getId());
                        return Collections.emptySet();
                    })).flatMap(Collection::stream).collect(Collectors.toSet());

            if (isSourceAvailable(catalog)) {
                availableSources.add(catalog);
                final Optional<Set<ContentType>> catalogContentTypes = contentTypesCache
                        .getCachedValueForSource(catalog);
                if (catalogContentTypes.isPresent()) {
                    contentTypes.addAll(catalogContentTypes.get());
                } else {
                    LOGGER.debug("Unknown content types for the localSource");
                }
            }

            List<Action> actions = getSourceActions(catalog);

            // only reveal this sourceDescriptor, not the federated sources
            sourceDescriptor = new SourceDescriptorImpl(this.getId(), contentTypes, actions);
            if (this.getVersion() != null) {
                sourceDescriptor.setVersion(this.getVersion());
            }
            sourceDescriptor.setAvailable(!availableSources.isEmpty());
            sourceDescriptors.add(sourceDescriptor);

            response = new SourceInfoResponseImpl(sourceInfoRequest, null, sourceDescriptors);

        } catch (RuntimeException re) {
            throw new SourceUnavailableException("Exception during runtime while performing getSourceInfo", re);
        }
        return response;
    }

    private List<Action> getSourceActions(Source localSource) {
        return localSource != null ? sourceActionRegistry.list(localSource) : Collections.emptyList();
    }

    /**
     * Creates a {@link Set} of {@link SourceDescriptor} based on the incoming list of {@link Source}.
     *
     * @param sources {@link Collection} of {@link Source} to obtain descriptor information from
     * @return new {@link Set} of {@link SourceDescriptor}
     */
    private Set<SourceDescriptor> getFederatedSourceDescriptors(Collection<FederatedSource> sources,
            boolean addCatalogProviderDescriptor) {
        SourceDescriptorImpl sourceDescriptor;
        Set<SourceDescriptor> sourceDescriptors = new TreeSet<>(new SourceDescriptorComparator());
        if (sources != null) {
            for (Source source : sources) {
                if (source != null) {
                    String sourceId = source.getId();
                    LOGGER.debug("adding sourceId: {}", sourceId);

                    sourceDescriptor = new SourceDescriptorImpl(sourceId,
                            contentTypesCache.getCachedValueForSource(source).orElseGet(() -> {
                                LOGGER.debug("Unknown content types for source id={}", source.getId());
                                return Collections.emptySet();
                            }), sourceActionRegistry.list(source));
                    sourceDescriptor.setVersion(source.getVersion());
                    sourceDescriptor.setAvailable(isSourceAvailable(source));

                    sourceDescriptors.add(sourceDescriptor);
                }
            }
        }
        if (addCatalogProviderDescriptor) {
            addCatalogSourceDescriptor(sourceDescriptors);
        }
        return sourceDescriptors;
    }

    private void validateSourceInfoRequest(SourceInfoRequest sourceInfoRequest) {
        if (sourceInfoRequest == null) {
            throw new IllegalArgumentException("SourceInfoRequest was null");
        }
    }

    /**
     * Adds the local catalog's {@link SourceDescriptor} to the set of {@link SourceDescriptor}s for
     * this framework.
     *
     * @param descriptors the set of {@link SourceDescriptor}s to add the local catalog's descriptor
     *     to
     */
    private void addCatalogSourceDescriptor(Set<SourceDescriptor> descriptors) {
        /*
         * DDF-1614 if (catalog != null && descriptors != null ) { SourceDescriptorImpl descriptor =
         * new SourceDescriptorImpl(getId(), catalog.getContentTypes());
         * descriptor.setVersion(this.getVersion()); descriptors.add(descriptor); }
         */
        // DDF-1614: Even when no local catalog provider is configured should still
        // return a local site with the framework's ID and version (and no content types
        // since there is no catalog provider).
        // But when a local catalog provider is configured, include its content types in the
        // local site info.
        if (descriptors != null) {
            if (catalog != null) {
                final SourceDescriptorImpl descriptor = new SourceDescriptorImpl(this.getId(),
                        contentTypesCache.getCachedValueForSource(catalog).orElseGet(() -> {
                            LOGGER.debug("Unknown content types for the localSource");
                            return Collections.emptySet();
                        }), sourceActionRegistry.list(catalog));
                if (this.getVersion() != null) {
                    descriptor.setVersion(this.getVersion());
                }
                descriptor.setAvailable(isSourceAvailable(catalog));
                descriptors.add(descriptor);
            }
        }
    }
}