/**
* 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.federationadmin.service.impl;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.codice.ddf.parser.ParserException;
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.registry.schemabindings.helper.InternationalStringTypeHelper;
import org.codice.ddf.registry.schemabindings.helper.MetacardMarshaller;
import org.codice.ddf.registry.schemabindings.helper.SlotTypeHelper;
import org.codice.ddf.security.common.Security;
import org.geotools.filter.SortByImpl;
import org.opengis.filter.Filter;
import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.sort.SortBy;
import org.opengis.filter.sort.SortOrder;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ddf.catalog.CatalogFramework;
import ddf.catalog.data.Metacard;
import ddf.catalog.data.Result;
import ddf.catalog.filter.FilterBuilder;
import ddf.catalog.filter.impl.PropertyNameImpl;
import ddf.catalog.operation.CreateRequest;
import ddf.catalog.operation.CreateResponse;
import ddf.catalog.operation.DeleteRequest;
import ddf.catalog.operation.DeleteResponse;
import ddf.catalog.operation.ProcessingDetails;
import ddf.catalog.operation.QueryRequest;
import ddf.catalog.operation.QueryResponse;
import ddf.catalog.operation.UpdateRequest;
import ddf.catalog.operation.UpdateResponse;
import ddf.catalog.operation.impl.CreateRequestImpl;
import ddf.catalog.operation.impl.DeleteRequestImpl;
import ddf.catalog.operation.impl.QueryImpl;
import ddf.catalog.operation.impl.QueryRequestImpl;
import ddf.catalog.operation.impl.UpdateRequestImpl;
import ddf.catalog.transform.CatalogTransformerException;
import ddf.catalog.transform.InputTransformer;
import ddf.security.service.SecurityServiceException;
import oasis.names.tc.ebxml_regrep.xsd.rim._3.RegistryPackageType;
@SuppressWarnings("PackageAccessibility")
public class FederationAdminServiceImpl implements FederationAdminService {
private static final Logger LOGGER = LoggerFactory.getLogger(FederationAdminServiceImpl.class);
private static final int PAGE_SIZE = 1000;
private final Security security;
private CatalogFramework catalogFramework;
private InputTransformer registryTransformer;
private MetacardMarshaller metacardMarshaller;
private FilterBuilder filterBuilder;
private SlotTypeHelper slotTypeHelper = new SlotTypeHelper();
private InternationalStringTypeHelper internationalStringTypeHelper =
new InternationalStringTypeHelper();
public FederationAdminServiceImpl() {
this(Security.getInstance());
}
FederationAdminServiceImpl(Security security) {
this(null, security);
}
FederationAdminServiceImpl(CatalogFramework catalogFramework) {
this(catalogFramework, Security.getInstance());
}
FederationAdminServiceImpl(CatalogFramework catalogFramework, Security security) {
if (catalogFramework != null) {
this.catalogFramework = catalogFramework;
}
this.security = security;
}
@Override
public String addRegistryEntry(String xml) throws FederationAdminException {
Metacard metacard = getRegistryMetacardFromString(xml);
return addRegistryEntry(metacard);
}
@Override
public String addRegistryEntry(String xml, Set<String> destinations)
throws FederationAdminException {
Metacard metacard = getRegistryMetacardFromString(xml);
return addRegistryEntry(metacard, destinations);
}
@Override
public String addRegistryEntry(Metacard metacard) throws FederationAdminException {
return addRegistryEntry(metacard, null);
}
@Override
public List<String> addRegistryEntries(List<Metacard> metacards, Set<String> destinations)
throws FederationAdminException {
validateRegistryMetacards(metacards);
List<String> registryIds;
Map<String, Serializable> properties = new HashMap<>();
CreateRequest createRequest = new CreateRequestImpl(metacards, properties, destinations);
try {
CreateResponse createResponse =
security.runWithSubjectOrElevate(() -> catalogFramework.create(createRequest));
//loop through to get id's
if (!createResponse.getProcessingErrors()
.isEmpty()) {
throw new FederationAdminException(
"Processing error occurred while creating registry entry. Details:"
+ System.lineSeparator()
+ stringifyProcessingErrors(createResponse.getProcessingErrors()));
}
registryIds = createResponse.getCreatedMetacards()
.stream()
.filter(RegistryUtility::isRegistryMetacard)
.map(RegistryUtility::getRegistryId)
.collect(Collectors.toList());
} catch (SecurityServiceException | InvocationTargetException e) {
throw new FederationAdminException("Error adding local registry entry.", e);
}
return registryIds;
}
@Override
public String addRegistryEntry(Metacard metacard, Set<String> destinations)
throws FederationAdminException {
List<String> registryIds = addRegistryEntries(Collections.singletonList(metacard),
destinations);
if (CollectionUtils.isNotEmpty(registryIds)) {
return registryIds.get(0);
}
return null;
}
@Override
public void updateRegistryEntry(String xml) throws FederationAdminException {
Metacard updateMetacard = getRegistryMetacardFromString(xml);
updateRegistryEntry(updateMetacard);
}
@Override
public void updateRegistryEntry(String xml, Set<String> destinations)
throws FederationAdminException {
Metacard updateMetacard = getRegistryMetacardFromString(xml);
updateRegistryEntry(updateMetacard, destinations);
}
@Override
public void updateRegistryEntry(Metacard updateMetacard) throws FederationAdminException {
updateRegistryEntry(updateMetacard, null);
}
@Override
public void updateRegistryEntry(Metacard updateMetacard, Set<String> destinations)
throws FederationAdminException {
validateRegistryMetacards(Collections.singletonList(updateMetacard));
Map<String, Serializable> properties = new HashMap<>();
String mcardId = updateMetacard.getId();
if (isRemoteMetacard(updateMetacard) || CollectionUtils.isNotEmpty(destinations)) {
Filter idFilter = filterBuilder.attribute(RegistryObjectMetacardType.REMOTE_METACARD_ID)
.is()
.equalTo()
.text(updateMetacard.getId());
Filter tagFilter = filterBuilder.attribute(Metacard.TAGS)
.is()
.like()
.text(RegistryConstants.REGISTRY_TAG_INTERNAL);
List<Metacard> results =
this.getRegistryMetacardsByFilter(filterBuilder.allOf(tagFilter, idFilter),
destinations);
if (results.size() != 1) {
throw new FederationAdminException("Could not find metacard to update.");
}
mcardId = results.get(0)
.getId();
LOGGER.debug("Looked up remote-mcard-id {} and got id {}",
updateMetacard.getId(),
mcardId);
}
List<Map.Entry<Serializable, Metacard>> updateList = new ArrayList<>();
updateList.add(new AbstractMap.SimpleEntry<>(mcardId, updateMetacard));
UpdateRequest updateRequest = new UpdateRequestImpl(updateList,
Metacard.ID,
properties,
destinations);
try {
UpdateResponse updateResponse =
security.runWithSubjectOrElevate(() -> catalogFramework.update(updateRequest));
if (!updateResponse.getProcessingErrors()
.isEmpty()) {
throw new FederationAdminException(
"Processing error occurred while updating registry entry. Details:"
+ System.lineSeparator()
+ stringifyProcessingErrors(updateResponse.getProcessingErrors()));
}
} catch (SecurityServiceException | InvocationTargetException e) {
String message = "Error updating registry entry.";
LOGGER.debug("{} Metacard ID: {}", message, updateMetacard.getId());
throw new FederationAdminException(message, e);
}
}
@Override
public void deleteRegistryEntriesByRegistryIds(List<String> registryIds)
throws FederationAdminException {
deleteRegistryEntriesByRegistryIds(registryIds, null);
}
@Override
public void deleteRegistryEntriesByRegistryIds(List<String> registryIds,
Set<String> destinations) throws FederationAdminException {
if (CollectionUtils.isEmpty(registryIds)) {
throw new FederationAdminException(
"An empty list of registry ids to be deleted was received. Nothing to delete.");
}
List<Serializable> serializableIds = new ArrayList<>(registryIds);
Map<String, Serializable> properties = new HashMap<>();
String deleteField = RegistryObjectMetacardType.REGISTRY_ID;
if (CollectionUtils.isNotEmpty(destinations)) {
deleteField = Metacard.ID;
try {
List<Metacard> localMetacards =
security.runWithSubjectOrElevate(() -> this.getRegistryMetacardsByRegistryIds(
registryIds));
List<Filter> idFilters = localMetacards.stream()
.map(e -> filterBuilder.attribute(RegistryObjectMetacardType.REMOTE_METACARD_ID)
.is()
.equalTo()
.text(e.getId()))
.collect(Collectors.toList());
Filter baseFilter =
filterBuilder.allOf(getBasicFilter(RegistryConstants.REGISTRY_TAG_INTERNAL));
List<Metacard> toDelete =
security.runWithSubjectOrElevate(() -> this.getRegistryMetacardsByFilter(
filterBuilder.allOf(baseFilter, filterBuilder.anyOf(idFilters)),
destinations));
serializableIds = toDelete.stream()
.map(e -> e.getId())
.collect(Collectors.toList());
} catch (SecurityServiceException | InvocationTargetException e) {
throw new FederationAdminException("Error looking up metacards to delete.", e);
}
}
DeleteRequest deleteRequest = new DeleteRequestImpl(serializableIds,
deleteField,
properties,
destinations);
try {
DeleteResponse deleteResponse =
security.runWithSubjectOrElevate(() -> catalogFramework.delete(deleteRequest));
if (!deleteResponse.getProcessingErrors()
.isEmpty()) {
throw new FederationAdminException(
"Processing error occurred while deleting registry entry. Details:"
+ System.lineSeparator()
+ stringifyProcessingErrors(deleteResponse.getProcessingErrors()));
}
} catch (SecurityServiceException | InvocationTargetException e) {
String message = "Error deleting registry entries by registry id.";
LOGGER.debug("{} Registry Ids provided: {}", message, registryIds);
throw new FederationAdminException(message, e);
}
}
@Override
public void deleteRegistryEntriesByMetacardIds(List<String> metacardIds)
throws FederationAdminException {
deleteRegistryEntriesByMetacardIds(metacardIds, null);
}
@Override
public void deleteRegistryEntriesByMetacardIds(List<String> metacardIds,
Set<String> destinations) throws FederationAdminException {
if (CollectionUtils.isEmpty(metacardIds)) {
throw new FederationAdminException(
"An empty list of metacard ids to be deleted was received. Nothing to delete.");
}
List<Serializable> serializableIds = new ArrayList<>(metacardIds);
Map<String, Serializable> properties = new HashMap<>();
DeleteRequest deleteRequest = new DeleteRequestImpl(serializableIds,
Metacard.ID,
properties,
destinations);
try {
DeleteResponse deleteResponse =
security.runWithSubjectOrElevate(() -> catalogFramework.delete(deleteRequest));
if (!deleteResponse.getProcessingErrors()
.isEmpty()) {
throw new FederationAdminException(
"Processing error occurred while deleting registry entry. Details"
+ System.lineSeparator()
+ stringifyProcessingErrors(deleteResponse.getProcessingErrors()));
}
} catch (SecurityServiceException | InvocationTargetException e) {
String message = "Error deleting registry entries by metacard ids.";
LOGGER.debug("{} Metacard Ids provided: {}", message, metacardIds);
throw new FederationAdminException(message, e);
}
}
@Override
public List<Metacard> getRegistryMetacards() throws FederationAdminException {
Filter filter = filterBuilder.allOf(getBasicFilter());
return getRegistryMetacardsByFilter(filter);
}
@Override
public List<Metacard> getInternalRegistryMetacards() throws FederationAdminException {
return getRegistryMetacardsByFilter(filterBuilder.allOf(getBasicFilter(RegistryConstants.REGISTRY_TAG_INTERNAL)));
}
@Override
public List<Metacard> getInternalRegistryMetacardsByRegistryId(String registryId)
throws FederationAdminException {
List<Filter> filters = getBasicFilter(RegistryConstants.REGISTRY_TAG_INTERNAL);
filters.add(filterBuilder.attribute(RegistryObjectMetacardType.REGISTRY_ID)
.is()
.equalTo()
.text(registryId));
return getRegistryMetacardsByFilter(filterBuilder.allOf(filters));
}
@Override
public List<Metacard> getRegistryMetacards(Set<String> destinations)
throws FederationAdminException {
Filter filter = filterBuilder.allOf(getBasicFilter());
return getRegistryMetacardsByFilter(filter, destinations);
}
@Override
public List<Metacard> getLocalRegistryMetacards() throws FederationAdminException {
List<Filter> filters = getBasicFilter();
filters.add(filterBuilder.attribute(RegistryObjectMetacardType.REGISTRY_LOCAL_NODE)
.is()
.bool(true));
Filter filter = filterBuilder.allOf(filters);
return getRegistryMetacardsByFilter(filter);
}
@Override
public List<Metacard> getLocalRegistryMetacardsByRegistryIds(List<String> ids)
throws FederationAdminException {
if (CollectionUtils.isEmpty(ids)) {
throw new FederationAdminException(
"Error getting local registry metacards by registry ids. Null list of Ids provided.");
}
List<Filter> idFilters = ids.stream()
.map(id -> filterBuilder.attribute(RegistryObjectMetacardType.REGISTRY_ID)
.is()
.equalTo()
.text(id))
.collect(Collectors.toList());
List<Filter> filters = getBasicFilter();
filters.add(filterBuilder.attribute(RegistryObjectMetacardType.REGISTRY_LOCAL_NODE)
.is()
.bool(true));
Filter filter = filterBuilder.allOf(filters);
filter = filterBuilder.allOf(filter, filterBuilder.anyOf(idFilters));
return getRegistryMetacardsByFilter(filter);
}
@Override
public List<Metacard> getRegistryMetacardsByRegistryIds(List<String> ids)
throws FederationAdminException {
return getRegistryMetacardsByRegistryIds(ids, false);
}
@Override
public List<Metacard> getRegistryMetacardsByRegistryIds(List<String> ids,
boolean includeInternal) throws FederationAdminException {
if (CollectionUtils.isEmpty(ids)) {
throw new FederationAdminException(
"Error getting registry metacards by registry ids. Null list of Ids provided.");
}
List<Filter> idFilters = ids.stream()
.map(id -> filterBuilder.attribute(RegistryObjectMetacardType.REGISTRY_ID)
.is()
.equalTo()
.text(id))
.collect(Collectors.toList());
List<Filter> filters = getBasicFilter();
Filter filter = filterBuilder.allOf(filters);
if (includeInternal) {
filter = filterBuilder.anyOf(filter,
filterBuilder.allOf(getBasicFilter(RegistryConstants.REGISTRY_TAG_INTERNAL)));
}
filter = filterBuilder.allOf(filter, filterBuilder.anyOf(idFilters));
return getRegistryMetacardsByFilter(filter);
}
@Override
public List<RegistryPackageType> getLocalRegistryObjects() throws FederationAdminException {
List<RegistryPackageType> registryEntries = new ArrayList<>();
for (Metacard metacard : getLocalRegistryMetacards()) {
registryEntries.add(getRegistryPackageFromMetacard(metacard));
}
return registryEntries;
}
@Override
public List<RegistryPackageType> getRegistryObjects() throws FederationAdminException {
List<RegistryPackageType> registryEntries = new ArrayList<>();
for (Metacard metacard : getRegistryMetacards()) {
registryEntries.add(getRegistryPackageFromMetacard(metacard));
}
return registryEntries;
}
@Override
public RegistryPackageType getRegistryObjectByRegistryId(String registryId)
throws FederationAdminException {
return getRegistryObjectByRegistryId(registryId, null);
}
@Override
public RegistryPackageType getRegistryObjectByRegistryId(String registryId,
Set<String> sourceIds) throws FederationAdminException {
if (StringUtils.isBlank(registryId)) {
throw new FederationAdminException(
"Error getting registry object by metacard id. Empty id provided.");
}
List<Filter> filters = getBasicFilter();
filters.add(filterBuilder.attribute(RegistryObjectMetacardType.REGISTRY_ID)
.is()
.equalTo()
.text(registryId));
Filter filter = filterBuilder.allOf(filters);
List<Metacard> metacards = getRegistryMetacardsByFilter(filter, sourceIds);
if (CollectionUtils.isEmpty(metacards)) {
String message = "Error getting registry object by metacard id. No result returned.";
LOGGER.debug("{} For metacard ID: {}, optional sources: {}",
message,
registryId,
sourceIds);
throw new FederationAdminException(message);
}
if (metacards.size() > 1) {
String message =
"Error getting registry object by metacard id. More than one metacards were returned.";
LOGGER.debug("{} For metacard ID: {}, optional sources: {}",
message,
registryId,
sourceIds);
throw new FederationAdminException(message);
}
return getRegistryPackageFromMetacard(metacards.get(0));
}
@Override
public Optional<Metacard> getLocalRegistryIdentityMetacard() throws FederationAdminException {
Optional<Metacard> metacardOptional = Optional.empty();
List<Filter> filters = getBasicFilter();
filters.add(filterBuilder.attribute(RegistryObjectMetacardType.REGISTRY_IDENTITY_NODE)
.is()
.bool(true));
Filter filter = filterBuilder.allOf(filters);
List<Metacard> identityMetacards = getRegistryMetacardsByFilter(filter);
if (CollectionUtils.isNotEmpty(identityMetacards)) {
if (identityMetacards.size() > 1) {
String message =
"Error getting registry identity metacard. More than one result found.";
LOGGER.debug("{} Found these: {}", message, identityMetacards);
throw new FederationAdminException(message);
}
metacardOptional = Optional.of(identityMetacards.get(0));
}
return metacardOptional;
}
private RegistryPackageType getRegistryPackageFromMetacard(Metacard metacard)
throws FederationAdminException {
try {
return metacardMarshaller.getRegistryPackageFromMetacard(metacard);
} catch (ParserException e) {
throw new FederationAdminException("Error parsing ebrim xml from metacard", e);
}
}
private void validateRegistryMetacards(List<Metacard> metacards)
throws FederationAdminException {
if (metacards == null) {
throw new FederationAdminException(
"Error creating local registry entry. Null metacard provided.");
}
for (Metacard metacard : metacards) {
if (metacard == null) {
throw new FederationAdminException("ValidationError: Metacard was null.");
}
if (!RegistryUtility.isRegistryMetacard(metacard)
&& !RegistryUtility.isInternalRegistryMetacard(metacard)) {
throw new FederationAdminException(
"ValidationError: Metacard does not have a registry tag and/or registry id.");
}
}
}
private List<Metacard> getRegistryMetacardsByFilter(Filter filter)
throws FederationAdminException {
return getRegistryMetacardsByFilter(filter, null);
}
private List<Metacard> getRegistryMetacardsByFilter(Filter filter, Set<String> sourceIds)
throws FederationAdminException {
if (filter == null) {
throw new FederationAdminException(
"Error getting registry metacards. Null filter provided.");
}
PropertyName propertyName = new PropertyNameImpl(Metacard.MODIFIED);
SortBy sortBy = new SortByImpl(propertyName, SortOrder.ASCENDING);
QueryImpl query = new QueryImpl(filter);
query.setSortBy(sortBy);
query.setPageSize(PAGE_SIZE);
QueryRequest queryRequest = new QueryRequestImpl(query, sourceIds);
try {
QueryResponse queryResponse =
security.runWithSubjectOrElevate(() -> catalogFramework.query(queryRequest));
return queryResponse.getResults()
.stream()
.map(Result::getMetacard)
.filter(Objects::nonNull)
.collect(Collectors.toList());
} catch (SecurityServiceException | InvocationTargetException e) {
String message = "Error querying for registry metacards.";
LOGGER.debug("{} For Filter: {}", message, filter);
throw new FederationAdminException(message, e);
}
}
private Metacard getRegistryMetacardFromString(String xml) throws FederationAdminException {
if (StringUtils.isBlank(xml)) {
throw new FederationAdminException(
"Error unmarshalling string to Metacard. Empty string was provided.");
}
Metacard metacard;
try {
metacard = registryTransformer.transform(IOUtils.toInputStream(xml));
} catch (IOException | CatalogTransformerException e) {
String message = "Error transforming xml string to metacard.";
LOGGER.debug("{}. XML: {}", message, xml);
throw new FederationAdminException(message);
}
return metacard;
}
BundleContext getBundleContext() {
Bundle bundle = FrameworkUtil.getBundle(this.getClass());
if (bundle != null) {
return bundle.getBundleContext();
}
return null;
}
private String stringifyProcessingErrors(Set<ProcessingDetails> details){
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
for(ProcessingDetails detail : details){
pw.append(detail.getSourceId());
pw.append(":");
if(detail.hasException()) {
detail.getException()
.printStackTrace(pw);
}
pw.append(System.lineSeparator());
}
return pw.toString();
}
private List<Filter> getBasicFilter() {
return getBasicFilter(RegistryConstants.REGISTRY_TAG);
}
private List<Filter> getBasicFilter(String tag) {
List<Filter> filters = new ArrayList<>();
filters.add(filterBuilder.attribute(Metacard.TAGS)
.is()
.equalTo()
.text(tag));
return filters;
}
private boolean isRemoteMetacard(Metacard metacard) {
return RegistryUtility.hasAttribute(metacard,
RegistryObjectMetacardType.REMOTE_REGISTRY_ID);
}
public void setCatalogFramework(CatalogFramework catalogFramework) {
this.catalogFramework = catalogFramework;
}
public void setFilterBuilder(FilterBuilder filterBuilder) {
this.filterBuilder = filterBuilder;
}
public void setMetacardMarshaller(MetacardMarshaller helper) {
this.metacardMarshaller = helper;
}
public void setRegistryTransformer(InputTransformer inputTransformer) {
this.registryTransformer = inputTransformer;
}
}