From 036e058f092222ab17452f8e56de5f6fba288575 Mon Sep 17 00:00:00 2001
From: anttil <anttil>
Date: Wed, 8 Mar 2017 09:33:04 +0000
Subject: [PATCH] SSDM-4725: Get all copies of linked datasets through v3 api

SVN: 37863
---
 .../server/dssapi/v3/DataStoreServerApi.java  |   2 +-
 .../dataset/ContentCopyBaseTranslator.java    |  22 +++
 ... => ContentCopyExternalDmsTranslator.java} |   8 +-
 .../translator/dataset/ContentCopyRecord.java |  12 ++
 .../dataset/ContentCopyTranslator.java        |  62 +++++++++
 .../v3/translator/dataset/DataSetQuery.java   |  10 +-
 .../dataset/IContentCopyBaseTranslator.java   |   8 ++
 ...=> IContentCopyExternalDmsTranslator.java} |   5 +-
 .../dataset/IContentCopyTranslator.java       |  10 ++
 .../ILinkedDataContentCopiesTranslator.java   |   6 +
 .../IObjectToContentCopiesTranslator.java     |  10 ++
 .../dataset/LinkedDataBaseRecord.java         |   4 +
 .../LinkedDataContentCopiesTranslator.java    |  21 +++
 .../dataset/LinkedDataTranslator.java         |  38 ++++--
 .../ObjectToContentCopiesTranslator.java      |  34 +++++
 .../bo/datasetlister/DatasetLister.java       |  11 +-
 .../generic/server/dataaccess/db/DataDAO.java |  11 ++
 .../generic/shared/dto/LinkDataPE.java        |   2 +-
 .../asapi/v3/AbstractExternalDmsTest.java     |  15 +-
 .../asapi/v3/AbstractLinkDataSetTest.java     |  99 +++++++++-----
 .../asapi/v3/CreateExternalDmsTest.java       |   3 -
 .../asapi/v3/CreateLinkDataSetTest.java       | 129 ++++++++++++++++--
 .../asapi/v3/GetExternalDmsTest.java          |   3 -
 .../asapi/v3/GetLinkDataSetTest.java          |  98 +++++++++++++
 .../asapi/v3/dto/dataset/LinkedData.java      |  19 +++
 .../generic/sharedapi/v3/dictionary.txt       |   4 +-
 26 files changed, 556 insertions(+), 90 deletions(-)
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyBaseTranslator.java
 rename openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/{LinkedDataExternalDmsTranslator.java => ContentCopyExternalDmsTranslator.java} (91%)
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyRecord.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyTranslator.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IContentCopyBaseTranslator.java
 rename openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/{ILinkedDataExternalDmsTranslator.java => IContentCopyExternalDmsTranslator.java} (86%)
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IContentCopyTranslator.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ILinkedDataContentCopiesTranslator.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IObjectToContentCopiesTranslator.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataContentCopiesTranslator.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ObjectToContentCopiesTranslator.java
 create mode 100644 openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetLinkDataSetTest.java

diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dssapi/v3/DataStoreServerApi.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dssapi/v3/DataStoreServerApi.java
index ba8b64783a5..b3f130aeaa5 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dssapi/v3/DataStoreServerApi.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dssapi/v3/DataStoreServerApi.java
@@ -308,7 +308,7 @@ public class DataStoreServerApi extends AbstractDssServiceRpc<IDataStoreServerAp
             List<DataSetFileCreation> files = newDataSets.get(i).getFileMetadata();
 
             long dataSetId =
