/**
* 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.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.function.Consumer;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
import org.codice.ddf.cxf.SecureCxfClientFactory;
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.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;
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.SourceMonitor;
import ddf.catalog.source.UnsupportedQueryException;
import ddf.security.SecurityConstants;
import ddf.security.encryption.EncryptionService;
import oasis.names.tc.ebxml_regrep.xsd.rim._3.ExternalIdentifierType;
import oasis.names.tc.ebxml_regrep.xsd.rim._3.RegistryPackageType;
public class RegistryStoreImpl extends AbstractCswStore implements RegistryStore {
private static final Logger LOGGER = LoggerFactory.getLogger(RegistryStoreImpl.class);
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;
public RegistryStoreImpl(BundleContext context, CswSourceConfiguration cswSourceConfiguration,
Converter provider, SecureCxfClientFactory factory,
EncryptionService encryptionService) {
super(context, cswSourceConfiguration, provider, factory, encryptionService);
}
public RegistryStoreImpl(EncryptionService encryptionService) {
super(encryptionService);
}
@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));
List<Metacard> metacardsToCreate = request.getMetacards()
.stream()
.filter(e -> !responseMap.containsKey(RegistryUtility.getRegistryId(e)))
.collect(Collectors.toList());
List<Metacard> allMetacards = new ArrayList<>(responseMap.values());
if (CollectionUtils.isNotEmpty(metacardsToCreate)) {
CreateResponse createResponse =
super.create(new CreateRequestImpl(metacardsToCreate,
request.getProperties()));
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;
}
public void init() {
SourceMonitor registrySourceMonitor = new SourceMonitor() {
@Override
public void setAvailable() {
try {
registryInfoQuery();
} catch (UnsupportedQueryException e) {
LOGGER.debug("Unable to query registry configurations, ", e);
}
}
@Override
public void setUnavailable() {
}
};
addSourceMonitor(registrySourceMonitor);
super.init();
}
void 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());
updateConfiguration(metacardTitle);
}
}
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 void updateConfiguration(String metacardTitle) {
if (metacardTitle == null) {
LOGGER.debug("Unable to update registry configurations. No metacard title.");
return;
}
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);
}
}
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;
}
}