org.codice.ddf.registry.api.impl.RegistryStoreImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.registry.api.impl.RegistryStoreImpl.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.registry.api.impl;

import com.google.common.collect.ImmutableSet;
import com.thoughtworks.xstream.converters.Converter;
import ddf.catalog.Constants;
import ddf.catalog.data.Metacard;
import ddf.catalog.data.Result;
import ddf.catalog.filter.delegate.TagsFilterDelegate;
import ddf.catalog.operation.CreateRequest;
import ddf.catalog.operation.CreateResponse;
import ddf.catalog.operation.DeleteRequest;
import ddf.catalog.operation.DeleteResponse;
import ddf.catalog.operation.OperationTransaction;
import ddf.catalog.operation.Query;
import ddf.catalog.operation.QueryRequest;
import ddf.catalog.operation.SourceResponse;
import ddf.catalog.operation.UpdateRequest;
import ddf.catalog.operation.UpdateResponse;
import ddf.catalog.operation.impl.CreateRequestImpl;
import ddf.catalog.operation.impl.CreateResponseImpl;
import ddf.catalog.operation.impl.DeleteRequestImpl;
import ddf.catalog.operation.impl.QueryImpl;
import ddf.catalog.operation.impl.QueryRequestImpl;
import ddf.catalog.operation.impl.SourceResponseImpl;
import ddf.catalog.source.IngestException;
import ddf.catalog.source.UnsupportedQueryException;
import ddf.security.SecurityConstants;
import ddf.security.encryption.EncryptionService;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import oasis.names.tc.ebxml_regrep.xsd.rim._3.ExternalIdentifierType;
import oasis.names.tc.ebxml_regrep.xsd.rim._3.RegistryPackageType;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.map.PassiveExpiringMap;
import org.apache.commons.lang.StringUtils;
import org.codice.ddf.cxf.client.ClientFactoryFactory;
import org.codice.ddf.parser.ParserException;
import org.codice.ddf.registry.api.internal.RegistryStore;
import org.codice.ddf.registry.common.RegistryConstants;
import org.codice.ddf.registry.common.metacard.RegistryObjectMetacardType;
import org.codice.ddf.registry.common.metacard.RegistryUtility;
import org.codice.ddf.registry.schemabindings.helper.MetacardMarshaller;
import org.codice.ddf.spatial.ogc.catalog.common.AvailabilityCommand;
import org.codice.ddf.spatial.ogc.csw.catalog.common.CswSourceConfiguration;
import org.codice.ddf.spatial.ogc.csw.catalog.common.source.AbstractCswStore;
import org.opengis.filter.Filter;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.metatype.MetaTypeInformation;
import org.osgi.service.metatype.MetaTypeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RegistryStoreImpl extends AbstractCswStore implements RegistryStore {

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

    private static final String IDENTITY_NODE_ERROR_MSG = "Unable to retrieve identity node of remote registry {}. Check to make sure the CSW endpoint supports registry operations and has a populated identity node.";

    public static final String PUSH_ALLOWED_PROPERTY = "pushAllowed";

    public static final String PULL_ALLOWED_PROPERTY = "pullAllowed";

    public static final String AUTO_PUSH = "autoPush";

    public static final String REGISTRY_URL = "registryUrl";

    public static final String ID = "id";

    private static int noPortFound = -1;

    private boolean pushAllowed = true;

    private boolean pullAllowed = true;

    private boolean autoPush = true;

    private String registryId = "";

    private String connectionType;

    private String factoryPid;

    private MetacardMarshaller metacardMarshaller;

    private ConfigurationAdmin configAdmin;

    private MetaTypeService metaTypeService;

    // age off entries after 3 seconds which should be enough time for the remote systems solr to
    // persist the entry
    private Map<String, Metacard> recent = new PassiveExpiringMap<>(TimeUnit.SECONDS.toMillis(3));

    public RegistryStoreImpl(BundleContext context, CswSourceConfiguration cswSourceConfiguration,
            Converter provider, ClientFactoryFactory clientFactoryFactory, EncryptionService encryptionService) {
        super(context, cswSourceConfiguration, provider, clientFactoryFactory, encryptionService);
    }

    public RegistryStoreImpl(EncryptionService encryptionService, ClientFactoryFactory clientFactoryFactory) {
        super(encryptionService, clientFactoryFactory);
    }

    @Override
    protected Map<String, Consumer<Object>> getAdditionalConsumers() {
        Map<String, Consumer<Object>> map = new HashMap<>();
        map.put(REGISTRY_URL, value -> setRegistryUrl((String) value));
        map.put(AUTO_PUSH, value -> setAutoPush((Boolean) value));
        map.put(ID, value -> setId((String) value));
        map.put(PUSH_ALLOWED_PROPERTY, value -> setPushAllowed((Boolean) value));
        map.put(PULL_ALLOWED_PROPERTY, value -> setPullAllowed((Boolean) value));

        map.put(RegistryConstants.CONFIGURATION_REGISTRY_ID_PROPERTY, value -> setRegistryId((String) value));
        return map;
    }

    @Override
    public boolean isPushAllowed() {
        return pushAllowed;
    }

    @Override
    public boolean isPullAllowed() {
        return pullAllowed;
    }

    @Override
    public boolean isAutoPush() {
        return autoPush;
    }

    @Override
    public CreateResponse create(CreateRequest request) throws IngestException {

        if (request.getMetacards().stream().map(RegistryUtility::getRegistryId).anyMatch(Objects::isNull)) {
            throw new IngestException("One or more of the metacards is not a registry metacard");
        }

        validateOperation();

        List<Filter> regIdFilters = request
                .getMetacards().stream().map(e -> filterBuilder
                        .attribute(RegistryObjectMetacardType.REMOTE_METACARD_ID).is().equalTo().text(e.getId()))
                .collect(Collectors.toList());
        Filter tagFilter = filterBuilder.attribute(Metacard.TAGS).is().equalTo()
                .text(RegistryConstants.REGISTRY_TAG_INTERNAL);
        Map<String, Serializable> queryProps = new HashMap<>();
        queryProps.put(SecurityConstants.SECURITY_SUBJECT,
                request.getPropertyValue(SecurityConstants.SECURITY_SUBJECT));
        QueryImpl query = new QueryImpl(filterBuilder.allOf(tagFilter,
                filterBuilder.attribute(RegistryObjectMetacardType.REGISTRY_LOCAL_NODE).empty(),
                filterBuilder.anyOf(regIdFilters)));
        QueryRequest queryRequest = new QueryRequestImpl(query, queryProps);
        try {
            SourceResponse queryResponse = super.query(queryRequest);

            Map<String, Metacard> responseMap = queryResponse.getResults().stream().collect(
                    Collectors.toMap(e -> RegistryUtility.getRegistryId(e.getMetacard()), Result::getMetacard));

            // need to synchronize this chunk so that the map holding the recently created metacards
            // will be populated correctly. Sometimes multiple create requests for the same registry
            // metacard are made and the above query will not return the expected results because
            // the previous create has not been persisted yet on the remote system. This can result
            // in duplicates so we prevent that here by keeping a PassiveExpiringMap.
            synchronized (recent) {
                request.getMetacards().stream().filter(e -> recent.containsKey(RegistryUtility.getRegistryId(e)))
                        .forEach(mcard -> responseMap.put(RegistryUtility.getRegistryId(mcard), mcard));

                List<Metacard> metacardsToCreate = request.getMetacards().stream()
                        .filter(mcard -> !responseMap.containsKey(RegistryUtility.getRegistryId(mcard)))
                        .collect(Collectors.toList());
                List<Metacard> allMetacards = new ArrayList<>(responseMap.values());
                if (CollectionUtils.isNotEmpty(metacardsToCreate)) {
                    CreateResponse createResponse = super.create(
                            new CreateRequestImpl(metacardsToCreate, request.getProperties()));
                    createResponse.getCreatedMetacards().stream()
                            .forEach(mcard -> recent.put(RegistryUtility.getRegistryId(mcard), mcard));
                    allMetacards.addAll(createResponse.getCreatedMetacards());
                }
                return new CreateResponseImpl(request, request.getProperties(), allMetacards);
            }
        } catch (UnsupportedQueryException e) {
            LOGGER.warn("Unable to perform pre-create remote query. Proceeding with original query. Error was {}",
                    e.getMessage());
        }
        return super.create(request);
    }

    public void setRegistryUrl(String registryUrl) {
        setCswUrl(registryUrl);
    }

    @Override
    public UpdateResponse update(UpdateRequest request) throws IngestException {

        if (request.getUpdates().stream().map(e -> RegistryUtility.getRegistryId(e.getValue()))
                .anyMatch(Objects::isNull)) {
            throw new IngestException("One or more of the metacards is not a registry metacard");
        }

        Map<String, Metacard> updatedMetacards = request.getUpdates().stream()
                .collect(Collectors.toMap(e -> RegistryUtility.getRegistryId(e.getValue()), Map.Entry::getValue));

        Map<String, Metacard> origMetacards = ((OperationTransaction) request
                .getPropertyValue(Constants.OPERATION_TRANSACTION_KEY)).getPreviousStateMetacards().stream()
                        .collect(Collectors.toMap(RegistryUtility::getRegistryId, e -> e));

        // update the new metacards with the id from the orig so that they can be found on the remote
        // system
        try {
            for (Map.Entry<String, Metacard> entry : updatedMetacards.entrySet()) {
                setMetacardExtID(entry.getValue(), origMetacards.get(entry.getKey()).getId());
            }
        } catch (ParserException e) {
            throw new IngestException("Could not update metacards id", e);
        }

        return super.update(request);
    }

    @Override
    public DeleteResponse delete(DeleteRequest request) throws IngestException {
        List<String> ids = ((OperationTransaction) request.getPropertyValue(Constants.OPERATION_TRANSACTION_KEY))
                .getPreviousStateMetacards().stream().map(Metacard::getId).collect(Collectors.toList());
        DeleteRequest newRequest = new DeleteRequestImpl(ids.toArray(new String[ids.size()]),
                request.getProperties());

        return super.delete(newRequest);
    }

    @Override
    public SourceResponse query(QueryRequest request) throws UnsupportedQueryException {

        // This is a registry store so only allow registry requests through
        if (!filterAdapter.adapt(request.getQuery(), new TagsFilterDelegate(
                ImmutableSet.of(RegistryConstants.REGISTRY_TAG, RegistryConstants.REGISTRY_TAG_INTERNAL), true))) {
            return new SourceResponseImpl(request, Collections.emptyList());
        }

        SourceResponse registryQueryResponse = super.query(request);
        for (Result singleResult : registryQueryResponse.getResults()) {
            if (registryId.equals(RegistryUtility.getRegistryId(singleResult.getMetacard()))) {
                String metacardTitle = singleResult.getMetacard().getTitle();

                if (metacardTitle != null && getId() != null
                        && !getId().equals(getFullRegistryName(metacardTitle))) {
                    setId(metacardTitle);
                    updateConfiguration(metacardTitle);
                }
                break;
            }
        }
        return registryQueryResponse;
    }

    public void setAutoPush(boolean autoPush) {
        this.autoPush = autoPush;
    }

    public void setPushAllowed(boolean pushAllowed) {
        this.pushAllowed = pushAllowed;
    }

    public void setPullAllowed(boolean pullAllowed) {
        this.pullAllowed = pullAllowed;
    }

    public void setRegistryId(String registryId) {
        this.registryId = registryId;
    }

    private void setMetacardExtID(Metacard metacard, String newId) throws ParserException {

        RegistryPackageType registryPackage = metacardMarshaller.getRegistryPackageFromMetacard(metacard);

        List<ExternalIdentifierType> currentExtIdList = registryPackage.getExternalIdentifier();
        currentExtIdList.stream().filter(extId -> extId.getId().equals(RegistryConstants.REGISTRY_MCARD_ID_LOCAL))
                .findFirst().ifPresent(extId -> extId.setValue(newId));

        metacardMarshaller.setMetacardRegistryPackage(metacard, registryPackage);
    }

    public void setMetacardMarshaller(MetacardMarshaller metacardMarshaller) {
        this.metacardMarshaller = metacardMarshaller;
    }

    @Override
    protected AvailabilityCommand getAvailabilityCommand() {
        return new RegistryAvailabilityCommand();
    }

    boolean registryInfoQuery() throws UnsupportedQueryException {
        List<Filter> filters = new ArrayList<>();
        filters.add(filterBuilder.attribute(Metacard.TAGS).is().equalTo().text(RegistryConstants.REGISTRY_TAG));
        filters.add(filterBuilder
                .not(filterBuilder.attribute(RegistryObjectMetacardType.REGISTRY_IDENTITY_NODE).empty()));
        Filter filter = filterBuilder.allOf(filters);

        Map<String, Serializable> queryProps = new HashMap<>();
        queryProps.put(SecurityConstants.SECURITY_SUBJECT, getSystemSubject());
        Query newQuery = new QueryImpl(filter);
        QueryRequest queryRequest = new QueryRequestImpl(newQuery, queryProps);
        SourceResponse identityMetacard = query(queryRequest);
        if (identityMetacard.getResults().size() > 0) {
            String metacardTitle = identityMetacard.getResults().get(0).getMetacard().getTitle();
            registryId = RegistryUtility.getRegistryId(identityMetacard.getResults().get(0).getMetacard());

            return updateConfiguration(metacardTitle);
        }
        return false;
    }

    private String getConnectionType(Bundle bundle, String pid) {
        if (connectionType != null) {
            return connectionType;
        }

        Locale locale = Locale.getDefault();
        if (bundle != null) {
            if (metaTypeService != null) {
                MetaTypeInformation mti = metaTypeService.getMetaTypeInformation(bundle);
                if (mti != null) {
                    try {
                        connectionType = mti.getObjectClassDefinition(pid, locale.toString()).getName();
                        return connectionType;
                    } catch (IllegalArgumentException e) {
                        LOGGER.debug("Unable to get connection type for registry configuration. ", e);
                        return null;
                    }
                }
            }
        }

        // fallback to nothing found
        return null;
    }

    public void setMetaTypeService(MetaTypeService metaTypeService) {
        this.metaTypeService = metaTypeService;
    }

    private boolean updateConfiguration(String metacardTitle) {
        if (metacardTitle == null || StringUtils.isBlank(registryId)) {
            LOGGER.debug("Unable to update registry configurations. No metacard title or registry id.");
            return false;
        }
        String currentPid = getConfigurationPid();
        try {
            Configuration currentConfig = configAdmin.getConfiguration(currentPid);
            Dictionary<String, Object> currentProperties = currentConfig.getProperties();
            String nameToSet = getFullRegistryName(metacardTitle);
            LOGGER.info("Changed RegistryStore.id from {} to {}", getId(), nameToSet);
            setId(nameToSet);
            currentProperties.put(ID, nameToSet);
            currentProperties.put(RegistryConstants.CONFIGURATION_REGISTRY_ID_PROPERTY, registryId);
            currentConfig.update(currentProperties);
        } catch (IOException e) {
            LOGGER.debug("Unable to update registry configurations, ", e);
            return false;
        }
        return true;
    }

    private String getFactoryPid() {
        if (factoryPid != null) {
            return factoryPid;
        }
        try {
            Configuration currentConfig = configAdmin.getConfiguration(getConfigurationPid());
            factoryPid = (String) currentConfig.getProperties().get("service.factoryPid");
            return factoryPid;
        } catch (IOException e) {
            LOGGER.debug("Unable to update registry configurations, ", e);
        }
        return null;
    }

    private String getFullRegistryName(String shortName) {
        URI uri;
        String registryUrl = this.cswSourceConfiguration.getCswUrl();
        try {
            uri = new URI(registryUrl);
        } catch (Exception e) {
            LOGGER.debug("Unable to parse registry url.", e);
            return "";
        }
        String port = "";
        if (uri.getPort() != noPortFound) {
            port = ":" + uri.getPort();
        }
        String connectionType = getConnectionType(this.getBundleContext().getBundle(), getFactoryPid());
        return String.format("%s (%s%s) (%s)", shortName, uri.getHost(), port, connectionType);
    }

    BundleContext getBundleContext() {
        return FrameworkUtil.getBundle(this.getClass()).getBundleContext();
    }

    public void setConfigAdmin(ConfigurationAdmin config) {
        this.configAdmin = config;
    }

    @Override
    public String getRegistryId() {
        return registryId;
    }

    private class RegistryAvailabilityCommand implements AvailabilityCommand {

        @Override
        public boolean isAvailable() {
            LOGGER.debug("Checking availability for source {} ", cswSourceConfiguration.getId());
            boolean oldAvailability = RegistryStoreImpl.this.isAvailable();
            boolean newAvailability;
            // Simple "ping" to ensure the source is responding
            newAvailability = (getCapabilities() != null);
            if (oldAvailability != newAvailability) {
                // If the source becomes available, configure it.
                if (newAvailability) {
                    configureCswSource();
                    try {
                        // Make sure the endpoint supports registry operations
                        newAvailability = registryInfoQuery();
                        if (!newAvailability) {
                            LOGGER.warn(IDENTITY_NODE_ERROR_MSG, cswSourceConfiguration.getCswUrl());
                        }
                    } catch (UnsupportedQueryException uqe) {
                        newAvailability = false;
                        LOGGER.warn(IDENTITY_NODE_ERROR_MSG, cswSourceConfiguration.getCswUrl(), uqe);
                    }
                }
                availabilityChanged(newAvailability);
            }
            return newAvailability;
        }
    }
}