-                    dao.createDataSet(permId, newDataSets.get(i).getMetadataCreation().getLinkedData().getExternalDmsId().toString());
+                    dao.createDataSet(permId, "External system(s)");
             try
             {
                 dao.createDataSetFiles(PathInfoDTOCreator.createPathEntries(dataSetId, permId, files));
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyBaseTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyBaseTranslator.java
new file mode 100644
index 00000000000..313623a91ee
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyBaseTranslator.java
@@ -0,0 +1,22 @@
+package ch.ethz.sis.openbis.generic.server.asapi.v3.translator.dataset;
+
+import java.util.List;
+
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.common.ObjectBaseTranslator;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import net.lemnik.eodsql.QueryTool;
+
+@Component
+public class ContentCopyBaseTranslator extends ObjectBaseTranslator<ContentCopyRecord> implements IContentCopyBaseTranslator
+{
+
+    @Override
+    protected List<ContentCopyRecord> loadRecords(LongOpenHashSet objectIds)
+    {
+        DataSetQuery query = QueryTool.getManagedQuery(DataSetQuery.class);
+        return query.getContentCopies(new LongOpenHashSet(objectIds));
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataExternalDmsTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyExternalDmsTranslator.java
similarity index 91%
rename from openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataExternalDmsTranslator.java
rename to openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyExternalDmsTranslator.java
index 3c470989860..fabbe1d92a4 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataExternalDmsTranslator.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyExternalDmsTranslator.java
@@ -34,12 +34,9 @@ import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.common.ObjectRelat
 import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.common.ObjectToOneRelationTranslator;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.externaldms.IExternalDmsTranslator;
 
-/**
- * @author pkupczyk
- */
 @Component
-public class LinkedDataExternalDmsTranslator extends ObjectToOneRelationTranslator<ExternalDms, ExternalDmsFetchOptions> implements
-        ILinkedDataExternalDmsTranslator
+public class ContentCopyExternalDmsTranslator extends ObjectToOneRelationTranslator<ExternalDms, ExternalDmsFetchOptions> implements
+        IContentCopyExternalDmsTranslator
 {
 
     @Autowired
@@ -58,5 +55,4 @@ public class LinkedDataExternalDmsTranslator extends ObjectToOneRelationTranslat
     {
         return externalDmsTranslator.translate(context, relatedIds, relatedFetchOptions);
     }
-
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyRecord.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyRecord.java
new file mode 100644
index 00000000000..fa40d1be357
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyRecord.java
@@ -0,0 +1,12 @@
+package ch.ethz.sis.openbis.generic.server.asapi.v3.translator.dataset;
+
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.common.ObjectBaseRecord;
+
+public class ContentCopyRecord extends ObjectBaseRecord
+{
+    public String externalCode;
+
+    public String path;
+
+    public String gitCommitHash;
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyTranslator.java
new file mode 100644
index 00000000000..29d7a070a44
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ContentCopyTranslator.java
@@ -0,0 +1,62 @@
+package ch.ethz.sis.openbis.generic.server.asapi.v3.translator.dataset;
+
+import java.util.Collection;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.ContentCopy;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.LinkedDataFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDms;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.AbstractCachingTranslator;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.TranslationContext;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.TranslationResults;
+
+@Component
+public class ContentCopyTranslator extends AbstractCachingTranslator<Long, ContentCopy, LinkedDataFetchOptions> implements IContentCopyTranslator
+{
+
+    @Autowired
+    private IContentCopyBaseTranslator baseTranslator;
+
+    @Autowired
+    private IContentCopyExternalDmsTranslator externalDmsTranslator;
+
+    @Override
+    protected ContentCopy createObject(TranslationContext context, Long input, LinkedDataFetchOptions fetchOptions)
+    {
+        return new ContentCopy();
+    }
+
+    @Override
+    protected TranslationResults getObjectsRelations(TranslationContext context, Collection<Long> copyIds, LinkedDataFetchOptions fetchOptions)
+    {
+        TranslationResults relations = new TranslationResults();
+
+        relations.put(IContentCopyBaseTranslator.class, baseTranslator.translate(context, copyIds, null));
+
+        if (fetchOptions.hasExternalDms())
+        {
+            relations.put(IContentCopyExternalDmsTranslator.class, externalDmsTranslator.translate(context, copyIds, null));
+        }
+
+        return relations;
+    }
+
+    @Override
+    protected void updateObject(TranslationContext context, Long input, ContentCopy output, Object objectRelations,
+            LinkedDataFetchOptions fetchOptions)
+    {
+        TranslationResults relations = (TranslationResults) objectRelations;
+        ContentCopyRecord baseRecord = relations.get(IContentCopyBaseTranslator.class, input);
+        output.setExternalCode(baseRecord.externalCode);
+        output.setPath(baseRecord.path);
+        output.setGitCommitHash(baseRecord.gitCommitHash);
+        if (fetchOptions.hasExternalDms())
+        {
+            ExternalDms externalDms = relations.get(IContentCopyExternalDmsTranslator.class, input);
+            output.setExternalDms(externalDms);
+        }
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/DataSetQuery.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/DataSetQuery.java
index 69df673bf02..424a371e59e 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/DataSetQuery.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/DataSetQuery.java
@@ -25,6 +25,7 @@ import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.property.MaterialP
 import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.property.PropertyAssignmentRecord;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.property.PropertyRecord;
 import ch.systemsx.cisd.common.db.mapper.LongSetMapper;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
 import it.unimi.dsi.fastutil.longs.LongSet;
 import net.lemnik.eodsql.Select;
 
@@ -66,7 +67,7 @@ public interface DataSetQuery extends ObjectQuery
             LongSetMapper.class }, fetchSize = FETCH_SIZE)
     public List<LinkedDataBaseRecord> getLinkedDatas(LongSet dataSetIds);
 
-    @Select(sql = "select ld.data_id as objectId, cc.edms_id as relatedId from link_data ld, content_copies cc where cc.data_id = ld.data_id and ld.data_id = any(?{1})", parameterBindings = {
+    @Select(sql = "select cc.id as objectId, cc.edms_id as relatedId from content_copies cc where cc.id = any(?{1})", parameterBindings = {
             LongSetMapper.class }, fetchSize = FETCH_SIZE)
     public List<ObjectRelationRecord> getExternalDmsIds(LongSet dataSetIds);
 
@@ -193,4 +194,11 @@ public interface DataSetQuery extends ObjectQuery
             LongSetMapper.class }, fetchSize = FETCH_SIZE)
     public List<PropertyAssignmentRecord> getPropertyAssignments(LongSet dataSetTypePropertyTypeIds);
 
+    @Select(sql = "select d.id as objectId, cc.id as relatedId from data d, content_copies cc where cc.data_id = d.id and d.id = any(?{1})", parameterBindings = {
+            LongSetMapper.class }, fetchSize = FETCH_SIZE)
+    public List<ObjectRelationRecord> getContentCopyIds(LongOpenHashSet dataSetIds);
+
+    @Select(sql = "select id, external_code as externalCode, path, git_commit_hash as gitCommitHash from content_copies where id = any(?{1})", parameterBindings = {
+            LongSetMapper.class }, fetchSize = FETCH_SIZE)
+    public List<ContentCopyRecord> getContentCopies(LongOpenHashSet longOpenHashSet);
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IContentCopyBaseTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IContentCopyBaseTranslator.java
new file mode 100644
index 00000000000..17dedb68e7a
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IContentCopyBaseTranslator.java
@@ -0,0 +1,8 @@
+package ch.ethz.sis.openbis.generic.server.asapi.v3.translator.dataset;
+
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.common.IObjectBaseTranslator;
+
+public interface IContentCopyBaseTranslator extends IObjectBaseTranslator<ContentCopyRecord>
+{
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ILinkedDataExternalDmsTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IContentCopyExternalDmsTranslator.java
similarity index 86%
rename from openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ILinkedDataExternalDmsTranslator.java
rename to openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IContentCopyExternalDmsTranslator.java
index 2c1da37164f..37f94ba7d22 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ILinkedDataExternalDmsTranslator.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IContentCopyExternalDmsTranslator.java
@@ -20,10 +20,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDms;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.fetchoptions.ExternalDmsFetchOptions;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.common.IObjectToOneRelationTranslator;
 
-/**
- * @author pkupczyk
- */
-public interface ILinkedDataExternalDmsTranslator extends IObjectToOneRelationTranslator<ExternalDms, ExternalDmsFetchOptions>
+public interface IContentCopyExternalDmsTranslator extends IObjectToOneRelationTranslator<ExternalDms, ExternalDmsFetchOptions>
 {
 
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IContentCopyTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IContentCopyTranslator.java
new file mode 100644
index 00000000000..39c51f43fa1
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IContentCopyTranslator.java
@@ -0,0 +1,10 @@
+package ch.ethz.sis.openbis.generic.server.asapi.v3.translator.dataset;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.ContentCopy;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.LinkedDataFetchOptions;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.ITranslator;
+
+public interface IContentCopyTranslator extends ITranslator<Long, ContentCopy, LinkedDataFetchOptions>
+{
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ILinkedDataContentCopiesTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ILinkedDataContentCopiesTranslator.java
new file mode 100644
index 00000000000..18b2be647d3
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ILinkedDataContentCopiesTranslator.java
@@ -0,0 +1,6 @@
+package ch.ethz.sis.openbis.generic.server.asapi.v3.translator.dataset;
+
+public interface ILinkedDataContentCopiesTranslator extends IObjectToContentCopiesTranslator
+{
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IObjectToContentCopiesTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IObjectToContentCopiesTranslator.java
new file mode 100644
index 00000000000..62a1a012521
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/IObjectToContentCopiesTranslator.java
@@ -0,0 +1,10 @@
+package ch.ethz.sis.openbis.generic.server.asapi.v3.translator.dataset;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.ContentCopy;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.LinkedDataFetchOptions;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.common.IObjectToManyRelationTranslator;
+
+public interface IObjectToContentCopiesTranslator extends IObjectToManyRelationTranslator<ContentCopy, LinkedDataFetchOptions>
+{
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataBaseRecord.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataBaseRecord.java
index c5c0f5ed7a3..a839d139d7a 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataBaseRecord.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataBaseRecord.java
@@ -16,6 +16,8 @@
 
 package ch.ethz.sis.openbis.generic.server.asapi.v3.translator.dataset;
 
+import java.util.List;
+
 import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.common.ObjectBaseRecord;
 
 /**
@@ -26,4 +28,6 @@ public class LinkedDataBaseRecord extends ObjectBaseRecord
 
     public String externalCode;
 
+    public List<ContentCopyRecord> copies;
+
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataContentCopiesTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataContentCopiesTranslator.java
new file mode 100644
index 00000000000..6ba395994fe
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataContentCopiesTranslator.java
@@ -0,0 +1,21 @@
+package ch.ethz.sis.openbis.generic.server.asapi.v3.translator.dataset;
+
+import java.util.List;
+
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.common.ObjectRelationRecord;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import net.lemnik.eodsql.QueryTool;
+
+@Component
+public class LinkedDataContentCopiesTranslator extends ObjectToContentCopiesTranslator implements ILinkedDataContentCopiesTranslator
+{
+
+    @Override
+    protected List<ObjectRelationRecord> loadRecords(LongOpenHashSet dataSetIds)
+    {
+        DataSetQuery query = QueryTool.getManagedQuery(DataSetQuery.class);
+        return query.getContentCopyIds(dataSetIds);
+    }
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataTranslator.java
index 5ed7409026d..270dfcf6275 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataTranslator.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/LinkedDataTranslator.java
@@ -16,13 +16,17 @@
 
 package ch.ethz.sis.openbis.generic.server.asapi.v3.translator.dataset;
 
+import java.util.ArrayList;
 import java.util.Collection;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.ContentCopy;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.LinkedData;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.LinkedDataFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDms;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDmsAddressType;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.AbstractCachingTranslator;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.TranslationContext;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.TranslationResults;
@@ -36,10 +40,7 @@ public class LinkedDataTranslator extends AbstractCachingTranslator<Long, Linked
 {
 
     @Autowired
-    private ILinkedDataBaseTranslator baseTranslator;
-
-    @Autowired
-    private ILinkedDataExternalDmsTranslator externalDmsTranslator;
+    private ILinkedDataContentCopiesTranslator contentCopiesTranslator;
 
     @Override
     protected LinkedData createObject(TranslationContext context, Long dataSetId, LinkedDataFetchOptions fetchOptions)
@@ -54,13 +55,7 @@ public class LinkedDataTranslator extends AbstractCachingTranslator<Long, Linked
     {
         TranslationResults relations = new TranslationResults();
 
-        relations.put(ILinkedDataBaseTranslator.class, baseTranslator.translate(context, dataSetIds, null));
-
-        if (fetchOptions.hasExternalDms())
-        {
-            relations.put(ILinkedDataExternalDmsTranslator.class,
-                    externalDmsTranslator.translate(context, dataSetIds, fetchOptions.withExternalDms()));
-        }
+        relations.put(ILinkedDataContentCopiesTranslator.class, contentCopiesTranslator.translate(context, dataSetIds, fetchOptions));
 
         return relations;
     }
@@ -70,13 +65,28 @@ public class LinkedDataTranslator extends AbstractCachingTranslator<Long, Linked
             LinkedDataFetchOptions fetchOptions)
     {
         TranslationResults relations = (TranslationResults) objectRelations;
-        LinkedDataBaseRecord baseRecord = relations.get(ILinkedDataBaseTranslator.class, dataSetId);
+        Collection<ContentCopy> copies = relations.get(ILinkedDataContentCopiesTranslator.class, dataSetId);
+
+        ArrayList<ContentCopy> copyList = new ArrayList<>(copies);
+        result.setContentCopies(copyList);
+
+        String externalCode = LinkedData.NO_COPY_EXTERNAL_CODE;
+        ExternalDms externalDms = LinkedData.NO_COPY_EXTERNAL_DMS;
+        for (ContentCopy copy : copyList)
+        {
+            ExternalDmsAddressType type = copy.getExternalDms().getAddressType();
+            if (type.equals(ExternalDmsAddressType.OPENBIS) || type.equals(ExternalDmsAddressType.URL))
+            {
+                externalCode = copy.getExternalCode();
+                externalDms = copy.getExternalDms();
+            }
+        }
 
-        result.setExternalCode(baseRecord.externalCode);
+        result.setExternalCode(externalCode);
 
         if (fetchOptions.hasExternalDms())
         {
-            result.setExternalDms(relations.get(ILinkedDataExternalDmsTranslator.class, dataSetId));
+            result.setExternalDms(externalDms);
             result.getFetchOptions().withExternalDmsUsing(fetchOptions.withExternalDms());
         }
     }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ObjectToContentCopiesTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ObjectToContentCopiesTranslator.java
new file mode 100644
index 00000000000..ee88df83d2b
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/ObjectToContentCopiesTranslator.java
@@ -0,0 +1,34 @@
+package ch.ethz.sis.openbis.generic.server.asapi.v3.translator.dataset;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.ContentCopy;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.LinkedDataFetchOptions;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.TranslationContext;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.common.ObjectToManyRelationTranslator;
+
+public abstract class ObjectToContentCopiesTranslator extends ObjectToManyRelationTranslator<ContentCopy, LinkedDataFetchOptions>
+        implements IObjectToContentCopiesTranslator
+{
+
+    @Autowired
+    private IContentCopyTranslator contentCopyTranslator;
+
+    @Override
+    protected Map<Long, ContentCopy> translateRelated(TranslationContext context, Collection<Long> relatedIds,
+            LinkedDataFetchOptions relatedFetchOptions)
+    {
+
+        return contentCopyTranslator.translate(context, relatedIds, relatedFetchOptions);
+    }
+
+    @Override
+    protected Collection<ContentCopy> createCollection()
+    {
+        return new LinkedHashSet<ContentCopy>();
+    }
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetLister.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetLister.java
index 057b9170286..846942c9354 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetLister.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetLister.java
@@ -1181,9 +1181,14 @@ public class DatasetLister extends AbstractLister implements IDatasetLister
         LinkDataSet linkDataSet = new LinkDataSet();
 
         convertStandardAttributes(linkDataSet, record);
-        linkDataSet.setExternalDataManagementSystem(externalDataManagementSystems
-                .get(record.edms_id));
-        linkDataSet.setExternalCode(record.external_code);
+
+        if (record.edms_id != null)
+        {
+
+            linkDataSet.setExternalDataManagementSystem(externalDataManagementSystems
+                    .get(record.edms_id));
+            linkDataSet.setExternalCode(record.external_code);
+        }
 
         return linkDataSet;
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DataDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DataDAO.java
index fd5edb1c0ed..17ae4aab17c 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DataDAO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DataDAO.java
@@ -827,6 +827,7 @@ final class DataDAO extends AbstractGenericEntityWithPropertiesDAO<DataPE> imple
         sqls.insertEvent = SQLBuilder.createInsertEventSQL();
         // data set specific queries
         sqls.deleteExternalData = createDeleteExternalDataSQL();
+        sqls.deleteContentCopies = createDeleteContentCopiesSQL();
         Long relationshipTypeId = RelationshipUtils.getParentChildRelationshipType(relationshipTypeDAO).getId();
         sqls.deleteChildrenConnections = createDeleteChildrenConnectionsSQL(relationshipTypeId);
         sqls.deleteParentConnections = createDeleteParentConnectionsSQL(relationshipTypeId);
@@ -851,6 +852,8 @@ final class DataDAO extends AbstractGenericEntityWithPropertiesDAO<DataPE> imple
 
         public String deleteExternalData;
 
+        public String deleteContentCopies;
+
         public String deleteChildrenConnections;
 
         public String deleteParentConnections;
@@ -896,6 +899,12 @@ final class DataDAO extends AbstractGenericEntityWithPropertiesDAO<DataPE> imple
                 + SQLBuilder.inEntityIds();
     }
 
+    private static String createDeleteContentCopiesSQL()
+    {
+        return "DELETE FROM " + TableNames.CONTENT_COPIES_TABLE + " WHERE data_id "
+                + SQLBuilder.inEntityIds();
+    }
+
     private static String createDeleteChildrenConnectionsSQL(long relationshipTypeId)
     {
         return "DELETE FROM " + TableNames.DATA_SET_RELATIONSHIPS_ALL_TABLE
@@ -1048,6 +1057,7 @@ final class DataDAO extends AbstractGenericEntityWithPropertiesDAO<DataPE> imple
                 final SQLQuery deleteEntities = session.createSQLQuery(sqls.deleteDataSets);
                 final SQLQuery insertEvent = session.createSQLQuery(sqls.insertEvent);
                 final SQLQuery deleteExternalData = session.createSQLQuery(sqls.deleteExternalData);
+                final SQLQuery deleteContentCopies = session.createSQLQuery(sqls.deleteContentCopies);
                 final SQLQuery deleteChildrenConnections = session.createSQLQuery(sqls.deleteChildrenConnections);
                 final SQLQuery deleteParentConnections = session.createSQLQuery(sqls.deleteParentConnections);
 
@@ -1065,6 +1075,7 @@ final class DataDAO extends AbstractGenericEntityWithPropertiesDAO<DataPE> imple
 
                 executeUpdate(deleteProperties, entityIdsToDelete);
                 executeUpdate(deleteExternalData, entityIdsToDelete);
+                executeUpdate(deleteContentCopies, entityIdsToDelete);
                 executeUpdate(deleteChildrenConnections, entityIdsToDelete);
                 executeUpdate(deleteParentConnections, entityIdsToDelete);
                 executeUpdate(deleteEntities, entityIdsToDelete);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/LinkDataPE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/LinkDataPE.java
index 9cbae55e4c5..6762659d79c 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/LinkDataPE.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/LinkDataPE.java
@@ -49,7 +49,7 @@ public class LinkDataPE extends DataPE
 
     private Set<ContentCopyPE> contentCopies;
 
-    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "dataSet")
+    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "dataSet", orphanRemoval = true)
     @Fetch(FetchMode.SUBSELECT)
     @IndexedEmbedded(prefix = SearchFieldConstants.PREFIX_CONTENT_COPY)
     public Set<ContentCopyPE> getContentCopies()
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractExternalDmsTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractExternalDmsTest.java
index 7c8df4b2879..6dcc95b2a38 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractExternalDmsTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractExternalDmsTest.java
@@ -10,6 +10,7 @@ import org.apache.commons.lang.builder.ToStringBuilder;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.testng.annotations.BeforeClass;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDms;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDmsAddressType;
@@ -21,19 +22,25 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.id.IExternalDmsId;
 public abstract class AbstractExternalDmsTest extends AbstractTest
 {
 
+    protected String session;
+
+    @BeforeClass
+    protected void login()
+    {
+        session = v3api.login(TEST_USER, PASSWORD);
+    }
+
     protected ExternalDmsPermId create(ExternalDmsCreationBuilder creation)
     {
-        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
         List<ExternalDmsPermId> ids =
-                v3api.createExternalDataManagementSystems(sessionToken, Collections.singletonList(creation.build()));
+                v3api.createExternalDataManagementSystems(session, Collections.singletonList(creation.build()));
         return ids.get(0);
     }
 
     protected ExternalDms get(ExternalDmsPermId id)
     {
-        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
         Map<IExternalDmsId, ExternalDms> result =
-                v3api.getExternalDataManagementSystems(sessionToken, Collections.singletonList(id), new ExternalDmsFetchOptions());
+                v3api.getExternalDataManagementSystems(session, Collections.singletonList(id), new ExternalDmsFetchOptions());
         return result.get(id);
     }
 
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractLinkDataSetTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractLinkDataSetTest.java
index 08d258e835d..308a2266f60 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractLinkDataSetTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractLinkDataSetTest.java
@@ -1,90 +1,105 @@
 package ch.ethz.sis.openbis.systemtest.asapi.v3;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
+import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.ContentCopy;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSet;
-import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSetKind;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.create.ContentCopyCreation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.create.DataSetCreation;
-import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.create.DataSetTypeCreation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.create.LinkedDataCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.delete.DataSetDeletionOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.DataSetFetchOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.LinkedDataFetchOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.id.DataSetPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.id.IDataSetId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.datastore.id.DataStorePermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.deletion.id.IDeletionId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id.EntityTypePermId;
-import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id.IEntityTypeId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.create.ExperimentCreation;
-import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.create.ExperimentTypeCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.delete.ExperimentDeletionOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id.ExperimentPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDmsAddressType;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.id.ExternalDmsPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.id.IExternalDmsId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.create.ProjectCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.delete.ProjectDeletionOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.id.ProjectPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.create.SpaceCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.delete.SpaceDeletionOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.id.SpacePermId;
 
 public abstract class AbstractLinkDataSetTest extends AbstractExternalDmsTest
 {
 
-    private IEntityTypeId linkDataSetType;
-
     private ExperimentPermId experiment;
 
+    private SpacePermId space;
+
+    private ProjectPermId project;
+
+    private List<DataSetPermId> datasets = new ArrayList<DataSetPermId>();
+
     @BeforeClass
     protected void createData()
     {
-        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
-        DataSetTypeCreation datasetTypeCreation = new DataSetTypeCreation();
-        datasetTypeCreation.setCode("linked-" + uuid());
-        datasetTypeCreation.setKind(DataSetKind.LINK);
-
-        this.linkDataSetType = v3api.createDataSetTypes(sessionToken, Collections.singletonList(datasetTypeCreation)).get(0);
-
         SpaceCreation spaceCreation = new SpaceCreation();
         spaceCreation.setCode("space-" + uuid());
-        SpacePermId space = v3api.createSpaces(sessionToken, Collections.singletonList(spaceCreation)).get(0);
+        space = v3api.createSpaces(session, Collections.singletonList(spaceCreation)).get(0);
 
         ProjectCreation projectCreation = new ProjectCreation();
         projectCreation.setCode("project-" + uuid());
         projectCreation.setSpaceId(space);
-        ProjectPermId project = v3api.createProjects(sessionToken, Collections.singletonList(projectCreation)).get(0);
-
-        ExperimentTypeCreation experimentTypeCreation = new ExperimentTypeCreation();
-        experimentTypeCreation.setCode("experiment-type-" + uuid());
-        EntityTypePermId experimentType = v3api.createExperimentTypes(sessionToken, Collections.singletonList(experimentTypeCreation)).get(0);
+        project = v3api.createProjects(session, Collections.singletonList(projectCreation)).get(0);
 
         ExperimentCreation experimentCreation = new ExperimentCreation();
         experimentCreation.setCode("experiment-" + uuid());
-        experimentCreation.setTypeId(experimentType);
+        experimentCreation.setProperty("DESCRIPTION", "DESCRIPTION");
+        experimentCreation.setTypeId(new EntityTypePermId("SIRNA_HCS"));
         experimentCreation.setProjectId(project);
+        experiment = v3api.createExperiments(session, Collections.singletonList(experimentCreation)).get(0);
+    }
 
-        this.experiment = v3api.createExperiments(sessionToken, Collections.singletonList(experimentCreation)).get(0);
-
+    @AfterClass
+    protected void deleteData()
+    {
+        DataSetDeletionOptions dd = new DataSetDeletionOptions();
+        dd.setReason("test");
+        ExperimentDeletionOptions ed = new ExperimentDeletionOptions();
+        ed.setReason("test");
+        ProjectDeletionOptions pd = new ProjectDeletionOptions();
+        pd.setReason("test");
+        SpaceDeletionOptions sd = new SpaceDeletionOptions();
+        sd.setReason("test");
+
+        IDeletionId delDataSets = v3api.deleteDataSets(session, datasets, dd);
+        IDeletionId delExperiments = v3api.deleteExperiments(session, Collections.singletonList(experiment), ed);
+        v3api.confirmDeletions(session, Collections.singletonList(delDataSets));
+        v3api.confirmDeletions(session, Collections.singletonList(delExperiments));
+        v3api.deleteProjects(session, Collections.singletonList(project), pd);
+        v3api.deleteSpaces(session, Collections.singletonList(space), sd);
     }
 
     protected DataSetPermId create(LinkDataCreationBuilder creation)
     {
-        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
-        List<DataSetPermId> ids = v3api.createDataSets(sessionToken, Collections.singletonList(creation.build()));
+        List<DataSetPermId> ids = v3api.createDataSets(session, Collections.singletonList(creation.build()));
+        datasets.addAll(ids);
         return ids.get(0);
     }
 
     protected DataSet get(DataSetPermId id)
     {
-        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
         DataSetFetchOptions fo = new DataSetFetchOptions();
         LinkedDataFetchOptions lfo = new LinkedDataFetchOptions();
         lfo.withExternalDms();
         fo.withLinkedDataUsing(lfo);
-        Map<IDataSetId, DataSet> result = v3api.getDataSets(sessionToken, Collections.singletonList(id), fo);
+        Map<IDataSetId, DataSet> result = v3api.getDataSets(session, Collections.singletonList(id), fo);
         return result.get(id);
     }
 
@@ -159,24 +174,26 @@ public abstract class AbstractLinkDataSetTest extends AbstractExternalDmsTest
     protected class LinkDataCreationBuilder
     {
 
-        private List<ContentCopyCreation> copies;
+        private List<ContentCopyCreation> copies = new ArrayList<>();
 
         private String externalCode;
 
         private IExternalDmsId edms;
 
-        public LinkDataCreationBuilder with(ContentCopyCreationBuilder copy)
+        public LinkDataCreationBuilder with(ContentCopyCreationBuilder copy1, ContentCopyCreationBuilder... rest)
         {
-            return with(copy.build());
+            LinkDataCreationBuilder result = with(copy1.build());
+            for (ContentCopyCreationBuilder b : rest)
+            {
+                result = result.with(b.build());
+            }
+            return result;
         }
 
-        public LinkDataCreationBuilder with(ContentCopyCreation copy)
+        public LinkDataCreationBuilder with(ContentCopyCreation first, ContentCopyCreation... rest)
         {
-            if (copies == null)
-            {
-                copies = new ArrayList<>();
-            }
-            copies.add(copy);
+            copies.add(first);
+            copies.addAll(Arrays.asList(rest));
             return this;
         }
 
@@ -200,11 +217,21 @@ public abstract class AbstractLinkDataSetTest extends AbstractExternalDmsTest
             linkData.setExternalDmsId(edms);
             DataSetCreation creation = new DataSetCreation();
             creation.setLinkedData(linkData);
-            creation.setTypeId(linkDataSetType);
+            creation.setTypeId(new EntityTypePermId("LINK_TYPE"));
             creation.setExperimentId(experiment);
             creation.setAutoGeneratedCode(true);
             creation.setDataStoreId(new DataStorePermId("STANDARD"));
             return creation;
         }
     }
+
+    protected String stringify(ContentCopyCreation c)
+    {
+        return c.getExternalId() + " / " + c.getPath() + " / " + c.getGitCommitHash() + " / " + c.getExternalDmsId();
+    }
+
+    protected String stringify(ContentCopy c)
+    {
+        return c.getExternalCode() + " / " + c.getPath() + " / " + c.getGitCommitHash() + " / " + c.getExternalDms().getCode();
+    }
 }
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateExternalDmsTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateExternalDmsTest.java
index 8af18317149..cec5f019891 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateExternalDmsTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateExternalDmsTest.java
@@ -28,9 +28,6 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDmsAddressTy
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.id.ExternalDmsPermId;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 
-/**
- * @author anttil
- */
 public class CreateExternalDmsTest extends AbstractExternalDmsTest
 {
 
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateLinkDataSetTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateLinkDataSetTest.java
index f351f33f55f..8293a9c79d4 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateLinkDataSetTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateLinkDataSetTest.java
@@ -3,8 +3,12 @@ package ch.ethz.sis.openbis.systemtest.asapi.v3;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
+import org.hamcrest.collection.IsIterableContainingInAnyOrder;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
@@ -18,8 +22,8 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 public class CreateLinkDataSetTest extends AbstractLinkDataSetTest
 {
 
-    @Test(enabled = false)
-    void copiesInOpenBISWork()
+    @Test
+    void copyInOpenBIS()
     {
         ExternalDmsPermId openbis = create(externalDms().withType(ExternalDmsAddressType.OPENBIS));
         ContentCopyCreation creation = copyAt(openbis).build();
@@ -35,15 +39,115 @@ public class CreateLinkDataSetTest extends AbstractLinkDataSetTest
         assertThat(copy.getExternalDms(), isSimilarTo(get(openbis)));
     }
 
-    // this goes to GetLinkDataSetTest, look for problems in legacy fields
-    // @Test
-    // void copiesInOpenBISWork()
-    // {
-    // ExternalDmsPermId openbis = create(externalDms().withType(ExternalDmsAddressType.OPENBIS));
-    // DataSetPermId id = create(linkDataSet().with(copyAt(openbis)));
-    // DataSet dataSet = get(id);
-    // assertThat(dataSet.getLinkedData().getExternalDms(), isSimilarTo(get(openbis)));
-    // }
+    @Test
+    void copyInURL()
+    {
+        ExternalDmsPermId url = create(externalDms().withType(ExternalDmsAddressType.URL));
+        ContentCopyCreation creation = copyAt(url).build();
+        DataSetPermId id = create(linkDataSet().with(creation));
+        List<ContentCopy> contentCopies = get(id).getLinkedData().getContentCopies();
+
+        assertThat(contentCopies.size(), is(1));
+
+        ContentCopy copy = contentCopies.get(0);
+        assertThat(copy.getExternalCode(), is(creation.getExternalId()));
+        assertThat(copy.getPath(), is(creation.getPath()));
+        assertThat(copy.getGitCommitHash(), is(creation.getGitCommitHash()));
+        assertThat(copy.getExternalDms(), isSimilarTo(get(url)));
+    }
+
+    @Test
+    void copyInPlainFileSystem()
+    {
+        ExternalDmsPermId fs = create(externalDms().withType(ExternalDmsAddressType.FILE_SYSTEM));
+        ContentCopyCreation creation = copyAt(fs).withPath("/path/to/dir").build();
+        DataSetPermId id = create(linkDataSet().with(creation));
+        List<ContentCopy> contentCopies = get(id).getLinkedData().getContentCopies();
+
+        assertThat(contentCopies.size(), is(1));
+
+        ContentCopy copy = contentCopies.get(0);
+        assertThat(copy.getExternalCode(), is(creation.getExternalId()));
+        assertThat(copy.getPath(), is(creation.getPath()));
+        assertThat(copy.getGitCommitHash(), is(creation.getGitCommitHash()));
+        assertThat(copy.getExternalDms(), isSimilarTo(get(fs)));
+    }
+
+    @Test
+    void copyInGit()
+    {
+        ExternalDmsPermId fs = create(externalDms().withType(ExternalDmsAddressType.FILE_SYSTEM));
+        ContentCopyCreation creation = copyAt(fs).withPath("/path/to/dir").withGitCommitHash("asdfasfa").build();
+        DataSetPermId id = create(linkDataSet().with(creation));
+        List<ContentCopy> contentCopies = get(id).getLinkedData().getContentCopies();
+
+        assertThat(contentCopies.size(), is(1));
+
+        ContentCopy copy = contentCopies.get(0);
+        assertThat(copy.getExternalCode(), is(creation.getExternalId()));
+        assertThat(copy.getPath(), is(creation.getPath()));
+        assertThat(copy.getGitCommitHash(), is(creation.getGitCommitHash()));
+        assertThat(copy.getExternalDms(), isSimilarTo(get(fs)));
+    }
+
+    @Test
+    void noCopies()
+    {
+        DataSetPermId id = create(linkDataSet());
+        List<ContentCopy> contentCopies = get(id).getLinkedData().getContentCopies();
+
+        assertThat(contentCopies.size(), is(0));
+    }
+
+    @Test
+    void multipleCopies()
+    {
+        ExternalDmsPermId openbis1 = create(externalDms().withType(ExternalDmsAddressType.OPENBIS));
+        ExternalDmsPermId openbis2 = create(externalDms().withType(ExternalDmsAddressType.OPENBIS));
+
+        ExternalDmsPermId url1 = create(externalDms().withType(ExternalDmsAddressType.URL));
+        ExternalDmsPermId url2 = create(externalDms().withType(ExternalDmsAddressType.URL));
+
+        ExternalDmsPermId fs1 = create(externalDms().withType(ExternalDmsAddressType.FILE_SYSTEM));
+        ExternalDmsPermId fs2 = create(externalDms().withType(ExternalDmsAddressType.FILE_SYSTEM));
+
+        ContentCopyCreation creation1 = copyAt(openbis1).build();
+        ContentCopyCreation creation2 = copyAt(openbis1).build();
+
+        ContentCopyCreation creation3 = copyAt(openbis2).build();
+        ContentCopyCreation creation4 = copyAt(openbis2).build();
+
+        ContentCopyCreation creation5 = copyAt(url1).build();
+        ContentCopyCreation creation6 = copyAt(url1).build();
+
+        ContentCopyCreation creation7 = copyAt(url2).build();
+        ContentCopyCreation creation8 = copyAt(url2).build();
+
+        ContentCopyCreation creation9 = copyAt(fs1).withPath("/path/to/dir").withGitCommitHash("asdf").build();
+        ContentCopyCreation creation10 = copyAt(fs1).withPath("/path/to/dir2").build();
+
+        ContentCopyCreation creation11 = copyAt(fs2).withPath("/path/to/dir").withGitCommitHash("asdf").build();
+        ContentCopyCreation creation12 = copyAt(fs2).withPath("/path/to/dir2").build();
+
+        DataSetPermId id = create(linkDataSet().with(creation1, creation2, creation3, creation4, creation5, creation6, creation7, creation8,
+                creation9, creation10, creation11, creation12));
+        List<ContentCopy> contentCopies = get(id).getLinkedData().getContentCopies();
+
+        Set<String> expected = new HashSet<String>();
+        for (ContentCopyCreation c : Arrays.asList(creation1, creation2, creation3, creation4, creation5, creation6, creation7, creation8,
+                creation9, creation10, creation11, creation12))
+        {
+            expected.add(stringify(c));
+        }
+
+        Set<String> actual = new HashSet<String>();
+        for (ContentCopy c : contentCopies)
+        {
+            actual.add(stringify(c));
+        }
+        assertThat(contentCopies.size(), is(12));
+        assertThat(actual, IsIterableContainingInAnyOrder.<String> containsInAnyOrder(expected.toArray(new String[0])));
+    }
 
     @DataProvider(name = "InvalidLocationCombinations")
     public static Object[][] invalidLocationCombinations()
@@ -73,7 +177,7 @@ public class CreateLinkDataSetTest extends AbstractLinkDataSetTest
         };
     }
 
-    @Test(enabled = false, dataProvider = "InvalidLocationCombinations", expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = ".*Invalid arguments.*")
+    @Test(dataProvider = "InvalidLocationCombinations", expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = ".*Invalid arguments.*")
     void cannotLinkToExternalDmsOfWrongType(ExternalDmsAddressType dmsType, String externalCode, String path, String gitCommitHash)
     {
         ExternalDmsPermId edms = create(externalDms().withType(dmsType));
@@ -82,5 +186,4 @@ public class CreateLinkDataSetTest extends AbstractLinkDataSetTest
                 .withPath(path)
                 .withGitCommitHash(gitCommitHash)));
     }
-
 }
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetExternalDmsTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetExternalDmsTest.java
index 528d8401dd2..adccc67bf8f 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetExternalDmsTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetExternalDmsTest.java
@@ -25,9 +25,6 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDms;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDmsAddressType;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.id.ExternalDmsPermId;
 
-/**
- * @author anttil
- */
 public class GetExternalDmsTest extends AbstractExternalDmsTest
 {
 
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetLinkDataSetTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetLinkDataSetTest.java
new file mode 100644
index 00000000000..d853671e4fe
--- /dev/null
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetLinkDataSetTest.java
@@ -0,0 +1,98 @@
+package ch.ethz.sis.openbis.systemtest.asapi.v3;
+
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.testng.annotations.Test;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSet;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.LinkedData;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.id.DataSetPermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDmsAddressType;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.id.ExternalDmsPermId;
+
+public class GetLinkDataSetTest extends AbstractLinkDataSetTest
+{
+
+    @Test
+    void legacyFieldsAreProperlyPopulatedWithOpenBISBasedDataSets()
+    {
+        String externalCode = uuid();
+        ExternalDmsPermId openbis = create(externalDms().withType(ExternalDmsAddressType.OPENBIS));
+        DataSetPermId id = create(linkDataSet().with(copyAt(openbis).withExternalCode(externalCode)));
+        DataSet dataSet = get(id);
+        assertThat(dataSet.getLinkedData().getExternalCode(), is(externalCode));
+        assertThat(dataSet.getLinkedData().getExternalDms(), isSimilarTo(get(openbis)));
+    }
+
+    @Test
+    void legacyFieldsAreProperlyPopulatedWithURLBasedDataSets()
+    {
+        String externalCode = uuid();
+        ExternalDmsPermId url = create(externalDms().withType(ExternalDmsAddressType.URL));
+        DataSetPermId id = create(linkDataSet().with(copyAt(url).withExternalCode(externalCode)));
+        DataSet dataSet = get(id);
+        assertThat(dataSet.getLinkedData().getExternalCode(), is(externalCode));
+        assertThat(dataSet.getLinkedData().getExternalDms(), isSimilarTo(get(url)));
+    }
+
+    @Test
+    void legacyFieldsAreProperlyPopulatedWithFileSystemBasedDataSets()
+    {
+        ExternalDmsPermId fs = create(externalDms().withType(ExternalDmsAddressType.FILE_SYSTEM));
+        DataSetPermId id = create(linkDataSet().with(copyAt(fs).withPath("/path/to/dir")));
+        DataSet dataSet = get(id);
+        assertThat(dataSet.getLinkedData().getExternalCode(), is(LinkedData.NO_COPY_EXTERNAL_CODE));
+        assertThat(dataSet.getLinkedData().getExternalDms(), is(LinkedData.NO_COPY_EXTERNAL_DMS));
+    }
+
+    @Test
+    void legacyFieldsAreProperlyPopulatedWhenThereAreNoCopies()
+    {
+        DataSetPermId id = create(linkDataSet());
+        DataSet dataSet = get(id);
+        assertThat(dataSet.getLinkedData().getExternalCode(), is(LinkedData.NO_COPY_EXTERNAL_CODE));
+        assertThat(dataSet.getLinkedData().getExternalDms(), is(LinkedData.NO_COPY_EXTERNAL_DMS));
+    }
+
+    @Test
+    void legacyFieldsAreProperlyPopulatedWithMultipleCopiesThatAreAllFileBased()
+    {
+        ExternalDmsPermId fs1 = create(externalDms().withType(ExternalDmsAddressType.FILE_SYSTEM));
+        ExternalDmsPermId fs2 = create(externalDms().withType(ExternalDmsAddressType.FILE_SYSTEM));
+
+        DataSetPermId id = create(linkDataSet().with(
+                copyAt(fs1).withPath("/path/to/dir"),
+                copyAt(fs1).withPath("/path/to/dir2"),
+                copyAt(fs2).withPath("/path/to/dir")));
+        DataSet dataSet = get(id);
+
+        assertThat(dataSet.getLinkedData().getContentCopies().size(), is(3));
+        assertThat(dataSet.getLinkedData().getExternalCode(), is(LinkedData.NO_COPY_EXTERNAL_CODE));
+        assertThat(dataSet.getLinkedData().getExternalDms(), is(LinkedData.NO_COPY_EXTERNAL_DMS));
+    }
+
+    @Test
+    void legacyFieldsAreProperlyPopulatedWithMultipleCopiesSomeOfWhichResideInOpenBISOrInUrl()
+    {
+        String code = uuid();
+        ExternalDmsPermId openbis = create(externalDms().withType(ExternalDmsAddressType.OPENBIS));
+        ExternalDmsPermId url = create(externalDms().withType(ExternalDmsAddressType.URL));
+        ExternalDmsPermId fs1 = create(externalDms().withType(ExternalDmsAddressType.FILE_SYSTEM));
+        ExternalDmsPermId fs2 = create(externalDms().withType(ExternalDmsAddressType.FILE_SYSTEM));
+
+        DataSetPermId id = create(linkDataSet().with(
+                copyAt(fs1).withPath("/path/to/dir"),
+                copyAt(fs1).withPath("/path/to/dir2"),
+                copyAt(fs2).withPath("/path/to/dir"),
+                copyAt(openbis).withExternalCode(code),
+                copyAt(url).withExternalCode(code)));
+        DataSet dataSet = get(id);
+
+        assertThat(dataSet.getLinkedData().getContentCopies().size(), is(5));
+        assertThat(dataSet.getLinkedData().getExternalCode(), is(code));
+        assertThat(dataSet.getLinkedData().getExternalDms(), anyOf(isSimilarTo(get(url)), isSimilarTo(get(openbis))));
+    }
+
+}
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/LinkedData.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/LinkedData.java
index 6c21c94f2e2..5e27beea33a 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/LinkedData.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/LinkedData.java
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.LinkedDataFetchOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDms;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDmsAddressType;
 import ch.ethz.sis.openbis.generic.asapi.v3.exceptions.NotFetchedException;
 import ch.systemsx.cisd.base.annotation.JsonObject;
 import ch.systemsx.cisd.common.annotation.TechPreview;
@@ -35,6 +36,22 @@ public class LinkedData implements Serializable
 {
     private static final long serialVersionUID = 1L;
 
+    public static final String NO_COPY_EXTERNAL_CODE = "no copies";
+
+    public static final ExternalDms NO_COPY_EXTERNAL_DMS;
+
+    static
+    {
+        ExternalDms nced = new ExternalDms();
+        nced.setAddress("");
+        nced.setAddressType(ExternalDmsAddressType.URL);
+        nced.setCode("no copies");
+        nced.setLabel("");
+        nced.setOpenbis(false);
+        nced.setUrlTemplate("");
+        NO_COPY_EXTERNAL_DMS = nced;
+    }
+
     @JsonProperty
     private LinkedDataFetchOptions fetchOptions;
 
@@ -44,6 +61,7 @@ public class LinkedData implements Serializable
     @JsonProperty
     private ExternalDms externalDms;
 
+    @JsonIgnore
     private List<ContentCopy> contentCopies;
 
     // Method automatically generated with DtoGenerator
@@ -92,6 +110,7 @@ public class LinkedData implements Serializable
     }
 
     @TechPreview
+    @JsonIgnore
     public List<ContentCopy> getContentCopies()
     {
         return contentCopies;
diff --git a/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt b/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt
index cb6b4c3e3f1..398f2c95560 100644
--- a/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt
+++ b/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt
@@ -1550,4 +1550,6 @@ getSerialversionuid
 setContentCopies
 setExternalId
 setGitCommitHash
-ContentCopy
\ No newline at end of file
+ContentCopy
+NO_COPY_EXTERNAL_CODE
+NO_COPY_EXTERNAL_DMS
\ No newline at end of file
-- 
GitLab