/** * 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 java.io.Serializable; import java.security.PrivilegedActionException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.collections.CollectionUtils; 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.federationadmin.service.internal.FederationAdminException; import org.codice.ddf.registry.federationadmin.service.internal.FederationAdminService; import org.codice.ddf.security.common.Security; import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ddf.catalog.data.Metacard; import ddf.catalog.data.impl.AttributeImpl; import ddf.catalog.event.EventProcessor; /** * RegistryMetacardHandler is responsible for taking internal registry entries received from remote * registries and make/update registry entries that are visible to the rest of the registry framework. * This is achieved through the used of the metacard-tags attribute. This class also ensures that even if a * particular entry comes from multiple sources only the newest one will be represented in visible * registry entry. */ public class RegistryMetacardHandler implements EventHandler { private static final Logger LOGGER = LoggerFactory.getLogger(RegistryMetacardHandler.class); private static final int SHUTDOWN_TIMEOUT_SECONDS = 60; private final ExecutorService executor; private final FederationAdminService federationAdminService; public RegistryMetacardHandler(ExecutorService executor, FederationAdminService federationAdminService) { this.executor = executor; this.federationAdminService = federationAdminService; } @Override public void handleEvent(Event event) { Metacard mcard = (Metacard) event.getProperty(EventProcessor.EVENT_METACARD); if (mcard == null || !RegistryUtility.isInternalRegistryMetacard(mcard)) { return; } LOGGER.debug("RegistryMetacardHandler processing event {}", event.getTopic()); executor.execute(() -> processEvent(mcard, event.getTopic())); } private void processEvent(Metacard mcard, String topic) { try { Security security = Security.getInstance(); security.runAsAdminWithException(() -> { if (topic.equals(EventProcessor.EVENTS_TOPIC_DELETED)) { processMetacardDelete(mcard); } else if (topic.equals(EventProcessor.EVENTS_TOPIC_CREATED) || topic.equals( EventProcessor.EVENTS_TOPIC_UPDATED)) { processMetacardCreateUpdate(mcard); } return null; }); } catch (PrivilegedActionException e) { LOGGER.debug("Error processing registry metacard event.", e); } } private void processMetacardCreateUpdate(Metacard mcard) throws FederationAdminException { List<Metacard> metacards = federationAdminService.getRegistryMetacardsByRegistryIds( Collections.singletonList(RegistryUtility.getRegistryId(mcard))); if (CollectionUtils.isEmpty(metacards)) { prepareMetacard(mcard, null); federationAdminService.addRegistryEntry(mcard); LOGGER.debug("Successfully created new registry metacard"); } else if (shouldUpdate(mcard, metacards.get(0))) { prepareMetacard(mcard, metacards.get(0) .getId()); federationAdminService.updateRegistryEntry(mcard); LOGGER.debug("Successfully update registry metacard"); } } /** * When an internal registry entry is deleted we need to check to see if there is an associated * visible registry metacard that needs to be deleted. A visible registry metacard should be * deleted if all associated internal registry entries have been deleted. * * @param mcard * @throws FederationAdminException */ private void processMetacardDelete(Metacard mcard) throws FederationAdminException { String registryId = RegistryUtility.getRegistryId(mcard); List<Metacard> metacards = federationAdminService.getRegistryMetacardsByRegistryIds( Collections.singletonList(registryId)); if (CollectionUtils.isNotEmpty(metacards)) { if (CollectionUtils.isEmpty(federationAdminService.getInternalRegistryMetacardsByRegistryId( registryId))) { federationAdminService.deleteRegistryEntriesByRegistryIds(Collections.singletonList( registryId)); } } } /** * This is the method that converts an internal registry metacard with tag REGISTRY_TAG_INTERNAL * into a visible registry metacard with a tag of REGISTRY_TAG. Also removes other transient * metacard attributes that only internal entries have. * * @param metacard The internal registry metacard * @param id The metacard id of the visible registry metacard. Can be null */ private void prepareMetacard(Metacard metacard, String id) { List<Serializable> tags = new ArrayList<>(); tags.addAll(metacard.getTags()); tags.remove(RegistryConstants.REGISTRY_TAG_INTERNAL); tags.add(RegistryConstants.REGISTRY_TAG); metacard.setAttribute(new AttributeImpl(Metacard.TAGS, tags)); metacard.setAttribute(new AttributeImpl(RegistryObjectMetacardType.REMOTE_METACARD_ID, (Serializable) null)); metacard.setAttribute(new AttributeImpl(RegistryObjectMetacardType.REMOTE_REGISTRY_ID, (Serializable) null)); metacard.setAttribute(new AttributeImpl(Metacard.ID, id)); } private boolean shouldUpdate(Metacard newMetacard, Metacard oldMetacard) { return oldMetacard.getModifiedDate() .before(newMetacard.getModifiedDate()); } public void destroy() { executor.shutdown(); try { if (!executor.awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { executor.shutdownNow(); if (!executor.awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { LOGGER.debug("Thread pool didn't terminate"); } } } catch (InterruptedException e) { executor.shutdownNow(); } } }