diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/EntityDAG.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/EntityDAG.java new file mode 100644 index 0000000000000000000000000000000000000000..bca1db398042f88b8064fb22c5d87bb68f256dea --- /dev/null +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/EntityDAG.java @@ -0,0 +1,108 @@ +/* + * 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.openbis.generic.server; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + +import ch.systemsx.cisd.common.collections.DAG; + +/** + * Represents a DAG of registration dependencies between entities. The subclasses of this class + * should implement getCode(T), and getDependentEntitiesCodes(T). + * + * @author Chandrasekhar Ramakrishnan + */ +public abstract class EntityDAG<T> +{ + private final List<? extends T> dataSetRegistrations; + + // For simplicity, we use the data set codes to construct the graph, so we need a map + // from + // codes to data sets + HashMap<String, T> codeToDataMap = new HashMap<String, T>(); + + HashMap<String, Collection<String>> dependencyGraph = new HashMap<String, Collection<String>>(); + + /** + * Create a DAG from the registrations. + * + * @param dataSetRegistrations + */ + public EntityDAG(List<? extends T> dataSetRegistrations) + { + this.dataSetRegistrations = dataSetRegistrations; + constructGraph(); + } + + protected abstract String getCode(T entity); + + protected abstract Collection<String> getDependentEntitiesCodes(T entity); + + /** + * @return The registrations ordered topologically such that each registrations comes after the + * ones it depends on. + */ + public List<T> getOrderedRegistrations() + { + DAG<String, Collection<String>> dag = new DAG<String, Collection<String>>(dependencyGraph); + List<String> sortedCodes = dag.sortTopologically(); + ArrayList<T> sortedData = new ArrayList<T>(); + for (String code : sortedCodes) + { + T data = codeToDataMap.get(code); + // Some of the dependencies may be to *existing* data -- we don't care about those here. + if (null != data) + { + sortedData.add(data); + } + } + + return sortedData; + } + + /** + * Create a dependency graph that can be used for topological sorting. + */ + private void constructGraph() + { + for (T dataSet : dataSetRegistrations) + { + String dataSetCode = getCode(dataSet); + codeToDataMap.put(dataSetCode, dataSet); + + assertNoDependantsForThisDataset(dataSetCode); + + dependencyGraph.put(dataSetCode, getDependentEntitiesCodes(dataSet)); + } + } + + private Collection<String> assertNoDependantsForThisDataset(String dataSetCode) + { + + Collection<String> dependents = dependencyGraph.get(dataSetCode); + if (dependents != null) + { + throw new IllegalStateException("Forbidden to add dataset twice!"); + } + + return dependents; + } + +} diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/NewExternalDataDAG.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/NewExternalDataDAG.java index 82082591ddebcba1d6cc245e6bb987e2bb98324a..592293db8ab9736bd0a7ab608cd6531885fc5603 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/NewExternalDataDAG.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/NewExternalDataDAG.java @@ -17,12 +17,9 @@ package ch.systemsx.cisd.openbis.generic.server; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collection; import java.util.List; -import ch.systemsx.cisd.common.collections.DAG; -import ch.systemsx.cisd.common.conversation.context.ServiceConversationsThreadContext; -import ch.systemsx.cisd.common.conversation.progress.IServiceConversationProgressListener; import ch.systemsx.cisd.openbis.generic.shared.dto.NewContainerDataSet; import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData; @@ -31,94 +28,35 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData; * * @author Chandrasekhar Ramakrishnan */ -public class NewExternalDataDAG +public class NewExternalDataDAG extends EntityDAG<NewExternalData> { - private final List<? extends NewExternalData> dataSetRegistrations; - - // For simplicity, we use the data set codes to construct the graph, so we need a map from - // codes to data sets - HashMap<String, NewExternalData> codeToDataMap = new HashMap<String, NewExternalData>(); - - HashMap<String, ArrayList<String>> dependencyGraph = new HashMap<String, ArrayList<String>>(); - - /** - * Create a DAG from the registrations. - * - * @param dataSetRegistrations - */ public NewExternalDataDAG(List<? extends NewExternalData> dataSetRegistrations) { - super(); - this.dataSetRegistrations = dataSetRegistrations; - constructGraph(); + super(dataSetRegistrations); } - /** - * @return The registrations ordered topologically such that each registrations comes after the - * ones it depends on. - */ - public List<? extends NewExternalData> getOrderedRegistrations() + @Override + protected String getCode(NewExternalData entity) { - DAG<String, ArrayList<String>> dag = new DAG<String, ArrayList<String>>(dependencyGraph); - List<String> sortedCodes = dag.sortTopologically(); - ArrayList<NewExternalData> sortedData = new ArrayList<NewExternalData>(); - for (String code : sortedCodes) - { - NewExternalData data = codeToDataMap.get(code); - // Some of the dependencies may be to *existing* data -- we don't care about those here. - if (null != data) - { - sortedData.add(data); - } - } - - return sortedData; + return entity.getCode(); } - /** - * Create a dependency graph that can be used for topological sorting. - */ - private void constructGraph() + @Override + protected Collection<String> getDependentEntitiesCodes(NewExternalData dataSet) { - IServiceConversationProgressListener listener = - ServiceConversationsThreadContext.getProgressListener(); + ArrayList<String> dependents = new ArrayList<String>(); - int index = 0; + // All the parents are dependents + dependents.addAll(dataSet.getParentDataSetCodes()); - for (NewExternalData dataSet : dataSetRegistrations) + if (dataSet instanceof NewContainerDataSet) { - String dataSetCode = dataSet.getCode(); - codeToDataMap.put(dataSetCode, dataSet); - - // There may already be dependents for this data set -- get them or initialize the - // dependents - ArrayList<String> dependents = getDependentsList(dataSetCode); - - // All the parents are dependents - dependents.addAll(dataSet.getParentDataSetCodes()); - - if (dataSet instanceof NewContainerDataSet) - { - // All contained data sets are dependents - List<String> containedDataSetCodes = - ((NewContainerDataSet) dataSet).getContainedDataSetCodes(); - dependents.addAll(containedDataSetCodes); - } - - dependencyGraph.put(dataSet.getCode(), dependents); - - listener.update("constructGraph", dataSetRegistrations.size(), ++index); + // All contained data sets are dependents + List<String> containedDataSetCodes = + ((NewContainerDataSet) dataSet).getContainedDataSetCodes(); + dependents.addAll(containedDataSetCodes); } - } - private ArrayList<String> getDependentsList(String dataSetCode) - { - ArrayList<String> dependents = dependencyGraph.get(dataSetCode); - if (null == dependents) - { - dependents = new ArrayList<String>(); - dependencyGraph.put(dataSetCode, dependents); - } return dependents; } diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/EntityDagTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/EntityDagTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e81f89d781af5ec256f3497875b1122ccdfaa151 --- /dev/null +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/EntityDagTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2012 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.openbis.generic.server; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.common.exceptions.UserFailureException; + +/** + * @author Jakub Straszewski + */ +public class EntityDagTest + +{ + private class DependencyHolder + { + String code; + + Collection<String> dependants; + + public DependencyHolder(String code, Collection<String> dependants) + { + super(); + this.code = code; + this.dependants = dependants; + } + + @Override + public String toString() + { + return code; + } + } + + private EntityDAG<DependencyHolder> createDag(List<DependencyHolder> entities) + { + return new EntityDAG<EntityDagTest.DependencyHolder>(entities) + { + @Override + protected Collection<String> getDependentEntitiesCodes(DependencyHolder entity) + { + return entity.dependants; + } + + @Override + protected String getCode(DependencyHolder entity) + { + return entity.code; + } + }; + } + + @Test + public void testSortedDependencyOrderIsGood() + { + List<DependencyHolder> entities = new ArrayList<DependencyHolder>(); + + List<String> empty = Collections.emptyList(); + + entities.add(new DependencyHolder("A1", empty)); + entities.add(new DependencyHolder("A2", Collections.singleton("A1"))); + entities.add(new DependencyHolder("A3", Collections.singleton("A2"))); + + EntityDAG<DependencyHolder> dag = createDag(entities); + + List<? extends DependencyHolder> orderedRegistrations = dag.getOrderedRegistrations(); + + Assert.assertEquals(entities, orderedRegistrations); + } + + @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = "Graph cycle detected. Cannot execute topological sort.") + public void testCircularDependency() + { + List<DependencyHolder> entities = new ArrayList<DependencyHolder>(); + + entities.add(new DependencyHolder("A1", Collections.singleton("A3"))); + entities.add(new DependencyHolder("A2", Collections.singleton("A1"))); + entities.add(new DependencyHolder("A3", Collections.singleton("A2"))); + + EntityDAG<DependencyHolder> dag = createDag(entities); + + List<? extends DependencyHolder> orderedRegistrations = dag.getOrderedRegistrations(); + + Assert.assertEquals(entities, orderedRegistrations); + } + +}