repFields = replicationUtils.getReplications(true);
String idToDelete = id.getValue();
for (ReplicationField repField : repFields.getList()) {
if (idToDelete.equals(repField.source().id())
diff --git a/admin/query/src/test/java/org/codice/ditto/replication/admin/query/ReplicationUtilsTest.java b/admin/query/src/test/java/org/codice/ditto/replication/admin/query/ReplicationUtilsTest.java
index 8a16f953e..4459c7ed3 100644
--- a/admin/query/src/test/java/org/codice/ditto/replication/admin/query/ReplicationUtilsTest.java
+++ b/admin/query/src/test/java/org/codice/ditto/replication/admin/query/ReplicationUtilsTest.java
@@ -33,6 +33,7 @@
import org.codice.ditto.replication.admin.query.replications.fields.ReplicationField;
import org.codice.ditto.replication.admin.query.sites.fields.ReplicationSiteField;
import org.codice.ditto.replication.api.ReplicationException;
+import org.codice.ditto.replication.api.ReplicationPersistenceException;
import org.codice.ditto.replication.api.ReplicationStatus;
import org.codice.ditto.replication.api.Replicator;
import org.codice.ditto.replication.api.ReplicatorHistory;
@@ -54,7 +55,7 @@
@RunWith(MockitoJUnitRunner.class)
public class ReplicationUtilsTest {
- ReplicationUtils utils;
+ private ReplicationUtils utils;
@Mock SiteManager siteManager;
@@ -292,18 +293,31 @@ public void updateReplicationActiveSyncRequest() {
}
@Test
- public void deleteConfig() {
+ public void testMarkConfigDeleted() {
ReplicatorConfigImpl config = new ReplicatorConfigImpl();
config.setId("id");
config.setName("name");
- assertThat(utils.deleteConfig("id"), is(true));
- verify(configManager).remove(anyString());
+
+ when(configManager.get("id")).thenReturn(config);
+
+ assertThat(utils.markConfigDeleted("id", true), is(true));
+ assertThat(config.shouldDeleteData(), is(true));
+ assertThat(config.isDeleted(), is(true));
+ assertThat(config.isSuspended(), is(true));
+ verify(replicator).cancelSyncRequest("id");
+ verify(configManager).save(config);
+ }
+
+ @Test
+ public void testMarkConfigDeletedNotFound() {
+ doThrow(new NotFoundException()).when(configManager).get("id");
+ assertThat(utils.markConfigDeleted("id", true), is(true));
}
@Test
- public void deleteConfigFailed() {
- doThrow(new NotFoundException()).when(configManager).remove(anyString());
- assertThat(utils.deleteConfig("id"), is(false));
+ public void testMarkConfigDeletedErrorRetrievingConfig() {
+ doThrow(new ReplicationPersistenceException("")).when(configManager).get("id");
+ assertThat(utils.markConfigDeleted("id", true), is(false));
}
@Test
@@ -336,7 +350,7 @@ public void getReplications() throws Exception {
config.setDestination("destId");
config.setBidirectional(false);
when(configManager.objects()).thenReturn(Stream.of(config));
- ReplicationField field = utils.getReplications().getList().get(0);
+ ReplicationField field = utils.getReplications(false).getList().get(0);
assertThat(field.name(), is("test"));
assertThat(field.source().id(), is("srcId"));
assertThat(field.destination().id(), is("destId"));
@@ -346,6 +360,15 @@ public void getReplications() throws Exception {
assertThat(field.dataTransferred(), is("15 MB"));
}
+ @Test
+ public void testGetReplicationsFilterDeleted() {
+ ReplicatorConfigImpl config = new ReplicatorConfigImpl();
+ config.setDeleted(true);
+
+ when(configManager.objects()).thenReturn(Stream.of(config));
+ assertThat(utils.getReplications(true).getList().size(), is(0));
+ }
+
@Test
public void cancelConfig() {
assertThat(utils.cancelConfig("test"), is(true));
@@ -393,7 +416,7 @@ public void enableConfig() {
@Test
public void siteIdExists() {
- when(siteManager.get(anyString())).thenReturn(new ReplicationSiteImpl());
+ when(siteManager.exists(anyString())).thenReturn(true);
assertThat(utils.siteIdExists("id"), is(true));
}
diff --git a/admin/query/src/test/java/org/codice/ditto/replication/admin/query/replications/persist/DeleteReplicationTest.java b/admin/query/src/test/java/org/codice/ditto/replication/admin/query/replications/persist/DeleteReplicationTest.java
new file mode 100644
index 000000000..b39a60d91
--- /dev/null
+++ b/admin/query/src/test/java/org/codice/ditto/replication/admin/query/replications/persist/DeleteReplicationTest.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
+package org.codice.ditto.replication.admin.query.replications.persist;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.codice.ddf.admin.api.report.ErrorMessage;
+import org.codice.ddf.admin.api.report.FunctionReport;
+import org.codice.ditto.replication.admin.query.ReplicationMessages;
+import org.codice.ditto.replication.admin.query.ReplicationUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DeleteReplicationTest {
+
+ private static final String ID = "abc123";
+
+ private DeleteReplication deleteReplication;
+
+ private Map args;
+
+ @Mock ReplicationUtils utils;
+
+ @Before
+ public void setup() {
+ deleteReplication = new DeleteReplication(utils);
+ args = new HashMap<>();
+ args.put("id", ID);
+ }
+
+ @Test
+ public void testValidateNoConfig() {
+ when(utils.configExists(ID)).thenReturn(false);
+ FunctionReport report =
+ deleteReplication.execute(args, Collections.singletonList(DeleteReplication.FIELD_NAME));
+ assertThat(report.getErrorMessages().size(), is(1));
+ assertThat(
+ ((ErrorMessage) report.getErrorMessages().get(0)).getCode(),
+ is(ReplicationMessages.CONFIG_DOES_NOT_EXIST));
+ }
+
+ @Test
+ public void testGetFunctionErrorCodes() {
+ assertThat(
+ deleteReplication
+ .getFunctionErrorCodes()
+ .contains(ReplicationMessages.CONFIG_DOES_NOT_EXIST),
+ is(true));
+ }
+
+ @Test
+ public void testDeleteDataArgDefaultsToFalse() {
+ when(utils.configExists(ID)).thenReturn(true);
+ FunctionReport report =
+ deleteReplication.execute(args, Collections.singletonList(DeleteReplication.FIELD_NAME));
+ assertThat(report.getErrorMessages().size(), is(0));
+ verify(utils, times(1)).markConfigDeleted(ID, false);
+ }
+}
diff --git a/dependency-check-maven-config.xml b/dependency-check-maven-config.xml
index 8d1088a96..eaaa09c86 100644
--- a/dependency-check-maven-config.xml
+++ b/dependency-check-maven-config.xml
@@ -141,6 +141,12 @@
CVE-2018-11788
+
+
+ Not using the hadoop-auth feature.
+
+ CVE-2018-11766
+
diff --git a/replication-api-impl/pom.xml b/replication-api-impl/pom.xml
index cfc34b4d8..272d7a5b6 100644
--- a/replication-api-impl/pom.xml
+++ b/replication-api-impl/pom.xml
@@ -331,17 +331,17 @@
INSTRUCTION
COVEREDRATIO
- 0.54
+ 0.57
BRANCH
COVEREDRATIO
- 0.44
+ 0.49
COMPLEXITY
COVEREDRATIO
- 0.47
+ 0.52
diff --git a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/MetacardHelper.java b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/Metacards.java
similarity index 61%
rename from replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/MetacardHelper.java
rename to replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/Metacards.java
index 80992bb21..f3bc1ac32 100644
--- a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/MetacardHelper.java
+++ b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/Metacards.java
@@ -20,18 +20,21 @@
import ddf.catalog.data.Result;
import ddf.catalog.data.impl.AttributeImpl;
import ddf.catalog.data.types.Core;
-import ddf.catalog.federation.FederationException;
import ddf.catalog.filter.FilterBuilder;
import ddf.catalog.filter.impl.SortByImpl;
import ddf.catalog.operation.QueryRequest;
+import ddf.catalog.operation.impl.DeleteRequestImpl;
import ddf.catalog.operation.impl.QueryImpl;
import ddf.catalog.operation.impl.QueryRequestImpl;
+import ddf.catalog.source.IngestException;
import ddf.catalog.source.SourceUnavailableException;
-import ddf.catalog.source.UnsupportedQueryException;
import ddf.catalog.util.impl.ResultIterable;
import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.opengis.filter.Filter;
@@ -39,14 +42,18 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class MetacardHelper {
- private static final Logger LOGGER = LoggerFactory.getLogger(MetacardHelper.class);
+/** Provides common operations when dealing with {@link Metacard}s. */
+public class Metacards {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(Metacards.class);
+
+ private static final int DEFAULT_BATCH_SIZE = 250;
private final CatalogFramework framework;
private final FilterBuilder filterBuilder;
- public MetacardHelper(CatalogFramework framework, FilterBuilder filterBuilder) {
+ public Metacards(CatalogFramework framework, FilterBuilder filterBuilder) {
this.framework = framework;
this.filterBuilder = filterBuilder;
}
@@ -78,27 +85,6 @@ public T getAttributeValueOrDefault(Metacard mcard, String attribute, T defa
return defaultValue;
}
- public Metacard getMetacardById(String id) {
- if (id == null) {
- return null;
- }
- QueryRequest request =
- new QueryRequestImpl(
- new QueryImpl(
- filterBuilder.allOf(
- filterBuilder.attribute(Core.ID).is().equalTo().text(id),
- filterBuilder.attribute(Metacard.TAGS).is().like().text("*"))));
- try {
- List results = framework.query(request).getResults();
- if (results.size() == 1) {
- return results.get(0).getMetacard();
- }
- } catch (UnsupportedQueryException | SourceUnavailableException | FederationException e) {
- LOGGER.warn("Unable to retrieve replication metacard for {}", id, e);
- }
- return null;
- }
-
public List getTypeForFilter(Filter filter, Function function) {
QueryRequest request =
new QueryRequestImpl(
@@ -118,4 +104,64 @@ public List getTypeForFilter(Filter filter, Function functio
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
+
+ public Set getIdsOfMetacardsInCatalog(Set ids) {
+ List filters = new ArrayList<>();
+
+ for (String idString : ids) {
+ filters.add(filterBuilder.attribute(Core.ID).is().equalTo().text(idString));
+ }
+ Filter filter = filterBuilder.anyOf(filters);
+
+ QueryRequest request =
+ new QueryRequestImpl(
+ new QueryImpl(
+ filter, 1, ids.size(), new SortByImpl(Core.ID, SortOrder.ASCENDING), false, 0L));
+
+ ResultIterable results = ResultIterable.resultIterable(framework::query, request);
+ return results
+ .stream()
+ .map(Result::getMetacard)
+ .map(Metacard::getId)
+ .collect(Collectors.toSet());
+ }
+
+ public void doDelete(String[] idsToDelete) throws SourceUnavailableException {
+ doDelete(idsToDelete, DEFAULT_BATCH_SIZE);
+ }
+
+ public void doDelete(String[] idsToDelete, int batchSize) throws SourceUnavailableException {
+ if (idsToDelete.length > 0) {
+ int start = 0;
+ int end;
+
+ while (start < idsToDelete.length) {
+ end = start + batchSize;
+ if (end > idsToDelete.length) {
+ end = idsToDelete.length;
+ }
+ deleteBatch(Arrays.copyOfRange(idsToDelete, start, end));
+ start += batchSize;
+ }
+ }
+ }
+
+ private void deleteBatch(String[] idsToDelete) throws SourceUnavailableException {
+ try {
+ framework.delete(new DeleteRequestImpl(idsToDelete));
+ } catch (IngestException ie) {
+ // nothing in batch was deleted, so...
+ deleteSequentially(idsToDelete);
+ }
+ }
+
+ private void deleteSequentially(String[] ids) throws SourceUnavailableException {
+ for (String id : ids) {
+ try {
+ framework.delete(new DeleteRequestImpl(id));
+ } catch (IngestException e) {
+ LOGGER.debug("Failed to delete metacard with id: {}", id, e);
+ }
+ }
+ }
}
diff --git a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicationPersistentStoreImpl.java b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicationItemManagerImpl.java
similarity index 86%
rename from replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicationPersistentStoreImpl.java
rename to replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicationItemManagerImpl.java
index f6e9b3a86..5be3ec611 100644
--- a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicationPersistentStoreImpl.java
+++ b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicationItemManagerImpl.java
@@ -19,18 +19,19 @@
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
import org.codice.ddf.persistence.PersistenceException;
import org.codice.ddf.persistence.PersistentItem;
import org.codice.ddf.persistence.PersistentStore;
import org.codice.ditto.replication.api.ReplicationItem;
-import org.codice.ditto.replication.api.ReplicationPersistentStore;
+import org.codice.ditto.replication.api.ReplicationItemManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class ReplicationPersistentStoreImpl implements ReplicationPersistentStore {
+public class ReplicationItemManagerImpl implements ReplicationItemManager {
- private static final Logger LOGGER =
- LoggerFactory.getLogger(ReplicationPersistentStoreImpl.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(ReplicationItemManagerImpl.class);
private static final String ID_KEY = "id";
@@ -54,15 +55,16 @@ public class ReplicationPersistentStoreImpl implements ReplicationPersistentStor
private final PersistentStore persistentStore;
- public ReplicationPersistentStoreImpl(PersistentStore persistentStore) {
+ public ReplicationItemManagerImpl(PersistentStore persistentStore) {
this.persistentStore = persistentStore;
}
@Override
- public Optional getItem(String id, String source, String destination) {
+ public Optional getItem(String metacardId, String source, String destination) {
String cqlFilter =
String.format(
- "'id' = '%s' AND 'source' = '%s' AND 'destination' = '%s'", id, source, destination);
+ "'id' = '%s' AND 'source' = '%s' AND 'destination' = '%s'",
+ metacardId, source, destination);
List> matchingPersistentItems;
try {
@@ -70,23 +72,23 @@ public Optional getItem(String id, String source, String destin
} catch (PersistenceException e) {
LOGGER.debug(
"failed to retrieve item with id: {}, source: {}, and destination: {}",
- id,
+ metacardId,
source,
destination);
return Optional.empty();
}
- if (matchingPersistentItems == null || matchingPersistentItems.isEmpty()) {
+ if (CollectionUtils.isEmpty(matchingPersistentItems)) {
LOGGER.debug(
"couldn't find persisted item with id: {}, source: {}, and destination: {}. This is expected during initial replication.",
- id,
+ metacardId,
source,
destination);
return Optional.empty();
} else if (matchingPersistentItems.size() > 1) {
throw new IllegalStateException(
"Found multiple persistent items with id: "
- + id
+ + metacardId
+ ", source: "
+ source
+ ", and destination: "
@@ -102,7 +104,7 @@ public void deleteAllItems() throws PersistenceException {
// translation currently does not work.
String cql = "";
int index = DEFAULT_START_INDEX;
- long itemsDeleted = 0;
+ long itemsDeleted;
do {
itemsDeleted = persistentStore.delete(PERSISTENCE_TYPE, cql, index, DEFAULT_PAGE_SIZE);
index += DEFAULT_PAGE_SIZE;
@@ -112,7 +114,12 @@ public void deleteAllItems() throws PersistenceException {
@Override
public List getItemsForConfig(String configId, int startIndex, int pageSize)
throws PersistenceException {
- String cql = String.format("'config-id' = '%s'", configId);
+ String cql;
+ if (StringUtils.isNotEmpty(configId)) {
+ cql = String.format("'%s' = '%s'", CONFIGURATION_ID_KEY, configId);
+ } else {
+ cql = "";
+ }
List> matchingPersistentItems;
matchingPersistentItems = persistentStore.get(PERSISTENCE_TYPE, cql, startIndex, pageSize);
@@ -133,16 +140,17 @@ public void saveItem(ReplicationItem replicationItem) {
}
@Override
- public void deleteItem(String id, String source, String destination) {
+ public void deleteItem(String metacardId, String source, String destination) {
String cqlFilter =
String.format(
- "'id' = '%s' AND 'source' = '%s' AND 'destination' = '%s'", id, source, destination);
+ "'id' = '%s' AND 'source' = '%s' AND 'destination' = '%s'",
+ metacardId, source, destination);
try {
persistentStore.delete(PERSISTENCE_TYPE, cqlFilter);
} catch (PersistenceException e) {
LOGGER.error(
"error deleting persisted item with id: {}, source: {}, and destination: {}",
- id,
+ metacardId,
source,
destination);
}
@@ -175,7 +183,7 @@ public List getFailureList(int maximumFailureCount, String source, Strin
@Override
public void deleteItemsForConfig(String configId) throws PersistenceException {
- String cql = String.format("'config-id' = '%s'", configId);
+ String cql = String.format("'%s' = '%s'", CONFIGURATION_ID_KEY, configId);
int itemsDeleted;
do {
diff --git a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicatorHistoryImpl.java b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicatorHistoryImpl.java
index c2d06c1b4..cb5f52522 100644
--- a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicatorHistoryImpl.java
+++ b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicatorHistoryImpl.java
@@ -58,7 +58,7 @@ public class ReplicatorHistoryImpl implements ReplicatorHistory {
private final FilterBuilder filterBuilder;
- private final MetacardHelper helper;
+ private final Metacards helper;
private final MetacardType metacardType;
@@ -66,7 +66,7 @@ public ReplicatorHistoryImpl(
CatalogFramework framework,
CatalogProvider provider,
FilterBuilder filterBuilder,
- MetacardHelper helper,
+ Metacards helper,
MetacardType metacardType) {
this(framework, provider, filterBuilder, helper, metacardType, Security.getInstance());
}
@@ -76,7 +76,7 @@ public ReplicatorHistoryImpl(
CatalogFramework framework,
CatalogProvider provider,
FilterBuilder filterBuilder,
- MetacardHelper helper,
+ Metacards helper,
MetacardType metacardType,
Security security) {
this.framework = framework;
@@ -113,7 +113,7 @@ public List getReplicationEvents() {
}
@Override
- public List getReplicationEvents(String replicatorid) {
+ public List getReplicationEvents(String replicationConfigId) {
return helper.getTypeForFilter(
filterBuilder.allOf(
filterBuilder
@@ -121,7 +121,11 @@ public List getReplicationEvents(String replicatorid) {
.is()
.equalTo()
.text(ReplicationHistory.METACARD_TAG),
- filterBuilder.attribute(ReplicationConfig.NAME).is().equalTo().text(replicatorid)),
+ filterBuilder
+ .attribute(ReplicationConfig.NAME)
+ .is()
+ .equalTo()
+ .text(replicationConfigId)),
this::getStatusFromMetacard);
}
@@ -164,7 +168,7 @@ public void removeReplicationEvent(ReplicationStatus replicationStatus) {
@Override
public void removeReplicationEvents(Set ids) {
try {
- provider.delete(new DeleteRequestImpl(ids.toArray(new String[ids.size()])));
+ provider.delete(new DeleteRequestImpl(ids.toArray(new String[0])));
} catch (IngestException e) {
throw new ReplicationPersistenceException(
"Error deleting replication history items " + ids, e);
diff --git a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicatorImpl.java b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicatorImpl.java
index f8e954595..f8d543518 100644
--- a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicatorImpl.java
+++ b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ReplicatorImpl.java
@@ -38,7 +38,7 @@
import org.apache.commons.collections4.queue.UnmodifiableQueue;
import org.codice.ddf.security.common.Security;
import org.codice.ditto.replication.api.ReplicationException;
-import org.codice.ditto.replication.api.ReplicationPersistentStore;
+import org.codice.ditto.replication.api.ReplicationItemManager;
import org.codice.ditto.replication.api.ReplicationStatus;
import org.codice.ditto.replication.api.ReplicationStore;
import org.codice.ditto.replication.api.Replicator;
@@ -60,7 +60,7 @@ public class ReplicatorImpl implements Replicator {
private final ReplicatorHistory history;
- private final ReplicationPersistentStore persistentStore;
+ private final ReplicationItemManager persistentStore;
private final SiteManager siteManager;
@@ -81,7 +81,7 @@ public class ReplicatorImpl implements Replicator {
public ReplicatorImpl(
ReplicatorStoreFactory replicatorStoreFactory,
ReplicatorHistory history,
- ReplicationPersistentStore persistentStore,
+ ReplicationItemManager persistentStore,
SiteManager siteManager,
ExecutorService executor,
FilterBuilder builder) {
@@ -98,7 +98,7 @@ public ReplicatorImpl(
public ReplicatorImpl(
ReplicatorStoreFactory replicatorStoreFactory,
ReplicatorHistory history,
- ReplicationPersistentStore persistentStore,
+ ReplicationItemManager persistentStore,
SiteManager siteManager,
ExecutorService executor,
FilterBuilder builder,
diff --git a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ScheduledReplicatorDeleter.java b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ScheduledReplicatorDeleter.java
new file mode 100644
index 000000000..ffe0de5e0
--- /dev/null
+++ b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/ScheduledReplicatorDeleter.java
@@ -0,0 +1,288 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
+package org.codice.ditto.replication.api.impl;
+
+import com.google.common.annotations.VisibleForTesting;
+import ddf.catalog.source.SourceUnavailableException;
+import ddf.security.service.SecurityServiceException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import org.codice.ddf.configuration.SystemInfo;
+import org.codice.ddf.persistence.PersistenceException;
+import org.codice.ddf.security.common.Security;
+import org.codice.ditto.replication.api.ReplicationItem;
+import org.codice.ditto.replication.api.ReplicationItemManager;
+import org.codice.ditto.replication.api.ReplicationPersistenceException;
+import org.codice.ditto.replication.api.ReplicationStatus;
+import org.codice.ditto.replication.api.ReplicatorHistory;
+import org.codice.ditto.replication.api.data.ReplicatorConfig;
+import org.codice.ditto.replication.api.persistence.ReplicatorConfigManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Periodically polls for available {@link ReplicatorConfig}s and deletes them based on the {@link
+ * ReplicatorConfig#isDeleted()} and {@link ReplicatorConfig#shouldDeleteData()} properties. A
+ * {@link ReplicatorConfig} marked as deleted always has its history deleted.
+ */
+public class ScheduledReplicatorDeleter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledReplicatorDeleter.class);
+
+ private static final int DEFAULT_PAGE_SIZE = 1000;
+
+ private final ReplicatorConfigManager replicatorConfigManager;
+
+ private final ReplicationItemManager replicationItemManager;
+
+ private final ReplicatorHistory replicatorHistory;
+
+ private final Metacards metacards;
+
+ private final ScheduledExecutorService scheduledExecutorService;
+
+ private final Security security;
+
+ private final int pageSize;
+
+ public ScheduledReplicatorDeleter(
+ ReplicatorConfigManager replicatorConfigManager,
+ ScheduledExecutorService scheduledExecutorService,
+ ReplicationItemManager replicationItemManager,
+ ReplicatorHistory replicatorHistory,
+ Metacards metacards) {
+ this(
+ replicatorConfigManager,
+ scheduledExecutorService,
+ replicationItemManager,
+ replicatorHistory,
+ metacards,
+ Security.getInstance(),
+ TimeUnit.MINUTES.toMillis(1),
+ DEFAULT_PAGE_SIZE);
+ }
+
+ @VisibleForTesting
+ @SuppressWarnings("squid:S00107" /* Only for testing */)
+ ScheduledReplicatorDeleter(
+ ReplicatorConfigManager replicatorConfigManager,
+ ScheduledExecutorService scheduledExecutorService,
+ ReplicationItemManager replicationItemManager,
+ ReplicatorHistory replicatorHistory,
+ Metacards metacards,
+ Security security,
+ long pollPeriod,
+ int pageSize) {
+ this.replicatorConfigManager = replicatorConfigManager;
+ this.scheduledExecutorService = scheduledExecutorService;
+ this.replicationItemManager = replicationItemManager;
+ this.replicatorHistory = replicatorHistory;
+ this.metacards = metacards;
+ this.security = security;
+ this.pageSize = pageSize;
+
+ LOGGER.info(
+ "Scheduling replicator config cleanup every {} seconds",
+ TimeUnit.MILLISECONDS.toSeconds(pollPeriod));
+ scheduledExecutorService.scheduleAtFixedRate(
+ this::cleanup, 0, pollPeriod, TimeUnit.MILLISECONDS);
+ }
+
+ public void destroy() {
+ scheduledExecutorService.shutdownNow();
+ }
+
+ void cleanup() {
+ security.runAsAdmin(
+ () -> {
+ try {
+ security.runWithSubjectOrElevate(
+ () -> {
+ List replicatorConfigs =
+ replicatorConfigManager.objects().collect(Collectors.toList());
+
+ cleanupOrphanedReplicationItems(replicatorConfigs);
+ cleanupDeletedConfigs(replicatorConfigs);
+
+ return null;
+ });
+ } catch (SecurityServiceException | InvocationTargetException e) {
+ LOGGER.debug("Failed scheduled cleanup of deleted replicator configs", e);
+ }
+
+ return null;
+ });
+ }
+
+ /**
+ * Deletes {@link ReplicationItem}s which have no corresponding data store entry or {@link
+ * ReplicatorConfig}. This occurs when a {@link ReplicatorConfig} was deleted without cleaning up
+ * data, but the data was then deleted manually afterwards.
+ */
+ private void cleanupOrphanedReplicationItems(List replicatorConfigs) {
+ Set replicatorConfigIds =
+ replicatorConfigs.stream().map(ReplicatorConfig::getId).collect(Collectors.toSet());
+
+ int startIndex = 0;
+ List replicationItems;
+
+ do {
+ try {
+
+ replicationItems = replicationItemManager.getItemsForConfig("", startIndex, pageSize);
+
+ Set configlessItems =
+ replicationItems
+ .stream()
+ .filter(item -> !replicatorConfigIds.contains(item.getConfigurationId()))
+ .collect(Collectors.toSet());
+
+ if (!configlessItems.isEmpty()) {
+ Set itemMetacardIds =
+ configlessItems
+ .stream()
+ .map(ReplicationItem::getMetacardId)
+ .collect(Collectors.toSet());
+
+ Set idsInTheCatalog = metacards.getIdsOfMetacardsInCatalog(itemMetacardIds);
+
+ Set orphanedItems =
+ configlessItems
+ .stream()
+ .filter(item -> itemNotInCatalog(item, idsInTheCatalog))
+ .collect(Collectors.toSet());
+
+ orphanedItems.forEach(
+ item ->
+ replicationItemManager.deleteItem(
+ item.getMetacardId(), item.getSource(), item.getDestination()));
+
+ startIndex += replicationItems.size() - orphanedItems.size();
+ } else {
+ startIndex += replicationItems.size();
+ }
+ } catch (PersistenceException e) {
+ LOGGER.debug(
+ "Failed to delete orphaned replication items. Deletion will be retried next poll interval.",
+ e);
+ return;
+ }
+ } while (!replicationItems.isEmpty());
+ }
+
+ private boolean itemNotInCatalog(ReplicationItem item, Set idsInCatalog) {
+ return !idsInCatalog.contains(item.getMetacardId());
+ }
+
+ private void cleanupDeletedConfigs(List replicatorConfigs) {
+ List deletedConfigs =
+ replicatorConfigs.stream().filter(ReplicatorConfig::isDeleted).collect(Collectors.toList());
+
+ for (ReplicatorConfig config : deletedConfigs) {
+ final String configId = config.getId();
+ final String configName = config.getName();
+
+ if (config.shouldDeleteData()) {
+ try {
+ deleteMetacards(configId);
+ } catch (PersistenceException e) {
+ LOGGER.debug(
+ "Failed to retrieve replication items for config: {}. Deletion will be retried next poll interval.",
+ configName,
+ e);
+ break;
+ } catch (SourceUnavailableException e) {
+ LOGGER.debug(
+ "Failed to delete metacards replicated by config: {}. Deletion will be retried next poll interval.",
+ configName,
+ e);
+ break;
+ }
+
+ try {
+ replicationItemManager.deleteItemsForConfig(configId);
+ } catch (PersistenceException e) {
+ LOGGER.debug(
+ "Removed metacards for replicator {}, but failed to delete replication items. Deletion will be tried next poll interval.",
+ configName);
+ break;
+ }
+ }
+
+ if (deleteHistory(config)) {
+ replicatorConfigManager.remove(configId);
+ }
+ }
+ }
+
+ private boolean deleteHistory(ReplicatorConfig config) {
+ try {
+ deleteReplicatorHistory(config);
+ return true;
+ } catch (ReplicationPersistenceException e) {
+ LOGGER.debug(
+ "History for replicator configuration {} could not be deleted. Deletion will be retried next polling interval.",
+ config.getName(),
+ e);
+ return false;
+ }
+ }
+
+ private void deleteMetacards(String configId)
+ throws PersistenceException, SourceUnavailableException {
+ int startIndex = 0;
+ List replicationItems;
+
+ do {
+ replicationItems = replicationItemManager.getItemsForConfig(configId, startIndex, pageSize);
+
+ Set idsToDelete =
+ replicationItems
+ .stream()
+ .filter(item -> item.getDestination().equals(getSiteName()))
+ .map(ReplicationItem::getMetacardId)
+ .collect(Collectors.toSet());
+
+ if (!idsToDelete.isEmpty()) {
+ Set idsInTheCatalog = metacards.getIdsOfMetacardsInCatalog(idsToDelete);
+ metacards.doDelete(idsInTheCatalog.toArray(new String[0]));
+ startIndex += idsToDelete.size();
+ } else {
+ startIndex += pageSize;
+ }
+
+ } while (!replicationItems.isEmpty());
+ }
+
+ private void deleteReplicatorHistory(ReplicatorConfig config) {
+ Set eventIds =
+ replicatorHistory
+ .getReplicationEvents(config.getName())
+ .stream()
+ .map(ReplicationStatus::getId)
+ .collect(Collectors.toSet());
+
+ replicatorHistory.removeReplicationEvents(eventIds);
+ }
+
+ /** Solely for mocking out a static call. */
+ @VisibleForTesting
+ String getSiteName() {
+ return SystemInfo.getSiteName();
+ }
+}
diff --git a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/SyncHelper.java b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/SyncHelper.java
index 6ca1741bb..f20a7033a 100644
--- a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/SyncHelper.java
+++ b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/SyncHelper.java
@@ -67,7 +67,7 @@
import org.apache.shiro.SecurityUtils;
import org.codice.ditto.replication.api.ReplicationException;
import org.codice.ditto.replication.api.ReplicationItem;
-import org.codice.ditto.replication.api.ReplicationPersistentStore;
+import org.codice.ditto.replication.api.ReplicationItemManager;
import org.codice.ditto.replication.api.ReplicationStatus;
import org.codice.ditto.replication.api.ReplicationStore;
import org.codice.ditto.replication.api.ReplicatorHistory;
@@ -95,7 +95,7 @@ class SyncHelper {
private final ReplicatorConfig config;
- private final ReplicationPersistentStore persistentStore;
+ private final ReplicationItemManager persistentStore;
private final ReplicatorHistory history;
@@ -120,7 +120,7 @@ public SyncHelper(
ReplicationStore destination,
ReplicatorConfig config,
ReplicationStatus status,
- ReplicationPersistentStore persistentStore,
+ ReplicationItemManager persistentStore,
ReplicatorHistory history,
FilterBuilder builder) {
this.source = source;
diff --git a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/data/ReplicatorConfigImpl.java b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/data/ReplicatorConfigImpl.java
index 997fff770..ed0d86238 100644
--- a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/data/ReplicatorConfigImpl.java
+++ b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/data/ReplicatorConfigImpl.java
@@ -41,12 +41,29 @@ public class ReplicatorConfigImpl extends AbstractPersistable implements Replica
private static final String SUSPENDED_KEY = "suspended";
+ private static final String DELETED_KEY = "deleted";
+
+ private static final String DELETE_DATA_KEY = "deleteData";
+
/**
- * 0/No Version - initial version of configs which were saved in the catalog framework. 1 - the
- * first version of configs to be saved in the replication persistent store.
+ * Field specifying the version of the configuration. Possible versions include:
+ *
+ *
+ * 0 (No version) - initial version of configs which were saved in the catalog framework
+ * 1 - The first version of configs to be saved in the replication persistent store
+ *
+ * Add suspended field of type boolean with default of false
+ * Add deleted field of type boolean with default of false
+ * Add deleteData field of type boolean with default of false
+ *
+ *
*/
public static final int CURRENT_VERSION = 1;
+ private boolean deleted = false;
+
+ private boolean deleteData = false;
+
private String name;
private boolean bidirectional;
@@ -78,6 +95,8 @@ public Map toMap() {
result.put(RETRY_COUNT_KEY, getFailureRetryCount());
result.put(DESCRIPTION_KEY, getDescription());
result.put(SUSPENDED_KEY, Boolean.toString(isSuspended()));
+ result.put(DELETED_KEY, Boolean.toString(isDeleted()));
+ result.put(DELETE_DATA_KEY, Boolean.toString(shouldDeleteData()));
return result;
}
@@ -92,6 +111,8 @@ public void fromMap(Map properties) {
setFailureRetryCount((int) properties.get(RETRY_COUNT_KEY));
setDescription((String) properties.get(DESCRIPTION_KEY));
setSuspended(Boolean.valueOf((String) properties.get(SUSPENDED_KEY)));
+ setDeleted(Boolean.valueOf((String) properties.get(DELETED_KEY)));
+ setDeleteData(Boolean.valueOf((String) properties.get(DELETE_DATA_KEY)));
}
@Override
@@ -173,4 +194,24 @@ public boolean isSuspended() {
public void setSuspended(boolean suspended) {
this.suspended = suspended;
}
+
+ @Override
+ public boolean isDeleted() {
+ return deleted;
+ }
+
+ @Override
+ public void setDeleted(boolean deleted) {
+ this.deleted = deleted;
+ }
+
+ @Override
+ public boolean shouldDeleteData() {
+ return deleteData;
+ }
+
+ @Override
+ public void setDeleteData(boolean deleteData) {
+ this.deleteData = deleteData;
+ }
}
diff --git a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/legacy/LegacyDataConverter.java b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/legacy/LegacyDataConverter.java
index d714bdc5c..b3ab914bb 100644
--- a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/legacy/LegacyDataConverter.java
+++ b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/legacy/LegacyDataConverter.java
@@ -42,7 +42,7 @@
import org.codice.ditto.replication.api.ReplicationPersistenceException;
import org.codice.ditto.replication.api.data.ReplicationSite;
import org.codice.ditto.replication.api.data.ReplicatorConfig;
-import org.codice.ditto.replication.api.impl.MetacardHelper;
+import org.codice.ditto.replication.api.impl.Metacards;
import org.codice.ditto.replication.api.impl.data.ReplicatorConfigImpl;
import org.codice.ditto.replication.api.impl.persistence.ReplicationPersistentStore;
import org.codice.ditto.replication.api.mcard.ReplicationConfig;
@@ -65,7 +65,7 @@ public class LegacyDataConverter {
private final FilterBuilder filterBuilder;
- private final MetacardHelper helper;
+ private final Metacards metacards;
private final SiteManager siteManager;
@@ -78,14 +78,14 @@ public class LegacyDataConverter {
public LegacyDataConverter(
CatalogFramework framework,
FilterBuilder filterBuilder,
- MetacardHelper helper,
+ Metacards metacards,
SiteManager siteManager,
ReplicatorConfigManager newConfigManager,
ReplicationPersistentStore persistentStore) {
this(
framework,
filterBuilder,
- helper,
+ metacards,
siteManager,
newConfigManager,
persistentStore,
@@ -96,14 +96,14 @@ public LegacyDataConverter(
LegacyDataConverter(
CatalogFramework framework,
FilterBuilder filterBuilder,
- MetacardHelper helper,
+ Metacards metacards,
SiteManager siteManager,
ReplicatorConfigManager newConfigManager,
ReplicationPersistentStore persistentStore,
Security security) {
this.framework = framework;
this.filterBuilder = filterBuilder;
- this.helper = helper;
+ this.metacards = metacards;
this.siteManager = siteManager;
this.newConfigManager = newConfigManager;
this.persistentStore = persistentStore;
@@ -169,7 +169,7 @@ private void convertConfigs() {
}
List getAllConfigs() {
- return helper.getTypeForFilter(
+ return metacards.getTypeForFilter(
filterBuilder.attribute(Metacard.TAGS).is().like().text(ReplicationConfig.METACARD_TAG),
this::getConfigFromMetacard);
}
@@ -190,17 +190,17 @@ ReplicatorConfig getConfigFromMetacard(Metacard mcard) {
ReplicatorConfigImpl config = new ReplicatorConfigImpl();
Direction direction;
config.setId(mcard.getId());
- config.setName(helper.getAttributeValueOrDefault(mcard, ReplicationConfig.NAME, null));
+ config.setName(metacards.getAttributeValueOrDefault(mcard, ReplicationConfig.NAME, null));
config.setDescription(
- helper.getAttributeValueOrDefault(mcard, ReplicationConfig.DESCRIPTION, null));
+ metacards.getAttributeValueOrDefault(mcard, ReplicationConfig.DESCRIPTION, null));
direction =
Direction.valueOf(
- helper.getAttributeValueOrDefault(
+ metacards.getAttributeValueOrDefault(
mcard, ReplicationConfig.DIRECTION, Direction.PUSH.name()));
config.setFailureRetryCount(
- helper.getAttributeValueOrDefault(
+ metacards.getAttributeValueOrDefault(
mcard, ReplicationConfig.FAILURE_RETRY_COUNT, DEFAULT_FAILURE_RETRY_COUNT));
- String oldUrl = helper.getAttributeValueOrDefault(mcard, ReplicationConfig.URL, null);
+ String oldUrl = metacards.getAttributeValueOrDefault(mcard, ReplicationConfig.URL, null);
if (oldUrl != null) {
config = setSourceAndDestinationWithOldUrl(config, direction, oldUrl);
@@ -209,11 +209,11 @@ ReplicatorConfig getConfigFromMetacard(Metacard mcard) {
}
} else {
config.setDestination(
- helper.getAttributeValueOrDefault(mcard, ReplicationConfig.DESTINATION, null));
- config.setSource(helper.getAttributeValueOrDefault(mcard, ReplicationConfig.SOURCE, null));
+ metacards.getAttributeValueOrDefault(mcard, ReplicationConfig.DESTINATION, null));
+ config.setSource(metacards.getAttributeValueOrDefault(mcard, ReplicationConfig.SOURCE, null));
}
- config.setFilter(helper.getAttributeValueOrDefault(mcard, ReplicationConfig.CQL, null));
+ config.setFilter(metacards.getAttributeValueOrDefault(mcard, ReplicationConfig.CQL, null));
config.setBidirectional(direction == Direction.BOTH);
if (config.getDestination() == null
@@ -229,8 +229,9 @@ ReplicatorConfig getConfigFromMetacard(Metacard mcard) {
ReplicationConfig.CQL);
return null;
}
- config.setVersion(helper.getAttributeValueOrDefault(mcard, ReplicationConfig.VERSION, 1));
- config.setSuspended(helper.getAttributeValueOrDefault(mcard, ReplicationConfig.SUSPEND, false));
+ config.setVersion(metacards.getAttributeValueOrDefault(mcard, ReplicationConfig.VERSION, 1));
+ config.setSuspended(
+ metacards.getAttributeValueOrDefault(mcard, ReplicationConfig.SUSPEND, false));
return config;
}
diff --git a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/persistence/ReplicatorConfigManagerImpl.java b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/persistence/ReplicatorConfigManagerImpl.java
index ff27b1a2f..d6be31607 100644
--- a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/persistence/ReplicatorConfigManagerImpl.java
+++ b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/persistence/ReplicatorConfigManagerImpl.java
@@ -2,7 +2,6 @@
import java.util.stream.Stream;
import javax.ws.rs.NotFoundException;
-import org.codice.ditto.replication.api.ReplicationPersistenceException;
import org.codice.ditto.replication.api.data.ReplicatorConfig;
import org.codice.ditto.replication.api.impl.data.ReplicatorConfigImpl;
import org.codice.ditto.replication.api.persistence.ReplicatorConfigManager;
@@ -47,10 +46,10 @@ public void remove(String id) {
}
@Override
- public boolean configExists(String configId) {
+ public boolean exists(String id) {
try {
- persistentStore.get(ReplicatorConfigImpl.class, configId);
- } catch (NotFoundException | ReplicationPersistenceException e) {
+ persistentStore.get(ReplicatorConfigImpl.class, id);
+ } catch (NotFoundException e) {
return false;
}
return true;
diff --git a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/persistence/SiteManagerImpl.java b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/persistence/SiteManagerImpl.java
index 118ead9dd..da3d1a298 100644
--- a/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/persistence/SiteManagerImpl.java
+++ b/replication-api-impl/src/main/java/org/codice/ditto/replication/api/impl/persistence/SiteManagerImpl.java
@@ -92,4 +92,14 @@ public void save(ReplicationSite site) {
public void remove(String id) {
persistentStore.delete(ReplicationSiteImpl.class, id);
}
+
+ @Override
+ public boolean exists(String id) {
+ try {
+ persistentStore.get(ReplicationSiteImpl.class, id);
+ } catch (NotFoundException e) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/replication-api-impl/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/replication-api-impl/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 86c855ccc..d29cbc8a6 100644
--- a/replication-api-impl/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/replication-api-impl/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -30,13 +30,32 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ class="org.codice.ditto.replication.api.impl.persistence.ReplicationPersistentStore">
@@ -45,13 +64,13 @@
init-method="init">
-
+
-
+
@@ -92,10 +111,10 @@
-
+
-
+
@@ -110,7 +129,7 @@
-
+
@@ -120,7 +139,7 @@
destroy-method="cleanUp">
-
+
@@ -137,7 +156,8 @@
-
+
diff --git a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/MetacardsTest.java b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/MetacardsTest.java
new file mode 100644
index 000000000..9b17d0924
--- /dev/null
+++ b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/MetacardsTest.java
@@ -0,0 +1,116 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
+package org.codice.ditto.replication.api.impl;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.Sets;
+import ddf.catalog.CatalogFramework;
+import ddf.catalog.data.Metacard;
+import ddf.catalog.data.Result;
+import ddf.catalog.federation.FederationException;
+import ddf.catalog.filter.FilterBuilder;
+import ddf.catalog.filter.proxy.builder.GeotoolsFilterBuilder;
+import ddf.catalog.operation.DeleteRequest;
+import ddf.catalog.operation.DeleteResponse;
+import ddf.catalog.operation.QueryRequest;
+import ddf.catalog.operation.QueryResponse;
+import ddf.catalog.source.IngestException;
+import ddf.catalog.source.SourceUnavailableException;
+import ddf.catalog.source.UnsupportedQueryException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MetacardsTest {
+
+ private Metacards metacards;
+
+ @Mock CatalogFramework catalogFramework;
+
+ @Before
+ public void setup() {
+ FilterBuilder filterBuilder = new GeotoolsFilterBuilder();
+ metacards = new Metacards(catalogFramework, filterBuilder);
+ }
+
+ @Test
+ public void testGetIdsOfMetacardsInCatalog()
+ throws UnsupportedQueryException, FederationException, SourceUnavailableException {
+ Set ids = Sets.newHashSet(getIds(5));
+ QueryResponse queryResponse = mock(QueryResponse.class);
+ List results = mockResults(ids);
+ when(queryResponse.getResults()).thenReturn(results);
+ when(catalogFramework.query(any(QueryRequest.class))).thenReturn(queryResponse);
+
+ assertThat(metacards.getIdsOfMetacardsInCatalog(ids), equalTo(ids));
+ }
+
+ @Test
+ public void testBatchDelete() throws SourceUnavailableException, IngestException {
+ final int batchSize = 5;
+ String[] idsToDelete = getIds(batchSize * 3 + 1);
+
+ metacards.doDelete(idsToDelete, batchSize);
+ verify(catalogFramework, times(4)).delete(any(DeleteRequest.class));
+ }
+
+ @Test
+ public void testBatchDeleteWithSomeFailures() throws SourceUnavailableException, IngestException {
+ final int batchSize = 5;
+ String[] idsToDelete = getIds(batchSize * 3);
+
+ when(catalogFramework.delete(any(DeleteRequest.class)))
+ .thenReturn(mock(DeleteResponse.class))
+ .thenThrow(IngestException.class)
+ .thenReturn(mock(DeleteResponse.class));
+
+ metacards.doDelete(idsToDelete, batchSize);
+ verify(catalogFramework, times(8)).delete(any(DeleteRequest.class));
+ }
+
+ private String[] getIds(int size) {
+ String[] ids = new String[size];
+ for (int i = 0; i < size; i++) {
+ ids[i] = i + "";
+ }
+ return ids;
+ }
+
+ private List mockResults(Set ids) {
+ List results = new ArrayList<>();
+ ids.forEach(id -> results.add(mockResult(id)));
+ return results;
+ }
+
+ private Result mockResult(String metacardId) {
+ Result result = mock(Result.class);
+ Metacard metacard = mock(Metacard.class);
+ when(metacard.getId()).thenReturn(metacardId);
+ when(result.getMetacard()).thenReturn(metacard);
+ return result;
+ }
+}
diff --git a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ReplicatorHistoryImplTest.java b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ReplicatorHistoryImplTest.java
index f1c7335ca..de5b6727b 100644
--- a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ReplicatorHistoryImplTest.java
+++ b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ReplicatorHistoryImplTest.java
@@ -70,7 +70,7 @@ public class ReplicatorHistoryImplTest {
@Mock CatalogProvider provider;
- @Mock MetacardHelper helper;
+ @Mock Metacards metacards;
@Before
public void setUp() throws Exception {
@@ -80,10 +80,10 @@ public void setUp() throws Exception {
types.add(new ReplicationHistoryAttributes());
MetacardType type = new MetacardTypeImpl("replication-history", types);
doCallRealMethod()
- .when(helper)
+ .when(metacards)
.setIfPresent(any(Metacard.class), any(String.class), any(Serializable.class));
doCallRealMethod()
- .when(helper)
+ .when(metacards)
.setIfPresentOrDefault(
any(Metacard.class),
any(String.class),
@@ -94,7 +94,7 @@ public void setUp() throws Exception {
.thenAnswer(invocation -> invocation.getArgumentAt(0, Callable.class).call());
when(security.runAsAdmin(any(PrivilegedAction.class)))
.thenAnswer(invocation -> invocation.getArgumentAt(0, PrivilegedAction.class).run());
- history = new ReplicatorHistoryImpl(framework, provider, builder, helper, type, security);
+ history = new ReplicatorHistoryImpl(framework, provider, builder, metacards, type, security);
}
@Test
@@ -103,7 +103,7 @@ public void init() throws Exception {
generateOldStatus(
"test", new Date(0), new Date(TimeUnit.DAYS.toMillis(1)), TimeUnit.MINUTES.toMillis(5));
- when(helper.getTypeForFilter(any(Filter.class), any(Function.class))).thenReturn(events);
+ when(metacards.getTypeForFilter(any(Filter.class), any(Function.class))).thenReturn(events);
history.init();
ArgumentCaptor captor = ArgumentCaptor.forClass(CreateRequest.class);
verify(framework).create(captor.capture());
@@ -140,7 +140,7 @@ public void initPreviouslyCondensedEvents() throws Exception {
oldStatus.setLastRun(origTime);
oldStatus.setLastSuccess(origTime);
oldStatus.setStatus(Status.SUCCESS);
- when(helper.getTypeForFilter(any(Filter.class), any(Function.class)))
+ when(metacards.getTypeForFilter(any(Filter.class), any(Function.class)))
.thenReturn(Collections.singletonList(oldStatus));
history.init();
verify(framework, never()).create(any(CreateRequest.class));
@@ -149,7 +149,7 @@ public void initPreviouslyCondensedEvents() throws Exception {
@Test
public void addReplicationEventNoPreviousEventsSuccess() throws Exception {
- when(helper.getTypeForFilter(any(Filter.class), any(Function.class)))
+ when(metacards.getTypeForFilter(any(Filter.class), any(Function.class)))
.thenReturn(new ArrayList());
Date start = new Date();
ReplicationStatus status = new ReplicationStatusImpl("test");
@@ -166,7 +166,7 @@ public void addReplicationEventNoPreviousEventsSuccess() throws Exception {
@Test
public void addReplicationEventNoPreviousEventsFailure() throws Exception {
- when(helper.getTypeForFilter(any(Filter.class), any(Function.class)))
+ when(metacards.getTypeForFilter(any(Filter.class), any(Function.class)))
.thenReturn(new ArrayList());
Date start = new Date();
ReplicationStatus status = new ReplicationStatusImpl("test");
@@ -190,7 +190,7 @@ public void addReplicationEventPreviousEvents() throws Exception {
oldStatus.setLastSuccess(origTime);
oldStatus.setStatus(Status.SUCCESS);
- when(helper.getTypeForFilter(any(Filter.class), any(Function.class)))
+ when(metacards.getTypeForFilter(any(Filter.class), any(Function.class)))
.thenReturn(Collections.singletonList(oldStatus));
Date start = new Date();
ReplicationStatus status = new ReplicationStatusImpl("test");
@@ -210,7 +210,7 @@ public void addReplicationEventPreviousEvents() throws Exception {
@Test(expected = ReplicationPersistenceException.class)
public void addReplicationEventStorageException() throws Exception {
- when(helper.getTypeForFilter(any(Filter.class), any(Function.class)))
+ when(metacards.getTypeForFilter(any(Filter.class), any(Function.class)))
.thenReturn(new ArrayList());
when(framework.create(any(CreateRequest.class))).thenThrow(new IngestException());
Date start = new Date();
diff --git a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ReplicatorImplTest.java b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ReplicatorImplTest.java
index 0afbf49e1..1ea9bfcfb 100644
--- a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ReplicatorImplTest.java
+++ b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ReplicatorImplTest.java
@@ -28,7 +28,7 @@
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import org.codice.ddf.security.common.Security;
-import org.codice.ditto.replication.api.ReplicationPersistentStore;
+import org.codice.ditto.replication.api.ReplicationItemManager;
import org.codice.ditto.replication.api.ReplicationStatus;
import org.codice.ditto.replication.api.ReplicationStore;
import org.codice.ditto.replication.api.ReplicatorHistory;
@@ -56,7 +56,7 @@ public class ReplicatorImplTest {
@Mock ReplicatorStoreFactory replicatorStoreFactory;
@Mock ReplicatorHistory history;
- @Mock ReplicationPersistentStore persistentStore;
+ @Mock ReplicationItemManager persistentStore;
@Mock SiteManager siteManager;
@Mock ExecutorService executor;
@Mock Security security;
diff --git a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ReplicatorStoreFactoryImplTest.java b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ReplicatorStoreFactoryImplTest.java
index 087f2da1f..0fb9367a4 100644
--- a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ReplicatorStoreFactoryImplTest.java
+++ b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ReplicatorStoreFactoryImplTest.java
@@ -14,7 +14,7 @@
package org.codice.ditto.replication.api.impl;
import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.spy;
import com.thoughtworks.xstream.converters.Converter;
diff --git a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ScheduledReplicatorDeleterTest.java b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ScheduledReplicatorDeleterTest.java
new file mode 100644
index 000000000..ac9e1a7f5
--- /dev/null
+++ b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/ScheduledReplicatorDeleterTest.java
@@ -0,0 +1,385 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
+package org.codice.ditto.replication.api.impl;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anySet;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import ddf.catalog.source.SourceUnavailableException;
+import ddf.security.service.SecurityServiceException;
+import java.lang.reflect.InvocationTargetException;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.codice.ddf.configuration.SystemInfo;
+import org.codice.ddf.persistence.PersistenceException;
+import org.codice.ddf.security.common.Security;
+import org.codice.ditto.replication.api.ReplicationItem;
+import org.codice.ditto.replication.api.ReplicationItemManager;
+import org.codice.ditto.replication.api.ReplicationPersistenceException;
+import org.codice.ditto.replication.api.ReplicationStatus;
+import org.codice.ditto.replication.api.ReplicatorHistory;
+import org.codice.ditto.replication.api.data.ReplicatorConfig;
+import org.codice.ditto.replication.api.persistence.ReplicatorConfigManager;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ScheduledReplicatorDeleterTest {
+
+ private static final String SITE_NAME = "siteName";
+
+ private TestSheduledReplicatorDeleter scheduledReplicatorDeleter;
+
+ private final int pageSize = 1;
+
+ private final String SOURCE = "source";
+
+ @Mock ReplicatorConfigManager replicatorConfigManager;
+
+ @Mock ScheduledExecutorService scheduledExecutorService;
+
+ @Mock ReplicationItemManager replicationItemManager;
+
+ @Mock ReplicatorHistory replicatorHistory;
+
+ @Mock Metacards metacards;
+
+ @Mock Security security;
+
+ @Before
+ public void setup() throws SecurityServiceException, InvocationTargetException {
+ when(security.runWithSubjectOrElevate(any(Callable.class)))
+ .thenAnswer(invocation -> invocation.getArgumentAt(0, Callable.class).call());
+ when(security.runAsAdmin(any(PrivilegedAction.class)))
+ .thenAnswer(invocation -> invocation.getArgumentAt(0, PrivilegedAction.class).run());
+
+ scheduledReplicatorDeleter =
+ new TestSheduledReplicatorDeleter(
+ replicatorConfigManager,
+ scheduledExecutorService,
+ replicationItemManager,
+ replicatorHistory,
+ metacards,
+ security,
+ 1,
+ pageSize);
+ }
+
+ @Test
+ public void testCleanupOrphanedItems() throws PersistenceException {
+ final int pageSize = 2;
+ scheduledReplicatorDeleter =
+ new TestSheduledReplicatorDeleter(
+ replicatorConfigManager,
+ scheduledExecutorService,
+ replicationItemManager,
+ replicatorHistory,
+ metacards,
+ security,
+ 1,
+ pageSize);
+
+ final String configId = "configId";
+ final String configName = "configName";
+ ReplicatorConfig config = mockConfig(configId, configName, false, false);
+ when(replicatorConfigManager.objects()).thenReturn(Stream.of(config));
+
+ final String metacardId1 = "mId1";
+ final String metacardId2 = "mId2";
+ final String metacardId3 = "mId3";
+ final String metacardId4 = "mId4";
+ final String metacardId5 = "mId5";
+ final String metacardId6 = "mId6";
+
+ ReplicationItem rep1 = mockRepItem(metacardId1, SITE_NAME);
+ ReplicationItem rep2 = mockRepItem(metacardId2, SITE_NAME);
+ ReplicationItem rep3 = mockRepItem(metacardId3, SITE_NAME);
+ ReplicationItem rep4 = mockRepItem(metacardId4, SITE_NAME);
+ ReplicationItem rep5 = mockRepItem(metacardId5, SITE_NAME);
+ ReplicationItem rep6 = mockRepItem(metacardId6, SITE_NAME);
+ when(rep1.getConfigurationId()).thenReturn("noExistingConfigId");
+ when(rep2.getConfigurationId()).thenReturn(configId);
+ when(rep3.getConfigurationId()).thenReturn(configId);
+ when(rep4.getConfigurationId()).thenReturn(configId);
+ when(rep5.getConfigurationId()).thenReturn("noExistingConfigId");
+ when(rep6.getConfigurationId()).thenReturn("noExistingConfigId");
+
+ when(replicationItemManager.getItemsForConfig("", 0, pageSize))
+ .thenReturn(ImmutableList.of(rep1, rep2));
+ when(replicationItemManager.getItemsForConfig("", 1, pageSize))
+ .thenReturn(ImmutableList.of(rep3, rep4));
+ when(replicationItemManager.getItemsForConfig("", 3, pageSize))
+ .thenReturn(ImmutableList.of(rep5, rep6));
+
+ when(metacards.getIdsOfMetacardsInCatalog(ImmutableSet.of(metacardId1)))
+ .thenReturn(Collections.emptySet());
+ when(metacards.getIdsOfMetacardsInCatalog(ImmutableSet.of(metacardId5, metacardId6)))
+ .thenReturn(Collections.singleton(metacardId5));
+
+ scheduledReplicatorDeleter.cleanup();
+
+ verify(replicationItemManager, times(1)).deleteItem(metacardId1, SOURCE, SITE_NAME);
+ verify(replicationItemManager, never()).deleteItem(metacardId2, SOURCE, SITE_NAME);
+ verify(replicationItemManager, never()).deleteItem(metacardId3, SOURCE, SITE_NAME);
+ verify(replicationItemManager, never()).deleteItem(metacardId4, SOURCE, SITE_NAME);
+ verify(replicationItemManager, never()).deleteItem(metacardId5, SOURCE, SITE_NAME);
+ verify(replicationItemManager, times(1)).deleteItem(metacardId6, SOURCE, SITE_NAME);
+ }
+
+ @Test
+ public void testOrphanedItemsToFailGetItems() throws PersistenceException {
+ final String configId = "configId";
+ final String configName = "configName";
+ ReplicatorConfig config = mockConfig(configId, configName, true, true);
+
+ when(replicatorConfigManager.objects()).thenReturn(Stream.of(config));
+
+ doThrow(PersistenceException.class).when(replicationItemManager).getItemsForConfig("", 0, 1);
+ verify(replicationItemManager, never()).deleteItem(anyString(), anyString(), anyString());
+
+ scheduledReplicatorDeleter.cleanup();
+ }
+
+ @Test
+ public void testCleanupDeletedConfigs() throws PersistenceException, SourceUnavailableException {
+ final String deletedConfigId = "deletedConfigId";
+ final String deletedConfigName = "deletedConfigName";
+ final String metacardId1 = "mId1";
+ final String metacardId2 = "mId2";
+ final String metacardId3 = "mId3";
+ ReplicatorConfig deletedConfig = mockConfig(deletedConfigId, deletedConfigName, true, true);
+
+ final String configId = "configId";
+ final String configName = "configName";
+ ReplicatorConfig config = mockConfig(configId, configName, false, false);
+
+ when(replicatorConfigManager.objects()).thenReturn(Stream.of(config, deletedConfig));
+
+ ReplicationItem rep1 = mockRepItem(metacardId1, SITE_NAME);
+ ReplicationItem rep2 = mockRepItem(metacardId2, "anotherSite");
+ ReplicationItem rep3 = mockRepItem(metacardId3, SITE_NAME);
+
+ when(replicationItemManager.getItemsForConfig(deletedConfigId, 0, pageSize))
+ .thenReturn(Collections.singletonList(rep1));
+ when(replicationItemManager.getItemsForConfig(deletedConfigId, 1, pageSize))
+ .thenReturn(Collections.singletonList(rep2));
+ when(replicationItemManager.getItemsForConfig(deletedConfigId, 2, pageSize))
+ .thenReturn(Collections.singletonList(rep3));
+
+ when(metacards.getIdsOfMetacardsInCatalog(Collections.singleton(metacardId1)))
+ .thenReturn(Collections.singleton(metacardId1));
+ when(metacards.getIdsOfMetacardsInCatalog(Collections.singleton(metacardId3)))
+ .thenReturn(Collections.singleton(metacardId3));
+
+ ReplicationStatus mockStatus = mockRepStatus("id");
+ List replicationStatuses = Collections.singletonList(mockStatus);
+
+ when(replicatorHistory.getReplicationEvents(deletedConfigName)).thenReturn(replicationStatuses);
+ Set eventIds =
+ replicationStatuses.stream().map(ReplicationStatus::getId).collect(Collectors.toSet());
+
+ scheduledReplicatorDeleter.cleanup();
+
+ // deleted config
+ verify(metacards, times(1)).doDelete(Collections.singleton(metacardId1).toArray(new String[0]));
+ verify(metacards, times(1)).doDelete(Collections.singleton(metacardId3).toArray(new String[0]));
+ verify(replicatorHistory, times(1)).getReplicationEvents(deletedConfigName);
+ verify(replicatorHistory, times(1)).removeReplicationEvents(eventIds);
+ verify(replicatorConfigManager, times(1)).remove(deletedConfigId);
+
+ // not deleted config
+ verify(replicationItemManager, never()).getItemsForConfig(configId, 0, pageSize);
+ verify(replicatorHistory, never()).getReplicationEvents(configName);
+ verify(replicationItemManager, times(1)).deleteItemsForConfig(deletedConfigId);
+ verify(replicatorConfigManager, times(1)).remove(deletedConfigId);
+ }
+
+ @Test
+ public void testConfigNotMarkedForDeleteData()
+ throws PersistenceException, SourceUnavailableException {
+ final String configId = "configId";
+ final String configName = "configName";
+ final String metacardId = "metacardId";
+ ReplicatorConfig config = mockConfig(configId, configName, true, false);
+ when(replicatorConfigManager.objects()).thenReturn(Stream.of(config));
+
+ List mockItems = new ArrayList<>();
+ mockItems.add(mockRepItem(metacardId, SITE_NAME));
+ when(replicationItemManager.getItemsForConfig(configId, 0, pageSize)).thenReturn(mockItems);
+
+ ReplicationStatus mockStatus = mockRepStatus("id");
+ List replicationStatuses = Collections.singletonList(mockStatus);
+
+ when(replicatorHistory.getReplicationEvents(configName)).thenReturn(replicationStatuses);
+ Set eventIds =
+ replicationStatuses.stream().map(ReplicationStatus::getId).collect(Collectors.toSet());
+
+ scheduledReplicatorDeleter.cleanup();
+
+ verify(metacards, never()).doDelete(any());
+ verify(replicatorHistory, times(1)).getReplicationEvents(configName);
+ verify(replicatorHistory, times(1)).removeReplicationEvents(eventIds);
+ verify(replicatorConfigManager, times(1)).remove(configId);
+ }
+
+ @Test
+ public void testFailToRetrieveItemsDoesntRemoveMetacardsOrHistoryOrConfig()
+ throws SourceUnavailableException, PersistenceException {
+ final String configId = "configId";
+ ReplicatorConfig config = mockConfig(configId, "name", true, true);
+ when(replicatorConfigManager.objects()).thenReturn(Stream.of(config));
+
+ when(replicationItemManager.getItemsForConfig(configId, 0, pageSize))
+ .thenThrow(PersistenceException.class);
+
+ scheduledReplicatorDeleter.cleanup();
+
+ verify(metacards, never()).doDelete(any());
+ verify(replicationItemManager, never()).deleteItemsForConfig(configId);
+ verify(replicatorHistory, never()).getReplicationEvents(configId);
+ verify(replicatorHistory, never()).removeReplicationEvents(anySet());
+ verify(replicatorConfigManager, never()).remove(configId);
+ }
+
+ @Test
+ public void testFailDeleteMetacardsDoesntRemoveItemsOrConfig()
+ throws SourceUnavailableException, PersistenceException {
+ final String configId = "configId";
+ ReplicatorConfig config = mockConfig(configId, "name", true, true);
+ when(replicatorConfigManager.objects()).thenReturn(Stream.of(config));
+
+ List mockItems = new ArrayList<>();
+ mockItems.add(mockRepItem("metcardId", SITE_NAME));
+ when(replicationItemManager.getItemsForConfig(configId, 0, pageSize)).thenReturn(mockItems);
+
+ doThrow(SourceUnavailableException.class).when(metacards).doDelete(any());
+
+ scheduledReplicatorDeleter.cleanup();
+
+ verify(replicationItemManager, never()).deleteItemsForConfig(configId);
+ verify(replicatorHistory, never()).getReplicationEvents(configId);
+ verify(replicatorHistory, never()).removeReplicationEvents(anySet());
+ verify(replicatorConfigManager, never()).remove(configId);
+ }
+
+ @Test
+ public void testFailCleanupHistoryDoesntRemoveConfig()
+ throws PersistenceException, SourceUnavailableException {
+ final String configId = "configId";
+ final String configName = "configName";
+ final String metacardId1 = "mId1";
+ ReplicatorConfig config = mockConfig(configId, configName, true, true);
+ when(replicatorConfigManager.objects()).thenReturn(Stream.of(config));
+
+ List mockItems = new ArrayList<>();
+ mockItems.add(mockRepItem(metacardId1, SITE_NAME));
+ when(replicationItemManager.getItemsForConfig(configId, 0, pageSize)).thenReturn(mockItems);
+
+ Set itemMetacardIds = Collections.singleton(metacardId1);
+ Set idsInCatalog = Collections.singleton(metacardId1);
+ when(metacards.getIdsOfMetacardsInCatalog(itemMetacardIds)).thenReturn(idsInCatalog);
+
+ ReplicationStatus mockStatus = mockRepStatus("id");
+ List replicationStatuses = Collections.singletonList(mockStatus);
+
+ when(replicatorHistory.getReplicationEvents(configName)).thenReturn(replicationStatuses);
+ Set eventIds =
+ replicationStatuses.stream().map(ReplicationStatus::getId).collect(Collectors.toSet());
+ doThrow(ReplicationPersistenceException.class)
+ .when(replicatorHistory)
+ .removeReplicationEvents(eventIds);
+
+ scheduledReplicatorDeleter.cleanup();
+
+ verify(metacards, times(1)).doDelete(idsInCatalog.toArray(new String[0]));
+ verify(replicatorHistory, times(1)).getReplicationEvents(configName);
+ verify(replicatorHistory, times(1)).removeReplicationEvents(eventIds);
+ verify(replicatorConfigManager, never()).remove(configId);
+ }
+
+ private ReplicatorConfig mockConfig(
+ String id, String name, boolean isDeleted, boolean deleteData) {
+ ReplicatorConfig config = mock(ReplicatorConfig.class);
+ when(config.getId()).thenReturn(id);
+ when(config.getName()).thenReturn(name);
+ when(config.isDeleted()).thenReturn(isDeleted);
+ when(config.shouldDeleteData()).thenReturn(deleteData);
+ return config;
+ }
+
+ private ReplicationStatus mockRepStatus(String id) {
+ ReplicationStatus status = mock(ReplicationStatus.class);
+ when(status.getId()).thenReturn(id);
+ return status;
+ }
+
+ private ReplicationItem mockRepItem(String metacardId, String destination) {
+ ReplicationItem item = mock(ReplicationItem.class);
+ when(item.getMetacardId()).thenReturn(metacardId);
+ when(item.getDestination()).thenReturn(destination);
+ when(item.getSource()).thenReturn(SOURCE);
+ return item;
+ }
+
+ /**
+ * {@link ScheduledReplicatorDeleter} extension solely for overriding a static method call to
+ * {@link SystemInfo#getSiteName()}
+ */
+ private class TestSheduledReplicatorDeleter extends ScheduledReplicatorDeleter {
+
+ public TestSheduledReplicatorDeleter(
+ ReplicatorConfigManager replicatorConfigManager,
+ ScheduledExecutorService scheduledExecutorService,
+ ReplicationItemManager replicationItemManager,
+ ReplicatorHistory replicatorHistory,
+ Metacards metacards,
+ Security security,
+ long pollPeriod,
+ int pageSize) {
+ super(
+ replicatorConfigManager,
+ scheduledExecutorService,
+ replicationItemManager,
+ replicatorHistory,
+ metacards,
+ security,
+ pollPeriod,
+ pageSize);
+ }
+
+ @Override
+ String getSiteName() {
+ return SITE_NAME;
+ }
+ }
+}
diff --git a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/SyncHelperTest.java b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/SyncHelperTest.java
index 5998fc4e4..abc02f31f 100644
--- a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/SyncHelperTest.java
+++ b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/SyncHelperTest.java
@@ -42,7 +42,7 @@
import java.util.Date;
import java.util.Optional;
import org.apache.shiro.util.ThreadContext;
-import org.codice.ditto.replication.api.ReplicationPersistentStore;
+import org.codice.ditto.replication.api.ReplicationItemManager;
import org.codice.ditto.replication.api.ReplicationStatus;
import org.codice.ditto.replication.api.ReplicationStore;
import org.codice.ditto.replication.api.ReplicatorHistory;
@@ -63,7 +63,7 @@ public class SyncHelperTest {
@Mock ReplicationStore source;
@Mock ReplicationStore destination;
@Mock ReplicatorConfig config;
- @Mock ReplicationPersistentStore persistentStore;
+ @Mock ReplicationItemManager persistentStore;
@Mock ReplicatorHistory history;
ReplicationStatus status;
diff --git a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/legacy/LegacyDataConverterTest.java b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/legacy/LegacyDataConverterTest.java
index 33cdb01b0..90507f396 100644
--- a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/legacy/LegacyDataConverterTest.java
+++ b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/legacy/LegacyDataConverterTest.java
@@ -19,7 +19,12 @@
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import ddf.catalog.CatalogFramework;
import ddf.catalog.data.Metacard;
@@ -49,7 +54,7 @@
import org.codice.ditto.replication.api.ReplicationPersistenceException;
import org.codice.ditto.replication.api.data.ReplicationSite;
import org.codice.ditto.replication.api.data.ReplicatorConfig;
-import org.codice.ditto.replication.api.impl.MetacardHelper;
+import org.codice.ditto.replication.api.impl.Metacards;
import org.codice.ditto.replication.api.impl.data.ReplicationSiteImpl;
import org.codice.ditto.replication.api.impl.data.ReplicatorConfigImpl;
import org.codice.ditto.replication.api.impl.mcard.ReplicationConfigAttributes;
@@ -72,16 +77,14 @@
@RunWith(MockitoJUnitRunner.class)
public class LegacyDataConverterTest {
- // @Rule public Timeout globalTimeout = Timeout.seconds(10);
-
@Rule
public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
- LegacyDataConverter converter;
+ private LegacyDataConverter converter;
@Mock CatalogFramework framework;
- @Mock MetacardHelper helper;
+ @Mock Metacards helper;
@Mock SiteManager siteManager;
@@ -91,7 +94,7 @@ public class LegacyDataConverterTest {
@Mock Security security;
- MetacardType type;
+ private MetacardType type;
@Before
public void setUp() throws Exception {
@@ -122,13 +125,7 @@ public void setUp() throws Exception {
oldSite.setName("oldSiteName");
oldSite.setUrl("https://oldurl:8080");
when(persistentStore.objects(eq(OldSite.class)))
- .thenAnswer(
- new Answer>() {
- @Override
- public Stream answer(InvocationOnMock invocation) throws Throwable {
- return Stream.of(oldSite);
- }
- });
+ .thenAnswer((Answer>) invocation -> Stream.of(oldSite));
when(siteManager.createSite(anyString(), anyString())).thenReturn(new ReplicationSiteImpl());
converter =
new LegacyDataConverter(
diff --git a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/persistence/ReplicationPersistentStoreTest.java b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/persistence/ReplicationPersistentStoreTest.java
index f71aa0b8e..c698a8cfd 100644
--- a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/persistence/ReplicationPersistentStoreTest.java
+++ b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/persistence/ReplicationPersistentStoreTest.java
@@ -116,6 +116,8 @@ private Map createRepSyncMap(int num) {
map.put(RETRY_COUNT, num);
map.put(BIDIRECTIONAL, "true");
map.put(SUSPENDED, "false");
+ map.put("deleted", "false");
+ map.put("deleteData", "false");
map.put(DESCRIPTION, DESCRIPTION + num);
map.put(VERSION, ReplicatorConfigImpl.CURRENT_VERSION);
return map;
diff --git a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/persistence/ReplicatorConfigManagerImplTest.java b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/persistence/ReplicatorConfigManagerImplTest.java
index e36464a90..ba75544e5 100644
--- a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/persistence/ReplicatorConfigManagerImplTest.java
+++ b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/persistence/ReplicatorConfigManagerImplTest.java
@@ -13,6 +13,8 @@
*/
package org.codice.ditto.replication.api.impl.persistence;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
@@ -21,6 +23,7 @@
import static org.mockito.Mockito.when;
import java.util.stream.Stream;
+import javax.ws.rs.NotFoundException;
import org.codice.ditto.replication.api.data.ReplicatorConfig;
import org.codice.ditto.replication.api.impl.data.ReplicatorConfigImpl;
import org.junit.Before;
@@ -32,7 +35,7 @@
@RunWith(MockitoJUnitRunner.class)
public class ReplicatorConfigManagerImplTest {
- ReplicatorConfigManagerImpl manager;
+ private ReplicatorConfigManagerImpl manager;
@Mock ReplicationPersistentStore persistentStore;
@@ -73,4 +76,15 @@ public void removeConfig() {
manager.remove("id");
verify(persistentStore).delete(eq(ReplicatorConfigImpl.class), anyString());
}
+
+ @Test
+ public void testExistsNotFound() {
+ when(persistentStore.get(ReplicatorConfigImpl.class, "id")).thenThrow(NotFoundException.class);
+ assertThat(manager.exists("id"), is(false));
+ }
+
+ @Test
+ public void testExistsConfigFound() {
+ assertThat(manager.exists("id"), is(true));
+ }
}
diff --git a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/persistence/SiteManagerImplTest.java b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/persistence/SiteManagerImplTest.java
index 9dc3ef21d..06142da3c 100644
--- a/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/persistence/SiteManagerImplTest.java
+++ b/replication-api-impl/src/test/java/org/codice/ditto/replication/api/impl/persistence/SiteManagerImplTest.java
@@ -41,20 +41,20 @@ public class SiteManagerImplTest {
private static final String LOCAL_SITE_ID = "local-site-id-1234567890";
- SiteManagerImpl store;
+ private SiteManagerImpl siteManager;
@Mock ReplicationPersistentStore persistentStore;
@Before
public void setUp() {
System.setProperty("org.codice.ddf.system.siteName", "testSite");
- store = new SiteManagerImpl(persistentStore);
+ siteManager = new SiteManagerImpl(persistentStore);
}
@Test
public void init() {
when(persistentStore.get(any(Class.class), anyString())).thenThrow(new NotFoundException());
- store.init();
+ siteManager.init();
ArgumentCaptor captor = ArgumentCaptor.forClass(ReplicationSiteImpl.class);
verify(persistentStore).save(captor.capture());
ReplicationSite site = captor.getValue();
@@ -69,7 +69,7 @@ public void initUpdateName() {
orig.setName("oldName");
orig.setUrl(SystemBaseUrl.EXTERNAL.getBaseUrl());
when(persistentStore.get(eq(ReplicationSiteImpl.class), anyString())).thenReturn(orig);
- store.init();
+ siteManager.init();
ArgumentCaptor captor = ArgumentCaptor.forClass(ReplicationSiteImpl.class);
verify(persistentStore).save(captor.capture());
ReplicationSiteImpl site = captor.getValue();
@@ -84,7 +84,7 @@ public void initUpdateURL() {
orig.setName(SystemInfo.getSiteName());
orig.setUrl("https://asdf:1234");
when(persistentStore.get(eq(ReplicationSiteImpl.class), anyString())).thenReturn(orig);
- store.init();
+ siteManager.init();
ArgumentCaptor captor = ArgumentCaptor.forClass(ReplicationSiteImpl.class);
verify(persistentStore).save(captor.capture());
ReplicationSiteImpl site = captor.getValue();
@@ -99,7 +99,7 @@ public void initNoOp() {
orig.setName(SystemInfo.getSiteName());
orig.setUrl(SystemBaseUrl.EXTERNAL.getBaseUrl());
when(persistentStore.get(eq(ReplicationSiteImpl.class), anyString())).thenReturn(orig);
- store.init();
+ siteManager.init();
verify(persistentStore, never()).save(any(ReplicationSiteImpl.class));
}
@@ -109,12 +109,12 @@ public void getSites() {
Stream siteStream = Stream.of(site);
when(persistentStore.objects(eq(ReplicationSiteImpl.class))).thenReturn(siteStream);
- assertThat(store.objects().anyMatch(site::equals), is(true));
+ assertThat(siteManager.objects().anyMatch(site::equals), is(true));
}
@Test
public void createSite() {
- ReplicationSite site = store.createSite("name", "url");
+ ReplicationSite site = siteManager.createSite("name", "url");
assertThat(site.getName(), is("name"));
assertThat(site.getUrl(), is("url"));
}
@@ -122,12 +122,23 @@ public void createSite() {
@Test(expected = IllegalArgumentException.class)
public void saveSiteBadSite() {
ReplicationSite site = mock(ReplicationSite.class);
- store.save(site);
+ siteManager.save(site);
}
@Test
public void deleteSite() {
- store.remove("id");
+ siteManager.remove("id");
verify(persistentStore).delete(eq(ReplicationSiteImpl.class), eq("id"));
}
+
+ @Test
+ public void testExistsNotFound() {
+ when(persistentStore.get(ReplicationSiteImpl.class, "id")).thenThrow(NotFoundException.class);
+ assertThat(siteManager.exists("id"), is(false));
+ }
+
+ @Test
+ public void testExistsConfigFound() {
+ assertThat(siteManager.exists("id"), is(true));
+ }
}
diff --git a/replication-api/src/main/java/org/codice/ditto/replication/api/ReplicationPersistentStore.java b/replication-api/src/main/java/org/codice/ditto/replication/api/ReplicationItemManager.java
similarity index 85%
rename from replication-api/src/main/java/org/codice/ditto/replication/api/ReplicationPersistentStore.java
rename to replication-api/src/main/java/org/codice/ditto/replication/api/ReplicationItemManager.java
index bd6195382..cab096760 100644
--- a/replication-api/src/main/java/org/codice/ditto/replication/api/ReplicationPersistentStore.java
+++ b/replication-api/src/main/java/org/codice/ditto/replication/api/ReplicationItemManager.java
@@ -17,9 +17,9 @@
import java.util.Optional;
import org.codice.ddf.persistence.PersistenceException;
-public interface ReplicationPersistentStore {
+public interface ReplicationItemManager {
- Optional getItem(String id, String source, String destination);
+ Optional getItem(String metacardId, String source, String destination);
List getItemsForConfig(String configId, int startIndex, int pageSize)
throws PersistenceException;
@@ -28,7 +28,7 @@ List getItemsForConfig(String configId, int startIndex, int pag
void deleteAllItems() throws PersistenceException;
- void deleteItem(String id, String source, String destination);
+ void deleteItem(String metacardId, String source, String destination);
List getFailureList(int maximumFailureCount, String source, String destination);
diff --git a/replication-api/src/main/java/org/codice/ditto/replication/api/ReplicatorHistory.java b/replication-api/src/main/java/org/codice/ditto/replication/api/ReplicatorHistory.java
index fa8d1edc3..0d2c20448 100644
--- a/replication-api/src/main/java/org/codice/ditto/replication/api/ReplicatorHistory.java
+++ b/replication-api/src/main/java/org/codice/ditto/replication/api/ReplicatorHistory.java
@@ -29,15 +29,17 @@ public interface ReplicatorHistory {
/**
* Get all replication events for a given replication configuration
*
- * @param replicatorid replication configuration id
+ * @param replicationConfigId replication configuration id
* @return List of associated replication events
*/
- List getReplicationEvents(String replicatorid);
+ List getReplicationEvents(String replicationConfigId);
/**
* Add a replication event to the history
*
* @param replicationStatus ReplicationConfig event to store
+ * @throws ReplicationPersistenceException if there is an error adding the {@link
+ * ReplicationStatus}
*/
void addReplicationEvent(ReplicationStatus replicationStatus);
@@ -45,6 +47,8 @@ public interface ReplicatorHistory {
* Remove a replication event from the history
*
* @param replicationStatus replication event to remove
+ * @throws ReplicationPersistenceException if there is an error deleting the {@link
+ * ReplicationStatus}
*/
void removeReplicationEvent(ReplicationStatus replicationStatus);
@@ -52,6 +56,7 @@ public interface ReplicatorHistory {
* Remove set of replication events from the history
*
* @param ids replication event ids
+ * @throws ReplicationPersistenceException if there is an error deleting 1 or more provided ids.
*/
void removeReplicationEvents(Set ids);
}
diff --git a/replication-api/src/main/java/org/codice/ditto/replication/api/data/ReplicatorConfig.java b/replication-api/src/main/java/org/codice/ditto/replication/api/data/ReplicatorConfig.java
index fc69302af..ec34113e0 100644
--- a/replication-api/src/main/java/org/codice/ditto/replication/api/data/ReplicatorConfig.java
+++ b/replication-api/src/main/java/org/codice/ditto/replication/api/data/ReplicatorConfig.java
@@ -136,4 +136,37 @@ public interface ReplicatorConfig extends Persistable {
* @param suspended the suspended state to give this config
*/
void setSuspended(boolean suspended);
+
+ /**
+ * See {@link #shouldDeleteData()}.
+ *
+ * @return whether or not this {@code ReplicatorConfig} should be considered as deleted
+ */
+ boolean isDeleted();
+
+ /**
+ * Marks this {@code ReplicatorConfig} as deleted.
+ *
+ * @param deleted whether or not this {@code ReplicatorConfig} should be considered as deleted
+ */
+ void setDeleted(boolean deleted);
+
+ /**
+ * Applies only when {@link #isDeleted()} returns {@code true}.
+ *
+ * Only data that has been replicated to this {@link ReplicationSite} from a remote {@link
+ * ReplicationSite} will be deleted.
+ *
+ * @return if {@code true}, delete the associated data replicated by this {@code
+ * ReplicatorConfig}, otherwise retain the data.
+ */
+ boolean shouldDeleteData();
+
+ /**
+ * See {@link #shouldDeleteData()}.
+ *
+ * @param deleteData {@code true} if this {@code ReplicatorConfig}'s data should be deleted,
+ * otherwise false
+ */
+ void setDeleteData(boolean deleteData);
}
diff --git a/replication-api/src/main/java/org/codice/ditto/replication/api/persistence/DataManager.java b/replication-api/src/main/java/org/codice/ditto/replication/api/persistence/DataManager.java
index 1cdb8d9dc..3aac8dc5b 100644
--- a/replication-api/src/main/java/org/codice/ditto/replication/api/persistence/DataManager.java
+++ b/replication-api/src/main/java/org/codice/ditto/replication/api/persistence/DataManager.java
@@ -21,8 +21,9 @@ public interface DataManager {
*
* @param id The object id
* @return the T object with the given id
- * @throws ReplicationPersistenceException if an error occurs while trying to retrieve the object
- * @throws NotFoundException if an object with the given id cannot be found
+ * @throws {@link org.codice.ditto.replication.api.ReplicationPersistenceException} if an error
+ * occurs while trying to retrieve the object
+ * @throws {@link javax.ws.rs.NotFoundException} if an object with the given id cannot be found
* @throws IllegalStateException if multiple objects were found with the given id
*/
T get(String id);
@@ -31,7 +32,8 @@ public interface DataManager {
* Gets all the currently saved T objects
*
* @return Stream of all T objects
- * @throws ReplicationPersistenceException if an error occurs while trying to retrieve the objects
+ * @throws {@link org.codice.ditto.replication.api.ReplicationPersistenceException} if an error
+ * occurs while trying to retrieve the objects
*/
Stream objects();
@@ -41,7 +43,8 @@ public interface DataManager {
* method included in this interface.
*
* @param object The T object to save or update
- * @throws ReplicationPersistenceException if an error occurs while trying to save the config
+ * @throws {@link org.codice.ditto.replication.api.ReplicationPersistenceException} if an error
+ * occurs while trying to save the config
* @throws IllegalArgumentException if the T implementation is not one that can be saved
*/
void save(T object);
@@ -50,8 +53,17 @@ public interface DataManager {
* Deletes a T object with the given id
*
* @param id The id of the object to be removed
- * @throws ReplicationPersistenceException if an error occurs while trying to delete the config
- * @throws NotFoundException if an object with the given id cannot be found
+ * @throws {@link org.codice.ditto.replication.api.ReplicationPersistenceException} if an error
+ * occurs while trying to delete the config
+ * @throws {@link javax.ws.rs.NotFoundException} if an object with the given id cannot be found
*/
void remove(String id);
+
+ /**
+ * @param id unique id of the {@link Persistable}
+ * @return {@code true} if the {@link Persistable} exists, otherwise {@code false}.
+ * @throws {@link org.codice.ditto.replication.api.ReplicationPersistenceException} if there is an
+ * error accessing storage
+ */
+ boolean exists(String id);
}
diff --git a/replication-api/src/main/java/org/codice/ditto/replication/api/persistence/ReplicatorConfigManager.java b/replication-api/src/main/java/org/codice/ditto/replication/api/persistence/ReplicatorConfigManager.java
index 290f02fd9..5790bdbb0 100644
--- a/replication-api/src/main/java/org/codice/ditto/replication/api/persistence/ReplicatorConfigManager.java
+++ b/replication-api/src/main/java/org/codice/ditto/replication/api/persistence/ReplicatorConfigManager.java
@@ -15,12 +15,5 @@
import org.codice.ditto.replication.api.data.ReplicatorConfig;
-/** Performs CRUD operations for {@link ReplicatorConfig}. */
-public interface ReplicatorConfigManager extends DataManager {
-
- /**
- * @param configId unique id of {@link ReplicatorConfig}
- * @return {@code true} if the config exists, otherwise {@code false}.
- */
- boolean configExists(String configId);
-}
+/** Performs CRUD operations for {@link ReplicatorConfig}s. */
+public interface ReplicatorConfigManager extends DataManager {}
diff --git a/replication-commands/src/main/java/org/codice/ditto/replication/commands/ConfigDeleteCommand.java b/replication-commands/src/main/java/org/codice/ditto/replication/commands/ConfigDeleteCommand.java
index d6782dbcd..e559c3dce 100644
--- a/replication-commands/src/main/java/org/codice/ditto/replication/commands/ConfigDeleteCommand.java
+++ b/replication-commands/src/main/java/org/codice/ditto/replication/commands/ConfigDeleteCommand.java
@@ -44,7 +44,7 @@
import org.codice.ddf.persistence.PersistenceException;
import org.codice.ditto.replication.api.ReplicationException;
import org.codice.ditto.replication.api.ReplicationItem;
-import org.codice.ditto.replication.api.ReplicationPersistentStore;
+import org.codice.ditto.replication.api.ReplicationItemManager;
import org.codice.ditto.replication.api.data.ReplicatorConfig;
import org.codice.ditto.replication.api.mcard.Replication;
import org.codice.ditto.replication.api.mcard.ReplicationConfig;
@@ -106,7 +106,7 @@ public class ConfigDeleteCommand extends SubjectCommands {
@Reference CatalogFramework framework;
- @Reference ReplicationPersistentStore store;
+ @Reference ReplicationItemManager store;
@Reference FilterBuilder builder;
@@ -281,7 +281,7 @@ private void deleteBatch(String[] idsToDelete) throws SourceUnavailableException
try {
framework.delete(new DeleteRequestImpl(id));
} catch (IngestException e) {
- LOGGER.debug("Failed to delete metacard with id:%s because of exception {}", id, e);
+ LOGGER.debug("Failed to delete metacard with id: {}", id, e);
}
}
}
diff --git a/ui/package.json b/ui/package.json
index f8730a8ef..1091fc921 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -30,7 +30,7 @@
"graphql-tag": "2.10.0",
"immutable": "3.8.2",
"moment": "2.24.0",
- "notistack": "0.5.0",
+ "notistack": "0.6.1",
"prop-types": "15.6.2",
"react": "16.8.3",
"react-apollo": "2.3.3",
@@ -74,10 +74,10 @@
],
"coverageThreshold": {
"global": {
- "statements": 7,
+ "statements": 6,
"branches": 6,
- "lines": 7,
- "functions": 2
+ "lines": 6,
+ "functions": 1
}
},
"collectCoverage": true,
diff --git a/ui/src/main/webapp/app.js b/ui/src/main/webapp/app.js
index 7b227bf32..ed8eae730 100644
--- a/ui/src/main/webapp/app.js
+++ b/ui/src/main/webapp/app.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import React from 'react'
import Home from './components/Home'
import Navbar from './components/Navbar'
diff --git a/ui/src/main/webapp/client.js b/ui/src/main/webapp/client.js
index 628575fc8..d011a59c7 100644
--- a/ui/src/main/webapp/client.js
+++ b/ui/src/main/webapp/client.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import ApolloClient from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { createHttpLink } from 'apollo-link-http'
diff --git a/ui/src/main/webapp/components/Home.js b/ui/src/main/webapp/components/Home.js
index 01ef05fe3..529c3ff96 100644
--- a/ui/src/main/webapp/components/Home.js
+++ b/ui/src/main/webapp/components/Home.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import React from 'react'
import ReplicationsContainer from './replications/ReplicationsContainer'
diff --git a/ui/src/main/webapp/components/Navbar.js b/ui/src/main/webapp/components/Navbar.js
index 9c83054f4..c3f6d22e8 100644
--- a/ui/src/main/webapp/components/Navbar.js
+++ b/ui/src/main/webapp/components/Navbar.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
/* global localStorage */
import React from 'react'
import AppBar from '@material-ui/core/AppBar'
@@ -30,7 +43,7 @@ const HelpDialog = props => {
return (
- {'Welcome to the Project Charleston BETA'}
+ {'Welcome to the Project Charleston'}
@@ -90,7 +103,7 @@ class Navbar extends React.Component {
>
- Project Charleston BETA
+ Project Charleston
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.
+ *
+ * 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
+ * .
+ */
import React from 'react'
import { CircularProgress } from '@material-ui/core'
diff --git a/ui/src/main/webapp/components/common/Confirmable.js b/ui/src/main/webapp/components/common/Confirmable.js
new file mode 100644
index 000000000..a39930cda
--- /dev/null
+++ b/ui/src/main/webapp/components/common/Confirmable.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
+import React from 'react'
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ DialogContentText,
+} from '@material-ui/core'
+
+function Confirmable(props) {
+ const {
+ onConfirm,
+ children,
+ message,
+ subMessage,
+ Button: Trigger,
+ onClose,
+ } = props
+ const [open, setOpen] = React.useState(false)
+
+ return (
+ <>
+ {
+ onClose()
+ setOpen(false)
+ }}
+ fullWidth
+ >
+ {message}
+
+ {subMessage}
+ {children}
+
+
+ {
+ onClose()
+ setOpen(false)
+ }}
+ color='primary'
+ >
+ Cancel
+
+ {
+ onConfirm()
+ setOpen(false)
+ }}
+ color='primary'
+ >
+ Confirm
+
+
+
+ setOpen(true)} />
+ >
+ )
+}
+
+export default Confirmable
diff --git a/ui/src/main/webapp/components/common/ServerError.js b/ui/src/main/webapp/components/common/ServerError.js
index 741015f12..334d3af25 100644
--- a/ui/src/main/webapp/components/common/ServerError.js
+++ b/ui/src/main/webapp/components/common/ServerError.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import React from 'react'
import { Grid, Typography } from '@material-ui/core'
diff --git a/ui/src/main/webapp/components/replications/ActionsMenu.js b/ui/src/main/webapp/components/replications/ActionsMenu.js
index 18fbc20d5..51db5b473 100644
--- a/ui/src/main/webapp/components/replications/ActionsMenu.js
+++ b/ui/src/main/webapp/components/replications/ActionsMenu.js
@@ -1,5 +1,27 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import React from 'react'
-import { Menu, MenuItem, Typography } from '@material-ui/core'
+import {
+ Menu,
+ MenuItem,
+ Typography,
+ FormGroup,
+ FormControlLabel,
+ Checkbox,
+ Tooltip,
+} from '@material-ui/core'
+import HelpIcon from '@material-ui/icons/Help'
import {
deleteReplication,
suspendReplication,
@@ -8,50 +30,98 @@ import {
import { allReplications } from './gql/queries'
import { Mutation } from 'react-apollo'
import Replications from './replications'
-import { withSnackbar } from 'notistack'
+import { withSnackbar, useSnackbar } from 'notistack'
+import Confirmable from '../common/Confirmable'
-const DeleteReplication = withSnackbar(props => {
- const { id, onClose, name, enqueueSnackbar } = props
+const DeleteReplication = withSnackbar(
+ class extends React.Component {
+ state = {
+ deleteData: false,
+ }
- return (
-
- {deleteReplication => (
- {
- deleteReplication({
- variables: {
- id: id,
- },
- update: store => {
- const data = store.readQuery({
- query: allReplications,
- })
+ handleCheck = name => event => {
+ this.setState({ [name]: event.target.checked })
+ }
- data.replication.replications = data.replication.replications.filter(
- r => r.id !== id
- )
- store.writeQuery({
- query: allReplications,
- data,
- })
+ render() {
+ const { id, onClose, name, enqueueSnackbar } = this.props
- enqueueSnackbar('Deleted ' + name + '.', { variant: 'success' })
- },
- })
- onClose()
- }}
- >
- Delete
-
- )}
-
- )
-})
+ return (
+
+ {deleteReplication => (
+ {
+ deleteReplication({
+ variables: {
+ id: id,
+ deleteData: this.state.deleteData,
+ },
+ update: store => {
+ const data = store.readQuery({
+ query: allReplications,
+ })
+
+ data.replication.replications = data.replication.replications.filter(
+ r => r.id !== id
+ )
+ store.writeQuery({
+ query: allReplications,
+ data,
+ })
+
+ enqueueSnackbar('Deleted ' + name + '.', {
+ variant: 'success',
+ })
+ },
+ })
+ onClose()
+ }}
+ message={`Are you sure you want to delete ${name}?`}
+ subMessage={
+ 'All historical statistics associated with this Replication will be removed in addition to the Replication.'
+ }
+ onClose={onClose}
+ Button={props => {
+ const { onClick } = props
+ return (
+
+ Delete
+
+ )
+ }}
+ >
+
+
+
+ }
+ label='Delete Data?'
+ />
+
+
+
+
+
+
+ )}
+
+ )
+ }
+ }
+)
DeleteReplication.displayName = 'DeleteReplication'
-const SuspendReplication = withSnackbar(props => {
- const { id, suspend, key, label, onClose, enqueueSnackbar, name } = props
+const SuspendReplication = props => {
+ const { id, suspend, key, label, onClose, name } = props
+ const { enqueueSnackbar } = useSnackbar()
return (
@@ -98,11 +168,11 @@ const SuspendReplication = withSnackbar(props => {
)}
)
-})
-SuspendReplication.displayName = 'SuspendReplication'
+}
-const CancelReplication = withSnackbar(props => {
- const { id, onClose, name, enqueueSnackbar } = props
+const CancelReplication = props => {
+ const { id, onClose, name } = props
+ const { enqueueSnackbar } = useSnackbar()
return (
@@ -149,8 +219,7 @@ const CancelReplication = withSnackbar(props => {
)}
)
-})
-CancelReplication.displayName = 'CancelReplication'
+}
const ActionsMenu = function(props) {
const { menuId, anchorEl = null, onClose, replication } = props
diff --git a/ui/src/main/webapp/components/replications/AddReplication.js b/ui/src/main/webapp/components/replications/AddReplication.js
index d9146f9f2..396ac5b36 100644
--- a/ui/src/main/webapp/components/replications/AddReplication.js
+++ b/ui/src/main/webapp/components/replications/AddReplication.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import React from 'react'
import {
MenuItem,
@@ -83,6 +96,7 @@ const defaultFormState = {
biDirectional: false,
filterErrorText: '',
nameErrorText: '',
+ disableSave: false,
}
class AddReplication extends React.Component {
@@ -141,6 +155,18 @@ class AddReplication extends React.Component {
})
}
+ disableSaveButton() {
+ this.setState({
+ disableSave: true,
+ })
+ }
+
+ enableSaveButton() {
+ this.setState({
+ disableSave: false,
+ })
+ }
+
render() {
const { Button: AddButton, classes } = this.props
const {
@@ -152,6 +178,7 @@ class AddReplication extends React.Component {
biDirectional,
filterErrorText,
nameErrorText,
+ disableSave = false,
} = this.state
return (
@@ -262,6 +289,7 @@ class AddReplication extends React.Component {
} else if (e.message === 'DUPLICATE_CONFIGURATION') {
this.handleInvalidName()
}
+ this.enableSaveButton()
})
}}
onCompleted={() => {
@@ -271,9 +299,13 @@ class AddReplication extends React.Component {
{(createReplication, { loading }) => (
{
+ this.disableSaveButton()
createReplication({
variables: {
name: name,
diff --git a/ui/src/main/webapp/components/replications/ReplicationsContainer.js b/ui/src/main/webapp/components/replications/ReplicationsContainer.js
index c6505837a..42f8bdf42 100644
--- a/ui/src/main/webapp/components/replications/ReplicationsContainer.js
+++ b/ui/src/main/webapp/components/replications/ReplicationsContainer.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import React, { Fragment } from 'react'
import { Query } from 'react-apollo'
import { allReplications } from './gql/queries'
diff --git a/ui/src/main/webapp/components/replications/ReplicationsTable.js b/ui/src/main/webapp/components/replications/ReplicationsTable.js
index 247a303ea..b9cbc7921 100644
--- a/ui/src/main/webapp/components/replications/ReplicationsTable.js
+++ b/ui/src/main/webapp/components/replications/ReplicationsTable.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import React from 'react'
import PropTypes from 'prop-types'
import {
diff --git a/ui/src/main/webapp/components/replications/__tests__/actionsMenu.spec.js b/ui/src/main/webapp/components/replications/__tests__/actionsMenu.spec.js
index 6edd1a577..37488c985 100644
--- a/ui/src/main/webapp/components/replications/__tests__/actionsMenu.spec.js
+++ b/ui/src/main/webapp/components/replications/__tests__/actionsMenu.spec.js
@@ -1,3 +1,17 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
+
/*global test, shallow, expect */
import React from 'react'
import ActionsMenu from '../ActionsMenu'
diff --git a/ui/src/main/webapp/components/replications/gql/mutations.js b/ui/src/main/webapp/components/replications/gql/mutations.js
index 389960dc0..bd060d6a6 100644
--- a/ui/src/main/webapp/components/replications/gql/mutations.js
+++ b/ui/src/main/webapp/components/replications/gql/mutations.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import gql from 'graphql-tag'
export const addReplication = gql`
@@ -56,7 +69,7 @@ export const cancelReplication = gql`
`
export const deleteReplication = gql`
- mutation deleteReplication($id: Pid!) {
- deleteReplication(id: $id)
+ mutation deleteReplication($id: Pid!, $deleteData: Boolean) {
+ deleteReplication(id: $id, deleteData: $deleteData)
}
`
diff --git a/ui/src/main/webapp/components/replications/gql/queries.js b/ui/src/main/webapp/components/replications/gql/queries.js
index af23f9af7..4151e6530 100644
--- a/ui/src/main/webapp/components/replications/gql/queries.js
+++ b/ui/src/main/webapp/components/replications/gql/queries.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import gql from 'graphql-tag'
export const allReplications = gql`
diff --git a/ui/src/main/webapp/components/replications/replications.js b/ui/src/main/webapp/components/replications/replications.js
index c9729f734..60e1d0b78 100644
--- a/ui/src/main/webapp/components/replications/replications.js
+++ b/ui/src/main/webapp/components/replications/replications.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
const Status = {
SUCCESS: 'SUCCESS',
PENDING: 'PENDING',
diff --git a/ui/src/main/webapp/components/sites/AddSite.js b/ui/src/main/webapp/components/sites/AddSite.js
index 89e820af5..6d9425dfe 100644
--- a/ui/src/main/webapp/components/sites/AddSite.js
+++ b/ui/src/main/webapp/components/sites/AddSite.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import React from 'react'
import { Mutation } from 'react-apollo'
import Button from '@material-ui/core/Button'
diff --git a/ui/src/main/webapp/components/sites/Site.js b/ui/src/main/webapp/components/sites/Site.js
index d1eaed081..26cc510e8 100644
--- a/ui/src/main/webapp/components/sites/Site.js
+++ b/ui/src/main/webapp/components/sites/Site.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import React from 'react'
import CardContent from '@material-ui/core/CardContent'
import {
diff --git a/ui/src/main/webapp/components/sites/Sites.js b/ui/src/main/webapp/components/sites/Sites.js
index b7a907751..8f99f35ed 100644
--- a/ui/src/main/webapp/components/sites/Sites.js
+++ b/ui/src/main/webapp/components/sites/Sites.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import React, { Fragment } from 'react'
import Site from './Site'
import PropTypes from 'prop-types'
diff --git a/ui/src/main/webapp/components/sites/SitesContainer.js b/ui/src/main/webapp/components/sites/SitesContainer.js
index cf34fe607..283b0f776 100644
--- a/ui/src/main/webapp/components/sites/SitesContainer.js
+++ b/ui/src/main/webapp/components/sites/SitesContainer.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import React from 'react'
import { allSites } from './gql/queries'
import { Query } from 'react-apollo'
@@ -30,7 +43,7 @@ function SitesContainer(props) {
const { classes } = props
return (
-
+
{({ data, loading, error }) => {
if (loading) return
if (error) return
diff --git a/ui/src/main/webapp/components/sites/__tests__/site.spec.js b/ui/src/main/webapp/components/sites/__tests__/site.spec.js
index cbd62c87b..3eb848e06 100644
--- a/ui/src/main/webapp/components/sites/__tests__/site.spec.js
+++ b/ui/src/main/webapp/components/sites/__tests__/site.spec.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
/*global test, shallow, expect */
import React from 'react'
import Site from '../Site'
diff --git a/ui/src/main/webapp/components/sites/gql/mutations.js b/ui/src/main/webapp/components/sites/gql/mutations.js
index d9e37d2ca..ca7d2e24c 100644
--- a/ui/src/main/webapp/components/sites/gql/mutations.js
+++ b/ui/src/main/webapp/components/sites/gql/mutations.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import gql from 'graphql-tag'
export const deleteSite = gql`
diff --git a/ui/src/main/webapp/components/sites/gql/queries.js b/ui/src/main/webapp/components/sites/gql/queries.js
index 455ef2677..9f4ac3671 100644
--- a/ui/src/main/webapp/components/sites/gql/queries.js
+++ b/ui/src/main/webapp/components/sites/gql/queries.js
@@ -1,3 +1,16 @@
+/**
+ * Copyright (c) Connexta
+ *
+ * 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.
+ *
+ *
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
+ * .
+ */
import gql from 'graphql-tag'
export const allSites = gql`
diff --git a/ui/src/main/webapp/index.html b/ui/src/main/webapp/index.html
index a2548b9b2..ba082e640 100644
--- a/ui/src/main/webapp/index.html
+++ b/ui/src/main/webapp/index.html
@@ -1,4 +1,19 @@
+