From f0dab14bad7229b51c9553a1f6988c7143366fcf Mon Sep 17 00:00:00 2001 From: kaloyane <kaloyane> Date: Tue, 17 May 2011 15:47:30 +0000 Subject: [PATCH] [LMS-2104]: initial implementation of deletion for virtual data sets SVN: 21347 --- .../systemsx/cisd/common/collections/DAG.java | 109 ++++++++++++++++++ .../cisd/common/collections/DAGTest.java | 80 +++++++++++++ .../server/business/bo/DataSetTable.java | 83 ++++++++++--- .../shared/basic/dto/ExternalData.java | 1 - .../openbis/generic/shared/dto/DataPE.java | 28 +++++ .../generic/shared/dto/ExternalDataPE.java | 20 ++++ .../shared/translator/DataSetTranslator.java | 1 - .../translator/ExternalDataTranslator.java | 1 - .../server/business/bo/DataSetTableTest.java | 1 + 9 files changed, 307 insertions(+), 17 deletions(-) create mode 100644 common/source/java/ch/systemsx/cisd/common/collections/DAG.java create mode 100644 common/sourceTest/java/ch/systemsx/cisd/common/collections/DAGTest.java diff --git a/common/source/java/ch/systemsx/cisd/common/collections/DAG.java b/common/source/java/ch/systemsx/cisd/common/collections/DAG.java new file mode 100644 index 00000000000..5f42de715f4 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/collections/DAG.java @@ -0,0 +1,109 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.common.collections; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +import ch.systemsx.cisd.common.exceptions.UserFailureException; + +/** + * Directed acyclic graph. + * + * @author Kaloyan Enimanev + */ +public abstract class DAG<T> +{ + protected Collection<T> nodes; + + /** + * constructs a {@link DAG} for a given set of nodes. + * + * @param nodes + */ + public DAG(Collection<T> nodes) + { + this.nodes = Collections.unmodifiableCollection(nodes); + } + + /** + * Return the graph's nodes. + */ + public Collection<T> getNodes() + { + return nodes; + } + + /** + * For a given node returns the list of its successors. + */ + public abstract Collection<T> getSuccessors(T node); + + /** + * @return a topological sort of the graph's nodes. + */ + public List<T> sortTopologically() + { + HashSet<T> nodesCopy = new HashSet<T>(nodes); + ArrayList<T> sorted = new ArrayList<T>(); + while (nodesCopy.isEmpty() == false) + { + T nextNode = getNextNodeForTopologicalSort(nodesCopy); + if (nextNode == null) + { + throw new UserFailureException( + "Graph cycle detected. Cannot execute topological sort."); + } + sorted.add(nextNode); + nodesCopy.remove(nextNode); + } + + return sorted; + } + + /** + * helper method for topological sorting. + */ + private T getNextNodeForTopologicalSort(Collection<T> nodesCollection) + { + for (T node : nodesCollection) + { + Collection<T> successors = getSuccessors(node); + boolean hasDependencies = false; + if (successors != null) + { + for (T successor : successors) + { + if (nodesCollection.contains(successor)) + { + hasDependencies = true; + } + } + + } + if (hasDependencies == false) + { + return node; + } + } + return null; + } + +} diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/collections/DAGTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/collections/DAGTest.java new file mode 100644 index 00000000000..87682931ff6 --- /dev/null +++ b/common/sourceTest/java/ch/systemsx/cisd/common/collections/DAGTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.common.collections; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.testng.AssertJUnit; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.common.exceptions.UserFailureException; + +/** + * @author Kaloyan Enimanev + */ +public class DAGTest extends AssertJUnit +{ + + @Test + public void testTopologicalSort() + { + HashMap<String, List<String>> adjacencyMap = new HashMap<String, List<String>>(); + + adjacencyMap.put("A", new ArrayList<String>()); + adjacencyMap.put("B", Arrays.asList("A")); + adjacencyMap.put("C", Arrays.asList("A", "B")); + adjacencyMap.put("D", Arrays.asList("C", "B", "A")); + adjacencyMap.put("E", Arrays.asList("C", "D")); + + Collection<String> sorted = sortTopologically(adjacencyMap); + + assertEquals(Arrays.asList("A", "B", "C", "D", "E"), sorted); + + } + + @Test(expectedExceptions = UserFailureException.class) + public void testCyclicGraph() + { + HashMap<String, List<String>> adjacencyMap = new HashMap<String, List<String>>(); + + adjacencyMap.put("A", Arrays.asList("B")); + adjacencyMap.put("B", Arrays.asList("C")); + adjacencyMap.put("C", Arrays.asList("A")); + + sortTopologically(adjacencyMap); + } + + private Collection<String> sortTopologically(final Map<String, List<String>> adjacencyMap) + { + DAG<String> dag = new DAG<String>(adjacencyMap.keySet()) + { + @Override + public Collection<String> getSuccessors(String node) + { + return adjacencyMap.get(node); + } + }; + + return dag.sortTopologically(); + } + +} diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTable.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTable.java index f0faf24754a..66d29d7b487 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTable.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTable.java @@ -18,6 +18,7 @@ package ch.systemsx.cisd.openbis.generic.server.business.bo; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -36,6 +37,7 @@ import org.springframework.dao.DataIntegrityViolationException; import ch.rinn.restrictions.Private; import ch.systemsx.cisd.common.collections.CollectionUtils; +import ch.systemsx.cisd.common.collections.DAG; import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; import ch.systemsx.cisd.common.exceptions.UserFailureException; import ch.systemsx.cisd.common.logging.LogCategory; @@ -240,19 +242,22 @@ public final class DataSetTable extends AbstractDataSetBusinessObject implements public void deleteLoadedDataSets(String reason) { + dataSets = loadContainedDataSetsRecursively(); assertDatasetsAreDeletable(dataSets); - Map<DataStorePE, List<ExternalDataPE>> allToBeDeleted = groupExternalDataByDataStores(); + dataSets = sortTopologicallyByContainerRelationship(dataSets); + + Map<DataStorePE, List<DataPE>> allToBeDeleted = groupDataSetsByDataStores(); Map<DataStorePE, List<ExternalDataPE>> availableDatasets = filterAvailableDatasets(allToBeDeleted); assertDataSetsAreKnown(availableDatasets); - for (Map.Entry<DataStorePE, List<ExternalDataPE>> entry : allToBeDeleted.entrySet()) + for (Map.Entry<DataStorePE, List<DataPE>> entry : allToBeDeleted.entrySet()) { DataStorePE dataStore = entry.getKey(); - List<ExternalDataPE> allDataSets = entry.getValue(); + List<DataPE> allDataSets = entry.getValue(); // delete locally from DB - for (ExternalDataPE dataSet : allDataSets) + for (DataPE dataSet : allDataSets) { deleteDataSetLocally(dataSet, reason); } @@ -262,19 +267,63 @@ public final class DataSetTable extends AbstractDataSetBusinessObject implements } } + private List<DataPE> loadContainedDataSetsRecursively() + { + Map<Long, DataPE> loadCache = new HashMap<Long, DataPE>(); + for (DataPE dataSet : dataSets) + { + loadContainedDataSetsInternal(dataSet, loadCache); + } + + return new ArrayList<DataPE>(loadCache.values()); + } + + private void loadContainedDataSetsInternal(DataPE dataSet, Map<Long /* id */, DataPE> loaded) + { + Long id = dataSet.getId(); + if (false == loaded.containsKey(id)) + { + loaded.put(id, dataSet); + if (dataSet.getContainedDataSets() != null) + { + for (DataPE containedDataSet : dataSet.getContainedDataSets()) + { + loadContainedDataSetsInternal(containedDataSet, loaded); + } + } + + } + } + + private List<DataPE> sortTopologicallyByContainerRelationship(List<DataPE> datasets) + { + DAG<DataPE> dag = new DAG<DataPE>(datasets) + { + @Override + public Collection<DataPE> getSuccessors(DataPE node) + { + return node.getContainedDataSets(); + } + }; + + return dag.sortTopologically(); + } + private Map<DataStorePE, List<ExternalDataPE>> filterAvailableDatasets( - Map<DataStorePE, List<ExternalDataPE>> map) + Map<DataStorePE, List<DataPE>> map) { Map<DataStorePE, List<ExternalDataPE>> result = new HashMap<DataStorePE, List<ExternalDataPE>>(); - for (Map.Entry<DataStorePE, List<ExternalDataPE>> entry : map.entrySet()) + for (Map.Entry<DataStorePE, List<DataPE>> entry : map.entrySet()) { ArrayList<ExternalDataPE> available = new ArrayList<ExternalDataPE>(); - for (ExternalDataPE data : entry.getValue()) + for (DataPE data : entry.getValue()) { - if (data.getStatus().isAvailable()) + ExternalDataPE externalData = data.tryAsExternalData(); + if (externalData != null && externalData.isAvailable()) { - available.add(data); + + available.add(externalData); } } result.put(entry.getKey(), available); @@ -282,7 +331,7 @@ public final class DataSetTable extends AbstractDataSetBusinessObject implements return result; } - private void deleteDataSetLocally(ExternalDataPE dataSet, String reason) + private void deleteDataSetLocally(DataPE dataSet, String reason) throws UserFailureException { try @@ -301,7 +350,7 @@ public final class DataSetTable extends AbstractDataSetBusinessObject implements } } - public static EventPE createDeletionEvent(ExternalDataPE dataSet, PersonPE registrator, + public static EventPE createDeletionEvent(DataPE dataSet, PersonPE registrator, String reason) { EventPE event = new EventPE(); @@ -315,9 +364,16 @@ public final class DataSetTable extends AbstractDataSetBusinessObject implements return event; } - private static String getDeletionDescription(ExternalDataPE dataSet) + private static String getDeletionDescription(DataPE dataSet) { - return dataSet.getLocation(); + if (dataSet.isExternalData()) + { + return dataSet.tryAsExternalData().getLocation(); + + } else + { + return StringUtils.EMPTY; + } } public String uploadLoadedDataSetsToCIFEX(DataSetUploadContext uploadContext) @@ -451,7 +507,6 @@ public final class DataSetTable extends AbstractDataSetBusinessObject implements /** groups all data sets (both virtual and non-virtual) by data stores */ // TODO 2011-05-17, Piotr Buczek: use this instead of groupExternalDataByDataStores in places // where we want to support virtual data sets - @SuppressWarnings("unused") private Map<DataStorePE, List<DataPE>> groupDataSetsByDataStores() { Map<DataStorePE, List<DataPE>> map = new LinkedHashMap<DataStorePE, List<DataPE>>(); diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/ExternalData.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/ExternalData.java index b2ce1cf8077..92a20655689 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/ExternalData.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/ExternalData.java @@ -62,7 +62,6 @@ public class ExternalData extends CodeWithRegistration<ExternalData> implements private Collection<ExternalData> parents; - // TODO KE: 2011-05-06 Implement a logic in ContainerDataSets to calculate the size private Long size; private Sample sample; diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataPE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataPE.java index 26bb5f19683..ef8e31f3cc5 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataPE.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataPE.java @@ -642,4 +642,32 @@ public class DataPE extends AbstractIdAndCodeHolder<DataPE> implements return (this instanceof ExternalDataPE) ? (ExternalDataPE) this : null; } + /** + * return true if the data set if available in the data store. + */ + @Transient + public boolean isAvailable() + { + return false; + } + + /** + * return true if the data set can be deleted. + */ + @Transient + public boolean isDeletable() + { + if (containedDataSets != null) + { + for (DataPE containedDataSet : containedDataSets) + { + if (containedDataSet.isDeletable() == false) + { + return false; + } + } + + } + return true; + } } \ No newline at end of file diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExternalDataPE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExternalDataPE.java index 3622089df33..f26e7454381 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExternalDataPE.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExternalDataPE.java @@ -234,4 +234,24 @@ public final class ExternalDataPE extends DataPE this.speedHint = speedHint == null ? Constants.DEFAULT_SPEED_HINT : speedHint; } + /** + * return true if the data set if available in the data store. + */ + @Transient + @Override + public boolean isAvailable() + { + return getStatus().isAvailable(); + } + + /** + * return true if the data set can be deleted. + */ + @Transient + @Override + public boolean isDeletable() + { + return getStatus().isDeletable(); + } + } diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/translator/DataSetTranslator.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/translator/DataSetTranslator.java index 6b8ed36280d..6e0e1cb9179 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/translator/DataSetTranslator.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/translator/DataSetTranslator.java @@ -67,7 +67,6 @@ public class DataSetTranslator DataSet dataSet = data.tryGetAsDataSet(); if (dataSet != null) { - // TODO KE: 2011-05-06 make sure this other classes handle NULL correctly description.setDataSetLocation(dataSet.getLocation()); description.setSpeedHint(dataSet.getSpeedHint()); } diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/translator/ExternalDataTranslator.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/translator/ExternalDataTranslator.java index 5f25ef920e7..c1a589d54ba 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/translator/ExternalDataTranslator.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/translator/ExternalDataTranslator.java @@ -67,7 +67,6 @@ public class ExternalDataTranslator DataSet dataSet = data.tryGetAsDataSet(); if (dataSet != null) { - // TODO KE: 2011-05-06 make sure this other classes handle NULL correctly description.setDataSetLocation(dataSet.getLocation()); description.setSpeedHint(dataSet.getSpeedHint()); } diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTableTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTableTest.java index 51386c46939..b640b92556b 100644 --- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTableTest.java +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTableTest.java @@ -481,6 +481,7 @@ public final class DataSetTableTest extends AbstractBOTest private ExternalDataPE createDataSet(String code, DataStorePE dataStore) { ExternalDataPE data = new ExternalDataPE(); + data.setId((long) code.hashCode()); data.setCode(code); data.setDataStore(dataStore); data.setLocation("here/" + code); -- GitLab