diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/EntityRetriever.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/EntityRetriever.java
index 5d1edc68ec251373d433cd16f3f27fd90b38ba05..0ecb7919b33a3d2fc7e95f540af27da0f26a720a 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/EntityRetriever.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/EntityRetriever.java
@@ -33,13 +33,10 @@ import org.apache.log4j.Logger;
 import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.ICodeHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult;
-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.LinkedData;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.DataSetFetchOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.Experiment;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.fetchoptions.ExperimentFetchOptions;
-import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDms;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.Material;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.fetchoptions.MaterialFetchOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.search.MaterialSearchCriteria;
@@ -308,16 +305,6 @@ public class EntityRetriever implements IEntityRetriever
         {
             Node<DataSet> dataSetNode = new Node<DataSet>(dataSet);
             graph.addEdge(expNode, dataSetNode, new Edge(CONNECTION));
-            LinkedData linkedData = dataSet.getLinkedData();
-            if (linkedData != null)
-            {
-                List<ContentCopy> contentCopies = linkedData.getContentCopies();
-                for (ContentCopy contentCopy : contentCopies)
-                {
-                    ExternalDms externalDms = contentCopy.getExternalDms();
-//                Node<ExternalDms> dmsNode = new Node<ExternalDms>(externalDms);
-                }
-            }
             addChildAndContainedDataSets(dataSetNode);
         }
     }
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/MasterDataExtractor.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/MasterDataExtractor.java
index 420e6ee2589cfa9efb3c199baf016cb0b4308f95..5eec8faf55abd8dbf99284782a765ff5adfe1d5e 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/MasterDataExtractor.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/MasterDataExtractor.java
@@ -22,18 +22,11 @@ import java.io.StringWriter;
 import java.util.List;
 import java.util.Set;
 
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
 import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.ICodeHolder;
@@ -66,6 +59,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.Vocabulary;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.VocabularyTerm;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.fetchoptions.VocabularyFetchOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.search.VocabularySearchCriteria;
+import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel;
 import ch.systemsx.cisd.common.shared.basic.string.CommaSeparatedListBuilder;
 import ch.systemsx.cisd.openbis.generic.server.jython.api.v1.DataType;
 import ch.systemsx.cisd.openbis.generic.server.jython.api.v1.IFileFormatTypeImmutable;
@@ -111,44 +105,32 @@ public class MasterDataExtractor
 
     public String fetchAsXmlString() throws ParserConfigurationException, TransformerException
     {
-        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
-        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
-        Document doc = docBuilder.newDocument();
-        docFactory.setNamespaceAware(true);
-        Element rootElement = doc.createElementNS("https://sis.id.ethz.ch/software/#openbis/xmdterms/", "xmd:masterData");
-        rootElement.setAttribute("xmlns:xmd", "https://sis.id.ethz.ch/software/#openbis/xmdterms/");
-        doc.appendChild(rootElement);
-
-        appendFileFormatTypes(doc, rootElement);
-
-        appendValidationPlugins(doc, rootElement);
-        appendVocabularies(doc, rootElement);
-
-        appendPropertyTypes(doc, rootElement);
-        appendSampleTypes(doc, rootElement);
-        appendExperimentTypes(doc, rootElement);
-        appendDataSetTypes(doc, rootElement);
-        appendMaterialTypes(doc, rootElement);
-
-        appendExternalDataManagementSystems(doc, rootElement);
-
-        TransformerFactory transformerFactory = TransformerFactory.newInstance();
-        Transformer transformer = transformerFactory.newTransformer();
-        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
-        transformer.setOutputProperty(OutputKeys.METHOD, "xml");
-        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
-        transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS,
-                "validationPlugin");
-        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
-
-        DOMSource source = new DOMSource(doc);
         StringWriter writer = new StringWriter();
-        StreamResult result = new StreamResult(writer);
-        transformer.transform(source, result);
-        return writer.toString();
+        try
+        {
+            XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
+            XMLStreamWriter xmlStreamWriter = xmlOutputFactory.createXMLStreamWriter(writer);
+            // xmlStreamWriter.writeStartDocument();
+            xmlStreamWriter.writeStartElement("xmd:masterData");
+            xmlStreamWriter.writeAttribute("xmlns:xmd", "https://sis.id.ethz.ch/software/#openbis/xmdterms/");
+            appendFileFormatTypes(xmlStreamWriter);
+            appendValidationPlugins(xmlStreamWriter);
+            appendVocabularies(xmlStreamWriter);
+            appendPropertyTypes(xmlStreamWriter);
+            appendSampleTypes(xmlStreamWriter);
+            appendExperimentTypes(xmlStreamWriter);
+            appendDataSetTypes(xmlStreamWriter);
+            appendMaterialTypes(xmlStreamWriter);
+            appendExternalDataManagementSystems(xmlStreamWriter);
+            xmlStreamWriter.writeEndElement();
+            return writer.toString();
+        } catch (Exception e)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(e);
+        }
     }
 
-    protected void appendExternalDataManagementSystems(Document doc, Element rootElement)
+    private void appendExternalDataManagementSystems(XMLStreamWriter out) throws XMLStreamException
     {
         ExternalDmsSearchCriteria searchCriteria = new ExternalDmsSearchCriteria();
         ExternalDmsFetchOptions fetchOptions = new ExternalDmsFetchOptions();
@@ -156,21 +138,29 @@ public class MasterDataExtractor
                 v3Api.searchExternalDataManagementSystems(sessionToken, searchCriteria, fetchOptions).getObjects();
         if (externalDataManagementSystems.isEmpty() == false)
         {
-            Element externalDataManagementSystemsElement = doc.createElement("xmd:externalDataManagementSystems");
-            rootElement.appendChild(externalDataManagementSystemsElement);
+            out.writeStartElement("xmd:externalDataManagementSystems");
             for (ExternalDms externalDms : externalDataManagementSystems)
             {
-                Element externalDmsElement = doc.createElement("xmd:externalDataManagementSystem");
-                externalDmsElement.setAttribute("code", externalDms.getCode());
-                externalDmsElement.setAttribute("label", externalDms.getLabel());
-                externalDmsElement.setAttribute("address", externalDms.getAddress());
-                externalDmsElement.setAttribute("addressType", externalDms.getAddressType().toString());
-                externalDataManagementSystemsElement.appendChild(externalDmsElement);
+                out.writeStartElement("xmd:externalDataManagementSystem");
+                writeAttributeIfNotNull(out, "code", externalDms.getCode());
+                writeAttributeIfNotNull(out, "label", externalDms.getLabel());
+                writeAttributeIfNotNull(out, "address", externalDms.getAddress());
+                writeAttributeIfNotNull(out, "addressType", externalDms.getAddressType().toString());
+                out.writeEndElement();
             }
+            out.writeEndElement();
+        }
+    }
+
+    private void writeAttributeIfNotNull(XMLStreamWriter out, String key, String value) throws XMLStreamException
+    {
+        if (value != null)
+        {
+            out.writeAttribute(key, value);
         }
     }
 
-    private void appendValidationPlugins(Document doc, Element rootElement)
+    private void appendValidationPlugins(XMLStreamWriter out) throws XMLStreamException
     {
         PluginFetchOptions fetchOptions = new PluginFetchOptions();
         fetchOptions.withScript();
@@ -179,22 +169,25 @@ public class MasterDataExtractor
         {
             return;
         }
-        Element pluginsElement = doc.createElement("xmd:validationPlugins");
-        rootElement.appendChild(pluginsElement);
+        out.writeStartElement("xmd:validationPlugins");
         for (Plugin plugin : plugins)
         {
-            Element pluginElement = doc.createElement("xmd:validationPlugin");
-            pluginElement.setAttribute("name", plugin.getName());
-            pluginElement.setAttribute("description", plugin.getDescription());
-            pluginElement.setAttribute("type", plugin.getPluginType().toString());
-            pluginElement.setAttribute("entityKind", getEntityKind(plugin));
-            pluginElement.setAttribute("isAvailable", String.valueOf(plugin.isAvailable()));
-            pluginElement.appendChild(doc.createCDATASection(plugin.getScript()));
-            pluginsElement.appendChild(pluginElement);
+            out.writeStartElement("xmd:validationPlugin");
+            writeAttributeIfNotNull(out, "name", plugin.getName());
+            writeAttributeIfNotNull(out, "description", plugin.getDescription());
+            writeAttributeIfNotNull(out, "type", plugin.getPluginType().toString());
+            writeAttributeIfNotNull(out, "entityKind", getEntityKind(plugin));
+            writeAttributeIfNotNull(out, "isAvailable", String.valueOf(plugin.isAvailable()));
+            if (plugin.getScript() != null)
+            {
+                out.writeCData(plugin.getScript());
+            }
+            out.writeEndElement();
         }
+        out.writeEndElement();
     }
 
-    protected String getEntityKind(Plugin plugin)
+    private String getEntityKind(Plugin plugin)
     {
         String entityKind = "All";
         Set<EntityKind> entityKinds = plugin.getEntityKinds();
@@ -210,24 +203,24 @@ public class MasterDataExtractor
         return entityKind;
     }
 
-    private void appendFileFormatTypes(Document doc, Element rootElement)
+    private void appendFileFormatTypes(XMLStreamWriter out) throws XMLStreamException
     {
         List<IFileFormatTypeImmutable> fileFormatTypes = masterDataRegistrationTransaction.listFileFormatTypes();
         if (fileFormatTypes.size() > 0)
         {
-            Element fileFormatTypesElement = doc.createElement("xmd:fileFormatTypes");
-            rootElement.appendChild(fileFormatTypesElement);
+            out.writeStartElement("xmd:fileFormatTypes");
             for (IFileFormatTypeImmutable fileFormatType : fileFormatTypes)
             {
-                Element fileFormatTypeElement = doc.createElement("xmd:fileFormatType");
-                fileFormatTypeElement.setAttribute("code", fileFormatType.getCode());
-                fileFormatTypeElement.setAttribute("description", fileFormatType.getDescription());
-                fileFormatTypesElement.appendChild(fileFormatTypeElement);
+                out.writeStartElement("xmd:fileFormatType");
+                writeAttributeIfNotNull(out, "code", fileFormatType.getCode());
+                writeAttributeIfNotNull(out, "description", fileFormatType.getDescription());
+                out.writeEndElement();
             }
+            out.writeEndElement();
         }
     }
 
-    private void appendPropertyTypes(Document doc, Element rootElement)
+    private void appendPropertyTypes(XMLStreamWriter out) throws XMLStreamException
     {
         PropertyTypeFetchOptions fetchOptions = new PropertyTypeFetchOptions();
         fetchOptions.withMaterialType();
@@ -237,8 +230,8 @@ public class MasterDataExtractor
         {
             return;
         }
-        Element propertyTypesElement = doc.createElement("xmd:propertyTypes");
-        rootElement.appendChild(propertyTypesElement);
+        out.writeStartElement("xmd:propertyTypes");
+
         for (PropertyType propertyType : propertyTypes)
         {
             Boolean internalNameSpace = propertyType.isInternalNameSpace();
@@ -246,32 +239,33 @@ public class MasterDataExtractor
                     (internalNameSpace && propertyType.getCode().startsWith(INTERNAL_NAMESPACE_PREFIX))
                             ? CodeConverter.tryToDatabase(propertyType.getCode())
                             : propertyType.getCode();
-            Element typeElement = doc.createElement("xmd:propertyType");
-            typeElement.setAttribute("code", code);
-            typeElement.setAttribute("label", propertyType.getLabel());
-            typeElement.setAttribute("dataType", propertyType.getDataType().name());
-            typeElement.setAttribute("internalNamespace", String.valueOf(internalNameSpace));
-            typeElement.setAttribute("managedInternally", String.valueOf(propertyType.isManagedInternally()));
-            typeElement.setAttribute("description", propertyType.getDescription());
+            out.writeStartElement("xmd:propertyType");
+            writeAttributeIfNotNull(out, "code", code);
+            writeAttributeIfNotNull(out, "label", propertyType.getLabel());
+            writeAttributeIfNotNull(out, "dataType", propertyType.getDataType().name());
+            writeAttributeIfNotNull(out, "internalNamespace", String.valueOf(internalNameSpace));
+            writeAttributeIfNotNull(out, "managedInternally", String.valueOf(propertyType.isManagedInternally()));
+            writeAttributeIfNotNull(out, "description", propertyType.getDescription());
             if (propertyType.getDataType().name().equals(DataType.CONTROLLEDVOCABULARY.name()))
             {
-                typeElement.setAttribute("vocabulary", propertyType.getVocabulary().getCode());
+                writeAttributeIfNotNull(out, "vocabulary", propertyType.getVocabulary().getCode());
             } else if (propertyType.getDataType().name().equals(DataType.MATERIAL.name()))
             {
                 if (propertyType.getMaterialType() != null)
                 {
-                    typeElement.setAttribute("material", propertyType.getMaterialType().getCode());
+                    writeAttributeIfNotNull(out, "material", propertyType.getMaterialType().getCode());
                 } else
                 {
                     // for properties like "inhibitor_of" where it is of Material of Any Type
-                    typeElement.setAttribute("material", "");
+                    writeAttributeIfNotNull(out, "material", "");
                 }
             }
-            propertyTypesElement.appendChild(typeElement);
+            out.writeEndElement();
         }
+        out.writeEndElement();
     }
 
-    private void appendVocabularies(Document doc, Element rootElement)
+    private void appendVocabularies(XMLStreamWriter out) throws XMLStreamException
     {
         VocabularyFetchOptions fetchOptions = new VocabularyFetchOptions();
         fetchOptions.withTerms();
@@ -280,34 +274,34 @@ public class MasterDataExtractor
         {
             return;
         }
-        Element vocabsElement = doc.createElement("xmd:controlledVocabularies");
-        rootElement.appendChild(vocabsElement);
+        out.writeStartElement("xmd:controlledVocabularies");
         for (Vocabulary vocabulary : vocabularies)
         {
-            Element vocabElement = doc.createElement("xmd:controlledVocabulary");
+            out.writeStartElement("xmd:controlledVocabulary");
             String code = vocabulary.isInternalNameSpace()
                     && vocabulary.getCode().startsWith(INTERNAL_NAMESPACE_PREFIX) ? CodeConverter.tryToDatabase(vocabulary.getCode())
                             : vocabulary.getCode();
-            vocabElement.setAttribute("code", code);
-            vocabElement.setAttribute("description", vocabulary.getDescription());
+            writeAttributeIfNotNull(out, "code", code);
+            writeAttributeIfNotNull(out, "description", vocabulary.getDescription());
             String urlTemplate = vocabulary.getUrlTemplate();
-            vocabElement.setAttribute("urlTemplate", urlTemplate);
-            vocabElement.setAttribute("managedInternally", String.valueOf(vocabulary.isManagedInternally()));
-            vocabElement.setAttribute("internalNamespace", String.valueOf(vocabulary.isInternalNameSpace()));
-            vocabElement.setAttribute("chosenFromList", String.valueOf(vocabulary.isChosenFromList()));
-            vocabsElement.appendChild(vocabElement);
+            writeAttributeIfNotNull(out, "urlTemplate", urlTemplate);
+            writeAttributeIfNotNull(out, "managedInternally", String.valueOf(vocabulary.isManagedInternally()));
+            writeAttributeIfNotNull(out, "internalNamespace", String.valueOf(vocabulary.isInternalNameSpace()));
+            writeAttributeIfNotNull(out, "chosenFromList", String.valueOf(vocabulary.isChosenFromList()));
 
             for (VocabularyTerm term : vocabulary.getTerms())
             {
-                Element termElement = doc.createElement("term");
-                termElement.setAttribute("code", term.getCode());
-                termElement.setAttribute("label", term.getLabel());
-                termElement.setAttribute("description", term.getDescription());
-                termElement.setAttribute("ordinal", String.valueOf(term.getOrdinal()));
-                termElement.setAttribute("url", createUrl(urlTemplate, term.getCode()));
-                vocabElement.appendChild(termElement);
+                out.writeStartElement("xmd:term");
+                writeAttributeIfNotNull(out, "code", term.getCode());
+                writeAttributeIfNotNull(out, "label", term.getLabel());
+                writeAttributeIfNotNull(out, "description", term.getDescription());
+                writeAttributeIfNotNull(out, "ordinal", String.valueOf(term.getOrdinal()));
+                writeAttributeIfNotNull(out, "url", createUrl(urlTemplate, term.getCode()));
+                out.writeEndElement();
             }
+            out.writeEndElement();
         }
+        out.writeEndElement();
     }
 
     private String createUrl(String urlTemplate, String code)
@@ -320,7 +314,7 @@ public class MasterDataExtractor
         return url.replaceAll(BasicConstant.VOCABULARY_URL_TEMPLATE_TERM_PATTERN, code);
     }
 
-    private void appendMaterialTypes(Document doc, Element rootElement)
+    private void appendMaterialTypes(XMLStreamWriter out) throws XMLStreamException
     {
         MaterialTypeFetchOptions fetchOptions = new MaterialTypeFetchOptions();
         fetchOptions.withPropertyAssignments().withPropertyType();
@@ -331,16 +325,18 @@ public class MasterDataExtractor
         {
             return;
         }
-        Element typesElement = doc.createElement("xmd:materialTypes");
-        rootElement.appendChild(typesElement);
+        out.writeStartElement("xmd:materialTypes");
         for (MaterialType type : types)
         {
-            Element typeElement = createTypeElement(doc, typesElement, "xmd:materialType", type);
-            typeElement.setAttribute("validationPlugin", type.getValidationPlugin() != null ? type.getValidationPlugin().getName() : null);
+            writeTypeElement(out, "xmd:materialType", type);
+            writeAttributeIfNotNull(out, "validationPlugin", type.getValidationPlugin() != null ? type.getValidationPlugin().getName() : null);
+            appendPropertyAssignments(out, type.getPropertyAssignments());
+            out.writeEndElement();
         }
+        out.writeEndElement();
     }
 
-    private void appendExperimentTypes(Document doc, Element rootElement)
+    private void appendExperimentTypes(XMLStreamWriter out) throws XMLStreamException
     {
         ExperimentTypeFetchOptions fetchOptions = new ExperimentTypeFetchOptions();
         fetchOptions.withPropertyAssignments().withPropertyType();
@@ -351,16 +347,18 @@ public class MasterDataExtractor
         {
             return;
         }
-        Element typesElement = doc.createElement("xmd:collectionTypes");
-        rootElement.appendChild(typesElement);
+        out.writeStartElement("xmd:collectionTypes");
         for (ExperimentType type : types)
         {
-            Element typeElement = createTypeElement(doc, typesElement, "xmd:collectionType", type);
-            typeElement.setAttribute("validationPlugin", type.getValidationPlugin() != null ? type.getValidationPlugin().getName() : null);
+            writeTypeElement(out, "xmd:collectionType", type);
+            writeAttributeIfNotNull(out, "validationPlugin", type.getValidationPlugin() != null ? type.getValidationPlugin().getName() : null);
+            appendPropertyAssignments(out, type.getPropertyAssignments());
+            out.writeEndElement();
         }
+        out.writeEndElement();
     }
 
-    private void appendSampleTypes(Document doc, Element rootElement)
+    private void appendSampleTypes(XMLStreamWriter out) throws XMLStreamException
     {
         SampleTypeFetchOptions fetchOptions = new SampleTypeFetchOptions();
         fetchOptions.withPropertyAssignments().withPropertyType();
@@ -371,23 +369,25 @@ public class MasterDataExtractor
         {
             return;
         }
-        Element typesElement = doc.createElement("xmd:objectTypes");
-        rootElement.appendChild(typesElement);
+        out.writeStartElement("xmd:objectTypes");
         for (SampleType type : types)
         {
-            Element typeElement = createTypeElement(doc, typesElement, "xmd:objectType", type);
-            typeElement.setAttribute("listable", String.valueOf(type.isListable()));
-            typeElement.setAttribute("showContainer", String.valueOf(type.isShowContainer()));
-            typeElement.setAttribute("showParents", String.valueOf(type.isShowParents()));
-            typeElement.setAttribute("showParentMetadata", String.valueOf(type.isShowParentMetadata()));
-            typeElement.setAttribute("subcodeUnique", String.valueOf(type.isSubcodeUnique()));
-            typeElement.setAttribute("autoGeneratedCode", String.valueOf(type.isAutoGeneratedCode()));
-            typeElement.setAttribute("generatedCodePrefix", type.getGeneratedCodePrefix());
-            typeElement.setAttribute("validationPlugin", type.getValidationPlugin() != null ? type.getValidationPlugin().getName() : null);
+            writeTypeElement(out, "xmd:objectType", type);
+            writeAttributeIfNotNull(out, "listable", String.valueOf(type.isListable()));
+            writeAttributeIfNotNull(out, "showContainer", String.valueOf(type.isShowContainer()));
+            writeAttributeIfNotNull(out, "showParents", String.valueOf(type.isShowParents()));
+            writeAttributeIfNotNull(out, "showParentMetadata", String.valueOf(type.isShowParentMetadata()));
+            writeAttributeIfNotNull(out, "subcodeUnique", String.valueOf(type.isSubcodeUnique()));
+            writeAttributeIfNotNull(out, "autoGeneratedCode", String.valueOf(type.isAutoGeneratedCode()));
+            writeAttributeIfNotNull(out, "generatedCodePrefix", type.getGeneratedCodePrefix());
+            writeAttributeIfNotNull(out, "validationPlugin", type.getValidationPlugin() != null ? type.getValidationPlugin().getName() : null);
+            appendPropertyAssignments(out, type.getPropertyAssignments());
+            out.writeEndElement();
         }
+        out.writeEndElement();
     }
 
-    private void appendDataSetTypes(Document doc, Element rootElement)
+    private void appendDataSetTypes(XMLStreamWriter out) throws XMLStreamException
     {
         DataSetTypeFetchOptions fetchOptions = new DataSetTypeFetchOptions();
         fetchOptions.withPropertyAssignments().withPropertyType();
@@ -398,50 +398,49 @@ public class MasterDataExtractor
         {
             return;
         }
-        Element typesElement = doc.createElement("xmd:dataSetTypes");
-        rootElement.appendChild(typesElement);
+        out.writeStartElement("xmd:dataSetTypes");
         for (DataSetType type : types)
         {
-            Element typeElement = createTypeElement(doc, typesElement, "xmd:dataSetType", type);
-            typeElement.setAttribute("mainDataSetPattern", type.getMainDataSetPattern());
-            typeElement.setAttribute("mainDataSetPath", type.getMainDataSetPath());
-            typeElement.setAttribute("deletionDisallowed", String.valueOf(type.isDisallowDeletion()));
-            typeElement.setAttribute("validationPlugin", type.getValidationPlugin() != null ? type.getValidationPlugin().getName() : null);
+            writeTypeElement(out, "xmd:dataSetType", type);
+            writeAttributeIfNotNull(out, "mainDataSetPattern", type.getMainDataSetPattern());
+            writeAttributeIfNotNull(out, "mainDataSetPath", type.getMainDataSetPath());
+            writeAttributeIfNotNull(out, "deletionDisallowed", String.valueOf(type.isDisallowDeletion()));
+            writeAttributeIfNotNull(out, "validationPlugin", type.getValidationPlugin() != null ? type.getValidationPlugin().getName() : null);
+            appendPropertyAssignments(out, type.getPropertyAssignments());
+            out.writeEndElement();
         }
+        out.writeEndElement();
     }
 
-    private <T extends ICodeHolder & IDescriptionHolder & IPropertyAssignmentsHolder> Element createTypeElement(
-            Document doc, Element rootElement, String elementType, T type)
+    private <T extends ICodeHolder & IDescriptionHolder & IPropertyAssignmentsHolder> void writeTypeElement(
+            XMLStreamWriter out, String elementType, T type) throws XMLStreamException
     {
-        Element typeElement = doc.createElement(elementType);
-        rootElement.appendChild(typeElement);
-        typeElement.setAttribute("code", type.getCode());
-        typeElement.setAttribute("description", type.getDescription());
-        appendPropertyAssignments(doc, typeElement, type.getPropertyAssignments());
-        return typeElement;
+        out.writeStartElement(elementType);
+        writeAttributeIfNotNull(out, "code", type.getCode());
+        writeAttributeIfNotNull(out, "description", type.getDescription());
     }
 
-    private void appendPropertyAssignments(Document doc, Element rootElement, List<PropertyAssignment> propertyAssignments)
+    private void appendPropertyAssignments(XMLStreamWriter out, List<PropertyAssignment> propertyAssignments) throws XMLStreamException
     {
-        Element propertyAssignmentsElement = doc.createElement("xmd:propertyAssignments");
-        rootElement.appendChild(propertyAssignmentsElement);
+        out.writeStartElement("xmd:propertyAssignments");
         for (PropertyAssignment propertyAssignment : propertyAssignments)
         {
-            Element propertyAssignmentElement = doc.createElement("xmd:propertyAssignment");
-            propertyAssignmentsElement.appendChild(propertyAssignmentElement);
-            propertyAssignmentElement.setAttribute("propertyTypeCode", propertyAssignment.getPropertyType().getCode());
-            propertyAssignmentElement.setAttribute("ordinal", String.valueOf(propertyAssignment.getOrdinal()));
-            propertyAssignmentElement.setAttribute("section", propertyAssignment.getSection());
-            propertyAssignmentElement.setAttribute("showInEdit", String.valueOf(propertyAssignment.isShowInEditView()));
-            propertyAssignmentElement.setAttribute("mandatory", String.valueOf(propertyAssignment.isMandatory()));
-            propertyAssignmentElement.setAttribute("showRawValueInForms", String.valueOf(propertyAssignment.isShowRawValueInForms()));
+            out.writeStartElement("xmd:propertyAssignment");
+            writeAttributeIfNotNull(out, "propertyTypeCode", propertyAssignment.getPropertyType().getCode());
+            writeAttributeIfNotNull(out, "ordinal", String.valueOf(propertyAssignment.getOrdinal()));
+            writeAttributeIfNotNull(out, "section", propertyAssignment.getSection());
+            writeAttributeIfNotNull(out, "showInEdit", String.valueOf(propertyAssignment.isShowInEditView()));
+            writeAttributeIfNotNull(out, "mandatory", String.valueOf(propertyAssignment.isMandatory()));
+            writeAttributeIfNotNull(out, "showRawValueInForms", String.valueOf(propertyAssignment.isShowRawValueInForms()));
             Plugin plugin = propertyAssignment.getPlugin();
             if (plugin != null)
             {
-                propertyAssignmentElement.setAttribute("plugin", plugin.getPermId().getPermId());
-                propertyAssignmentElement.setAttribute("pluginType", plugin.getPluginType().toString());
+                writeAttributeIfNotNull(out, "plugin", plugin.getPermId().getPermId());
+                writeAttributeIfNotNull(out, "pluginType", plugin.getPluginType().toString());
             }
+            out.writeEndElement();
         }
+        out.writeEndElement();
     }
 
 }
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/HarvesterMaintenanceTask.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/HarvesterMaintenanceTask.java
index b12da97a0687b94d33a29d691871fbfc54303917..7e04299ff54291e974b0321f336051b124ef622c 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/HarvesterMaintenanceTask.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/HarvesterMaintenanceTask.java
@@ -46,13 +46,13 @@ import org.apache.log4j.Logger;
 import org.apache.log4j.PatternLayout;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
+import ch.ethz.sis.openbis.generic.dssapi.v3.IDataStoreServerApi;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.common.SyncEntityKind;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.config.ConfigReader;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.config.SyncConfig;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.config.SynchronizationConfigReader;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.synchronizer.EntitySynchronizer;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.synchronizer.SynchronizationContext;
-import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.synchronizer.translator.INameTranslator;
 import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel;
 import ch.systemsx.cisd.cifex.shared.basic.UserFailureException;
 import ch.systemsx.cisd.common.filesystem.FileUtilities;
@@ -96,6 +96,8 @@ public class HarvesterMaintenanceTask<T extends DataSetInformation> implements I
 
     private IApplicationServerApi v3Api;
     
+    private IDataStoreServerApi v3DssApi;
+
     private DataSetProcessingContext context;
 
     private File harvesterConfigFile;
@@ -104,6 +106,7 @@ public class HarvesterMaintenanceTask<T extends DataSetInformation> implements I
 
     private String dataStoreCode;
 
+
     private static class Timestamps
     {
         final Date lastIncSyncTimestamp;
@@ -123,6 +126,8 @@ public class HarvesterMaintenanceTask<T extends DataSetInformation> implements I
     {
         service = ServiceProvider.getOpenBISService();
         v3Api = ServiceProvider.getV3ApplicationService();
+        v3DssApi = ServiceProvider.getDssServiceInternalV3();
+
         context = new DataSetProcessingContext(null, null, null, null, null, null);
         dataStoreCode = getConfigProvider().getDataStoreCode();
         storeRoot = new File(DssPropertyParametersUtil.loadServiceProperties().getProperty(PluginTaskInfoProvider.STOREROOT_DIR_KEY));
@@ -216,6 +221,7 @@ public class HarvesterMaintenanceTask<T extends DataSetInformation> implements I
         SynchronizationContext syncContext = new SynchronizationContext();
         syncContext.setService(service);
         syncContext.setV3Api(v3Api);
+        syncContext.setV3DssApi(v3DssApi);
         syncContext.setDataStoreCode(dataStoreCode);
         syncContext.setStoreRoot(storeRoot);
         syncContext.setLastSyncTimestamp(cutOffTimestamp);
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/AbstractRegistrationHolder.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/AbstractRegistrationHolder.java
new file mode 100644
index 0000000000000000000000000000000000000000..8cf1c1de3c574c2574ce53a1dfc91bdff7e724fe
--- /dev/null
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/AbstractRegistrationHolder.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019 ETH Zuerich, SIS
+ *
+ * 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.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.synchronizer;
+
+import java.util.Date;
+
+/**
+ * @author Franz-Josef Elmer
+ *
+ */
+public abstract class AbstractRegistrationHolder
+{
+    private String registrator;
+
+    private Date registrationTimestamp;
+
+    public String getRegistrator()
+    {
+        return registrator;
+    }
+
+    public void setRegistrator(String registrator)
+    {
+        this.registrator = registrator;
+    }
+
+    public Date getRegistrationTimestamp()
+    {
+        return registrationTimestamp;
+    }
+
+    public void setRegistrationTimestamp(Date registrationTimestamp)
+    {
+        this.registrationTimestamp = registrationTimestamp;
+    }
+
+
+}
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/EntitySynchronizer.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/EntitySynchronizer.java
index 032a31a0d2a05b83dd1e0692a3f0e01d54384fc9..27acb973269c7458bdb03e941dce1c91f12b3efc 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/EntitySynchronizer.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/EntitySynchronizer.java
@@ -34,6 +34,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import javax.sql.DataSource;
 import javax.xml.xpath.XPathExpressionException;
 
 import org.apache.commons.codec.binary.Hex;
@@ -45,14 +46,22 @@ import org.w3c.dom.Document;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult;
+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.DataSetCreation;
 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.id.DataSetPermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.id.IDataSetId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.update.DataSetUpdate;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.deletion.id.IDeletionId;
 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.material.delete.MaterialDeletionOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.id.MaterialPermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.person.create.PersonCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.person.fetchoptions.PersonFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.person.id.PersonPermId;
 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.sample.delete.SampleDeletionOptions;
@@ -60,6 +69,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.fetchoptions.SampleFetchO
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.ISampleId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SamplePermId;
 import ch.ethz.sis.openbis.generic.dssapi.v3.IDataStoreServerApi;
+import ch.ethz.sis.openbis.generic.dssapi.v3.dto.dataset.create.FullDataSetCreation;
 import ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.DataSetFile;
 import ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.fetchoptions.DataSetFileFetchOptions;
 import ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.search.DataSetFileSearchCriteria;
@@ -87,14 +97,19 @@ import ch.systemsx.cisd.common.logging.Log4jSimpleLogger;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.impl.ConversionUtils;
 import ch.systemsx.cisd.openbis.dss.generic.shared.DataSetDirectoryProvider;
 import ch.systemsx.cisd.openbis.dss.generic.shared.DataSetProcessingContext;
+import ch.systemsx.cisd.openbis.dss.generic.shared.DataSourceQueryService;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IConfigProvider;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IDataSetDirectoryProvider;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IShareIdManager;
 import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider;
+import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.IDataSourceQueryService;
 import ch.systemsx.cisd.openbis.dss.generic.shared.utils.SegmentedStoreUtils;
+import ch.systemsx.cisd.openbis.generic.server.batch.BatchOperationExecutor;
+import ch.systemsx.cisd.openbis.generic.server.batch.IBatchOperation;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.BatchRegistrationResult;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.GenericEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
@@ -131,6 +146,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifierF
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifierFactory;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
+import net.lemnik.eodsql.QueryTool;
 
 /**
  * @author Ganime Betul Akin
@@ -144,7 +160,9 @@ public class EntitySynchronizer
     private final IEncapsulatedOpenBISService service;
 
     private final IApplicationServerApi v3Api;
-    
+
+    private final IDataStoreServerApi v3DssApi;
+
     private final DataSetProcessingContext context;
 
     private final Date lastSyncTimestamp;
@@ -167,6 +185,7 @@ public class EntitySynchronizer
     {
         service = synContext.getService();
         v3Api = synContext.getV3Api();
+        v3DssApi = synContext.getV3DssApi();
         dataStoreCode = synContext.getDataStoreCode();
         storeRoot = synContext.getStoreRoot();
         lastSyncTimestamp = synContext.getLastSyncTimestamp();
@@ -188,21 +207,212 @@ public class EntitySynchronizer
         registerMasterData(data.getMasterData());
         MultiKeyMap<String, String> newEntities = registerEntities(data);
         List<String> notSyncedAttachmentsHolders = registerAttachments(data, newEntities);
-
         registerDataSets(data, notSyncedAttachmentsHolders);
 
+        updateRegistratorAndRegistrationTimestamp(data);
+
         return data.getResourceListTimestamp();
     }
 
+    private void updateRegistratorAndRegistrationTimestamp(ResourceListParserData data)
+    {
+        Monitor monitor = new Monitor("Update registrator and registration timestamp", operationLog);
+        createMissingUsers(data, monitor);
+        DataSource dataSource = ServiceProvider.getDataSourceProvider().getDataSource("openbis-db");
+        IHarvesterQuery query = QueryTool.getQuery(dataSource, IHarvesterQuery.class);
+        Map<String, Long> userTechIdsByUserId = getUserTechIds(query);
+        updateMaterials(data.getMaterialsToProcess().values(), query, userTechIdsByUserId, monitor);
+        updateProjects(data.getProjectsToProcess().values(), query, userTechIdsByUserId, monitor);
+        updateExperiments(data.getExperimentsToProcess().values(), query, userTechIdsByUserId, monitor);
+        updateSamples(data.getSamplesToProcess().values(), query, userTechIdsByUserId, monitor);
+        updateDataSets(data.getDataSetsToProcess().values(), query, userTechIdsByUserId, monitor);
+    }
+
+    private Map<String, Long> getUserTechIds(IHarvesterQuery query)
+    {
+        Map<String, Long> userTechIdsByUserId = new HashMap<>();
+        List<PersonRecord> allUsers = query.listAllUsers();
+        for (PersonRecord personRecord : allUsers)
+        {
+            userTechIdsByUserId.put(personRecord.userId, personRecord.id);
+        }
+        return userTechIdsByUserId;
+    }
+
+    private void createMissingUsers(ResourceListParserData data, Monitor monitor)
+    {
+        Set<String> registrators = new HashSet<>();
+        addRegistrators(registrators, data.getMaterialsToProcess().values());
+        addRegistrators(registrators, data.getProjectsToProcess().values());
+        addRegistrators(registrators, data.getExperimentsToProcess().values());
+        addRegistrators(registrators, data.getSamplesToProcess().values());
+        addRegistrators(registrators, data.getDataSetsToProcess().values());
+        Set<String> knownPersons = v3Api.getPersons(service.getSessionToken(),
+                registrators.stream().map(PersonPermId::new).collect(Collectors.toList()),
+                new PersonFetchOptions()).keySet().stream().map(p -> p.toString()).collect(Collectors.toSet());
+        List<PersonCreation> personCreations = new ArrayList<>();
+        for (String registrator : registrators)
+        {
+            if (knownPersons.contains(registrator) == false)
+            {
+                PersonCreation personCreation = new PersonCreation();
+                personCreation.setUserId(registrator);
+                personCreations.add(personCreation);
+            }
+        }
+        monitor.log(personCreations.size() + " from " + registrators.size() + " registrators are new.");
+        if (personCreations.isEmpty() == false)
+        {
+            v3Api.createPersons(service.getSessionToken(), personCreations);
+        }
+    }
+
+    private void updateMaterials(Collection<IncomingMaterial> materials, IHarvesterQuery query,
+            Map<String, Long> userTechIdsByUserId, Monitor monitor)
+    {
+        monitor.log("update " + materials.size() + " materials");
+        List<RegistrationDTO> registrations = new ArrayList<>();
+        for (IncomingMaterial incomingMaterial : materials)
+        {
+//            addRegistration(registrations, incomingMaterial.getPermID(), incomingMaterial, userTechIdsByUserId);
+        }
+        query.updateProjectRegistrations(registrations);
+    }
+
+    private void updateProjects(Collection<IncomingProject> projects, IHarvesterQuery query,
+            Map<String, Long> userTechIdsByUserId, Monitor monitor)
+    {
+        monitor.log("update " + projects.size() + " projects");
+        List<RegistrationDTO> registrations = new ArrayList<>();
+        for (IncomingProject incomingProject : projects)
+        {
+            addRegistration(registrations, incomingProject.getPermID(), incomingProject, userTechIdsByUserId);
+        }
+        query.updateProjectRegistrations(registrations);
+    }
+    
+    private void updateExperiments(Collection<IncomingExperiment> experiments, IHarvesterQuery query,
+            Map<String, Long> userTechIdsByUserId, Monitor monitor)
+    {
+        monitor.log("update " + experiments.size() + " experiments");
+        List<RegistrationDTO> registrations = new ArrayList<>();
+        for (IncomingExperiment incomingExperiment : experiments)
+        {
+            addRegistration(registrations, incomingExperiment.getPermID(), incomingExperiment, userTechIdsByUserId);
+        }
+        query.updateExperimentRegistrations(registrations);
+    }
+
+    private void updateSamples(Collection<IncomingSample> samples, IHarvesterQuery query,
+            Map<String, Long> userTechIdsByUserId, Monitor monitor)
+    {
+        monitor.log("update " + samples.size() + " samples");
+        BatchOperationExecutor.executeInBatches(new IBatchOperation<IncomingSample>()
+            {
+                @Override
+                public List<IncomingSample> getAllEntities()
+                {
+                    return new ArrayList<>(samples);
+                }
+
+                @Override
+                public void execute(List<IncomingSample> samples)
+                {
+                    List<RegistrationDTO> registrations = new ArrayList<>();
+                    for (IncomingSample incomingSamples : samples)
+                    {
+                        addRegistration(registrations, incomingSamples.getPermID(), incomingSamples, userTechIdsByUserId);
+                    }
+                    query.updateSampleRegistrations(registrations);
+                }
+
+                @Override
+                public String getEntityName()
+                {
+                    return "sample";
+                }
+
+                @Override
+                public String getOperationName()
+                {
+                    return "update registration";
+                }
+            });
+    }
+
+    private void updateDataSets(Collection<IncomingDataSet> dataSets, IHarvesterQuery query,
+            Map<String, Long> userTechIdsByUserId, Monitor monitor)
+    {
+        monitor.log("update " + dataSets.size() + " data sets");
+        BatchOperationExecutor.executeInBatches(new IBatchOperation<IncomingDataSet>()
+        {
+            @Override
+            public List<IncomingDataSet> getAllEntities()
+            {
+                return new ArrayList<>(dataSets);
+            }
+            
+            @Override
+            public void execute(List<IncomingDataSet> dataSets)
+            {
+                List<RegistrationDTO> registrations = new ArrayList<>();
+                for (IncomingDataSet incomingDataSet : dataSets)
+                {
+                    addRegistration(registrations, incomingDataSet.getFullDataSet().getMetadataCreation().getCode(), 
+                            incomingDataSet, userTechIdsByUserId);
+                }
+                query.updateDataSetRegistrations(registrations);
+            }
+            
+            @Override
+            public String getEntityName()
+            {
+                return "data set";
+            }
+            
+            @Override
+            public String getOperationName()
+            {
+                return "update registration";
+            }
+        });
+    }
+    
+    private void addRegistration(List<RegistrationDTO> registrations, String permID, AbstractRegistrationHolder entity,
+            Map<String, Long> userTechIdsByUserId)
+    {
+        String registrator = entity.getRegistrator();
+        Long registratorId = userTechIdsByUserId.get(registrator);
+        if (registratorId != null)
+        {
+            RegistrationDTO registration = new RegistrationDTO();
+            registration.setPermId(permID);
+            registration.setRegistrationTimestamp(entity.getRegistrationTimestamp());
+            registration.setRegistratorId(registratorId);
+            registrations.add(registration);
+        }
+    }
+
+    private void addRegistrators(Set<String> registrators, Collection<? extends AbstractRegistrationHolder> registrationHolders)
+    {
+        for (AbstractRegistrationHolder incomingMaterial : registrationHolders)
+        {
+            registrators.add(incomingMaterial.getRegistrator());
+        }
+    }
+
     private void registerDataSets(ResourceListParserData data, List<String> notSyncedAttachmentsHolders) throws IOException
     {
         Monitor monitor = new Monitor("Register data sets", operationLog);
+        operationLog.info("Registering data sets...");
+        registerLinkDataSets(data, monitor);
+
         // register physical data sets without any hierarchy
         // Note that container/component and parent/child relationships are established post-reg.
         // setParentDataSetsOnTheChildren(data);
         Map<String, IncomingDataSet> physicalDSMap =
-                data.filterPhysicalDataSetsByLastModificationDate(lastSyncTimestamp, dataSetsCodesToRetry, blackListedDataSetCodes);
-        operationLog.info("Registering data sets...");
+                data.filterByDataSetKindAndLastModificationDate(DataSetKind.PHYSICAL, lastSyncTimestamp, dataSetsCodesToRetry,
+                        blackListedDataSetCodes);
 
         if (config.isVerbose())
         {
@@ -235,10 +445,52 @@ public class EntitySynchronizer
         List<String> skippedDataSets = new ArrayList<String>();
         skippedDataSets.addAll(notRegisteredDataSetCodes);
         skippedDataSets.addAll(blackListedDataSetCodes);
-        establishDataSetRelationships(data.getDataSetsToProcess(), skippedDataSets, physicalDSMap);
+        Set<String> containerDataSets = data
+                .filterByDataSetKindAndLastModificationDate(DataSetKind.CONTAINER, lastSyncTimestamp, dataSetsCodesToRetry, blackListedDataSetCodes)
+                .keySet();
+        establishDataSetRelationships(data.getDataSetsToProcess(), skippedDataSets, containerDataSets);
         monitor.log();
     }
 
+    private void registerLinkDataSets(ResourceListParserData data, Monitor monitor)
+    {
+        Map<String, IncomingDataSet> linkDataSets =
+                data.filterByDataSetKindAndLastModificationDate(DataSetKind.LINK, lastSyncTimestamp, dataSetsCodesToRetry, blackListedDataSetCodes);
+        monitor.log("Register " + linkDataSets.size() + " link data sets");
+        Collection<IncomingDataSet> values = linkDataSets.values();
+        List<DataSetPermId> dataSetIds =
+                values.stream().map(ds -> new DataSetPermId(ds.getFullDataSet().getMetadataCreation().getCode())).collect(Collectors.toList());
+        System.err.println("new data sets:" + dataSetIds);
+        Map<IDataSetId, DataSet> existingDataSets = v3Api.getDataSets(service.getSessionToken(), dataSetIds, new DataSetFetchOptions());
+        System.err.println("existing data sets:" + existingDataSets.keySet());
+        List<DataSetUpdate> updates = new ArrayList<>();
+        List<FullDataSetCreation> creations = new ArrayList<>();
+        for (IncomingDataSet incomingDataSet : values)
+        {
+            FullDataSetCreation fullDataSet = incomingDataSet.getFullDataSet();
+            DataSetCreation dataSet = fullDataSet.getMetadataCreation();
+            DataSetPermId permId = new DataSetPermId(dataSet.getCode());
+            if (existingDataSets.containsKey(permId))
+            {
+                DataSetUpdate update = new DataSetUpdate();
+                update.setDataSetId(permId);
+                update.setProperties(dataSet.getProperties());
+                updates.add(update);
+            } else
+            {
+                creations.add(fullDataSet);
+            }
+        }
+        if (updates.isEmpty() && config.isDryRun() == false)
+        {
+            v3Api.updateDataSets(service.getSessionToken(), updates);
+        }
+        if (creations.isEmpty() == false && config.isDryRun() == false)
+        {
+            v3DssApi.createDataSets(service.getSessionToken(), creations);
+        }
+    }
+
     private List<String> registerAttachments(ResourceListParserData data, MultiKeyMap<String, String> newEntities)
     {
         Monitor monitor = new Monitor("Register attachments", operationLog);
@@ -510,7 +762,7 @@ public class EntitySynchronizer
         }
     }
 
-    private AttachmentSynchronizationSummary processAttachments(List<IncomingEntity<?>> attachmentHoldersToProcess, 
+    private AttachmentSynchronizationSummary processAttachments(List<IncomingEntity<?>> attachmentHoldersToProcess,
             Monitor monitor)
     {
         AttachmentSynchronizationSummary synchronizationSummary = new AttachmentSynchronizationSummary();
@@ -520,9 +772,9 @@ public class EntitySynchronizer
         List<List<IncomingEntity<?>>> attachmentHoldersChunks = chunk(attachmentHoldersToProcess);
         IApplicationServerApi v3apiDataSource = ServiceUtils.createAsV3Api(config.getDataSourceOpenbisURL());
         String sessionTokenDataSource = v3apiDataSource.login(config.getUser(), config.getPassword());
-        ParallelizedExecutor.process(attachmentHoldersChunks, 
-                new AttachmentsSynchronizer(v3Api, service.getSessionToken(), v3apiDataSource, sessionTokenDataSource, 
-                        lastSyncTimestamp, synchronizationSummary, monitor), 
+        ParallelizedExecutor.process(attachmentHoldersChunks,
+                new AttachmentsSynchronizer(v3Api, service.getSessionToken(), v3apiDataSource, sessionTokenDataSource,
+                        lastSyncTimestamp, synchronizationSummary, monitor),
                 preferences.getMachineLoad(), preferences.getMaxThreads(), "process attachments", preferences.getRetriesOnFail(),
                 preferences.isStopOnFailure());
 
@@ -558,7 +810,7 @@ public class EntitySynchronizer
     // }
 
     private void establishDataSetRelationships(Map<String, IncomingDataSet> dataSetsToProcess,
-            List<String> skippedDataSets, Map<String, IncomingDataSet> physicalDSMap)
+            List<String> skippedDataSets, Set<String> containerDataSets)
     {
         // set parent and container data set codes before everything else
         // container and physical data sets can both be parents/children of each other
@@ -608,7 +860,7 @@ public class EntitySynchronizer
             {
                 if (blackListedDataSetCodes.contains(dataSet.getCode()) == false)
                 {
-                    if (physicalDSMap.containsKey(dataSet.getCode()) == false && service.tryGetDataSet(dataSet.getCode()) == null)
+                    if (containerDataSets.contains(dataSet.getCode()) && service.tryGetDataSet(dataSet.getCode()) == null)
                     {
                         builder.dataSet(dataSet);
                     } else
@@ -664,7 +916,7 @@ public class EntitySynchronizer
             if (sampleIdentifier != null)
             {
                 Sample sampleWithExperiment = service.tryGetSampleWithExperiment(sampleIdentifier);
-//                dsBatchUpdatesDTO.setSampleIdentifierOrNull(SampleIdentifierFactory.parse(sampleWithExperiment.getIdentifier()));
+                // dsBatchUpdatesDTO.setSampleIdentifierOrNull(SampleIdentifierFactory.parse(sampleWithExperiment.getIdentifier()));
                 dsBatchUpdatesDTO.setSampleIdentifierOrNull(sampleIdentifier);
             } else
             {
@@ -676,7 +928,7 @@ public class EntitySynchronizer
             if (expIdentifier != null)
             {
                 Experiment experiment = service.tryGetExperiment(expIdentifier);
-//                dsBatchUpdatesDTO.setExperimentIdentifierOrNull(ExperimentIdentifierFactory.parse(experiment.getIdentifier()));
+                // dsBatchUpdatesDTO.setExperimentIdentifierOrNull(ExperimentIdentifierFactory.parse(experiment.getIdentifier()));
                 dsBatchUpdatesDTO.setExperimentIdentifierOrNull(expIdentifier);
             } else
             {
@@ -810,7 +1062,7 @@ public class EntitySynchronizer
         Set<String> incomingExperimentPermIds = data.getExperimentsToProcess().keySet();
         Set<String> incomingSamplePermIds = data.getSamplesToProcess().keySet();
         Set<String> incomingDataSetCodes = data.getDataSetsToProcess().keySet();
-        MultiKeyMap<String, MaterialWithLastModificationDate> incomingMaterials = data.getMaterialsToProcess();
+        MultiKeyMap<String, IncomingMaterial> incomingMaterials = data.getMaterialsToProcess();
 
         // find projects, experiments, samples and data sets to be deleted
         Map<ProjectPermId, String> projectsToDelete = new HashMap<ProjectPermId, String>();
@@ -1103,8 +1355,8 @@ public class EntitySynchronizer
     private void processMaterials(ResourceListParserData data, AtomicEntityOperationDetailsBuilder builder)
     {
         // process materials
-        MultiKeyMap<String, MaterialWithLastModificationDate> materialsToProcess = data.getMaterialsToProcess();
-        for (MaterialWithLastModificationDate newMaterialWithType : materialsToProcess.values())
+        MultiKeyMap<String, IncomingMaterial> materialsToProcess = data.getMaterialsToProcess();
+        for (IncomingMaterial newMaterialWithType : materialsToProcess.values())
         {
             NewMaterialWithType incomingMaterial = newMaterialWithType.getMaterial();
             if (newMaterialWithType.getLastModificationDate().after(lastSyncTimestamp))
@@ -1203,7 +1455,7 @@ public class EntitySynchronizer
         prjUpdate.setSpaceCode(projectIdentifier.getSpaceCode());
         return prjUpdate;
     }
-    
+
     private void processSamples(ResourceListParserData data, AtomicEntityOperationDetailsBuilder builder, Monitor monitor)
     {
         // process samples
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IHarvesterQuery.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IHarvesterQuery.java
new file mode 100644
index 0000000000000000000000000000000000000000..a56bbaa8cb733b1cc7125936e2d1b91fcfaa3739
--- /dev/null
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IHarvesterQuery.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019 ETH Zuerich, SIS
+ *
+ * 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.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.synchronizer;
+
+import java.util.List;
+
+import net.lemnik.eodsql.BaseQuery;
+import net.lemnik.eodsql.Select;
+import net.lemnik.eodsql.Update;
+
+/**
+ * @author Franz-Josef Elmer
+ */
+public interface IHarvesterQuery extends BaseQuery
+{
+    public static final String UPDATE2 =
+            "set registration_timestamp = ?{1.registrationTimestamp}, pers_id_registerer = ?{1.registratorId} "
+                    + "where perm_id = ?{1.permId}";
+
+    @Select("select id,user_id as userId from persons")
+    public List<PersonRecord> listAllUsers();
+
+//    @Update(sql = "update materials " + UPDATE2, batchUpdate = true)
+//    public void updateMaterialRegistrations(List<RegistrationDTO> registrations);
+
+    @Update(sql = "update projects " + UPDATE2, batchUpdate = true)
+    public void updateProjectRegistrations(List<RegistrationDTO> registrations);
+
+    @Update(sql = "update experiments_all " + UPDATE2, batchUpdate = true)
+    public void updateExperimentRegistrations(List<RegistrationDTO> registrations);
+
+    @Update(sql = "update samples_all " + UPDATE2, batchUpdate = true)
+    public void updateSampleRegistrations(List<RegistrationDTO> registrations);
+
+    @Update(sql = "update data_all set registration_timestamp = ?{1.registrationTimestamp}, pers_id_registerer = ?{1.registratorId} "
+            + "where code = ?{1.permId}", batchUpdate = true)
+    public void updateDataSetRegistrations(List<RegistrationDTO> registrations);
+}
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IncomingDataSet.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IncomingDataSet.java
index 419e309dceac605d853daf5281689d9089e40f67..9aa8ff090a1b3954905468ea1310281df2987ad2 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IncomingDataSet.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IncomingDataSet.java
@@ -22,17 +22,19 @@ import java.util.Date;
 import java.util.List;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSetKind;
-import ch.systemsx.cisd.openbis.generic.shared.dto.NewContainerDataSet;
+import ch.ethz.sis.openbis.generic.dssapi.v3.dto.dataset.create.FullDataSetCreation;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
-import ch.systemsx.cisd.openbis.generic.shared.dto.NewLinkDataSet;
 
-public class IncomingDataSet implements Serializable
+public class IncomingDataSet extends AbstractRegistrationHolder implements Serializable
 {
     private static final long serialVersionUID = 1L;
+
     private final NewExternalData dataSet;
 
     final Date lastModificationDate;
 
+    private FullDataSetCreation fullDataSet;
+
     public Date getLastModificationDate()
     {
         return lastModificationDate;
@@ -40,11 +42,7 @@ public class IncomingDataSet implements Serializable
 
     public DataSetKind getKind()
     {
-        if (dataSet instanceof NewContainerDataSet)
-            return DataSetKind.CONTAINER;
-        else if (dataSet instanceof NewLinkDataSet)
-            return DataSetKind.LINK;
-        return DataSetKind.PHYSICAL;
+        return fullDataSet.getMetadataCreation().getDataSetKind();
     }
 
     public NewExternalData getDataSet()
@@ -52,10 +50,16 @@ public class IncomingDataSet implements Serializable
         return dataSet;
     }
 
-    IncomingDataSet(NewExternalData dataSet, Date lastModDate)
+    public FullDataSetCreation getFullDataSet()
+    {
+        return fullDataSet;
+    }
+
+    IncomingDataSet(NewExternalData dataSet, FullDataSetCreation fullDataSet, Date lastModDate)
     {
         super();
         this.dataSet = dataSet;
+        this.fullDataSet = fullDataSet;
         this.lastModificationDate = lastModDate;
     }
 
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IncomingEntity.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IncomingEntity.java
index f4a5f93cdac8ae2e9c7062f6b2659b1580c0b653..16d4fd163330aad497610f914eb2aa0efcf6b57f 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IncomingEntity.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IncomingEntity.java
@@ -23,7 +23,7 @@ import java.util.List;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.common.SyncEntityKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Identifier;
 
-public class IncomingEntity<T extends Identifier<T>>
+public class IncomingEntity<T extends Identifier<T>> extends AbstractRegistrationHolder
 {
     private final Identifier<T> entity;
 
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MaterialWithLastModificationDate.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IncomingMaterial.java
similarity index 90%
rename from datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MaterialWithLastModificationDate.java
rename to datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IncomingMaterial.java
index 0c783e092bf5bd1e9bba2e385983e39162154feb..8293c9c3fccf057d5ab88912b76a8a39eded0277 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MaterialWithLastModificationDate.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/IncomingMaterial.java
@@ -20,7 +20,7 @@ import java.util.Date;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterialWithType;
 
-class MaterialWithLastModificationDate
+class IncomingMaterial extends AbstractRegistrationHolder
 {
     private final NewMaterialWithType material;
 
@@ -31,7 +31,7 @@ class MaterialWithLastModificationDate
         return material;
     }
 
-    MaterialWithLastModificationDate(NewMaterialWithType material, Date lastModDate)
+    IncomingMaterial(NewMaterialWithType material, Date lastModDate)
     {
         this.material = material;
         this.lastModificationDate = lastModDate;
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterData.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterData.java
index fc7822dd1274b760bb7baf988700e6982b3c2914..3758d566d3d054c35226c85dcd7c9f3321b43d74 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterData.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterData.java
@@ -22,6 +22,7 @@ import java.util.Map;
 
 import org.apache.commons.collections4.map.MultiKeyMap;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDms;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.FileFormatType;
@@ -36,6 +37,8 @@ class MasterData
 {
     private Map<String, FileFormatType> fileFormatTypesToProcess = new HashMap<String, FileFormatType>();
 
+    private Map<String, ExternalDms> externalDataManagementSystemsToProcess = new HashMap<>();
+
     private Map<String, Script> validationPluginsToProcess = new HashMap<String, Script>();
 
     private Map<String, NewVocabulary> vocabulariesToProcess = new HashMap<String, NewVocabulary>();
@@ -107,6 +110,16 @@ class MasterData
         this.fileFormatTypesToProcess = fileFormatTypesToProcess;
     }
 
+    public Map<String, ExternalDms> getExternalDataManagementSystemsToProcess()
+    {
+        return externalDataManagementSystemsToProcess;
+    }
+
+    public void setExternalDataManagementSystemsToProcess(Map<String, ExternalDms> edmsToProcess)
+    {
+        this.externalDataManagementSystemsToProcess = edmsToProcess;
+    }
+
     public void setVocabulariesToProcess(Map<String, NewVocabulary> vocabulariesToProcess)
     {
         this.vocabulariesToProcess = vocabulariesToProcess;
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterDataParser.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterDataParser.java
index 58dd1b31517b223581b2959ed845fe455d727324..099226e615a7b5fc94ff03678a8c29ae4478e5e3 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterDataParser.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterDataParser.java
@@ -35,6 +35,9 @@ import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
+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;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.synchronizer.translator.DefaultNameTranslator;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.synchronizer.translator.INameTranslator;
 import ch.systemsx.cisd.openbis.generic.shared.basic.CodeConverter;
@@ -65,6 +68,7 @@ public class MasterDataParser
     
     private Map<String, Script> validationPlugins = new HashMap<String, Script>();
 
+    private Map<String, ExternalDms> externalDataManagementSystems = new HashMap<>();
     private Map<String, FileFormatType> fileFormatTypes = new HashMap<String, FileFormatType>();
 
     private Map<String, PropertyType> propertyTypes = new HashMap<String, PropertyType>();
@@ -122,6 +126,7 @@ public class MasterDataParser
         parseSampleTypes(docElement.getElementsByTagName("xmd:objectTypes"));
         parseDataSetTypes(docElement.getElementsByTagName("xmd:dataSetTypes"));
         parseExperimentTypes(docElement.getElementsByTagName("xmd:collectionTypes"));
+        parseExternalDataManagementSystems(docElement.getElementsByTagName("xmd:externalDataManagementSystems"));
     }
 
 
@@ -170,6 +175,11 @@ public class MasterDataParser
         return materialTypes;
     }
 
+    public Map<String, ExternalDms> getExternalDataManagementSystems()
+    {
+        return externalDataManagementSystems;
+    }
+
     private void parseValidationPlugins(NodeList validationPluginsNode) throws XPathExpressionException
     {
         if (validationPluginsNode.getLength() == 0)
@@ -196,6 +206,30 @@ public class MasterDataParser
         }
     }
 
+    private void parseExternalDataManagementSystems(NodeList edmsNode) throws XPathExpressionException
+    {
+        if (edmsNode.getLength() == 0)
+        {
+            return;
+        }
+        validateElementNode(edmsNode, "externalDataManagementSystems");
+
+        NodeList edmsNodes = ((Element) edmsNode.item(0)).getElementsByTagName("xmd:externalDataManagementSystem");
+
+        for (int i = 0; i < edmsNodes.getLength(); i++)
+        {
+            Element element = (Element) edmsNodes.item(i);
+            ExternalDms edms = new ExternalDms();
+            String code = nameTranslator.translate(getAttribute(element, "code"));
+            edms.setCode(code);
+            edms.setPermId(new ExternalDmsPermId(code));
+            edms.setLabel(getAttribute(element, "label"));
+            edms.setAddressType(ExternalDmsAddressType.valueOf(getAttribute(element, "addressType")));
+            edms.setAddress(getAttribute(element, "address"));
+            externalDataManagementSystems.put(code, edms);
+        }
+    }
+
     private void parseFileFormatTypes(NodeList fileFormatTypesNode) throws XPathExpressionException
     {
         if (fileFormatTypesNode.getLength() == 0)
@@ -203,14 +237,14 @@ public class MasterDataParser
             return;
         }
         validateElementNode(fileFormatTypesNode, "fileFormatTypes");
-
+        
         Element fileFormatTypesElement = (Element) fileFormatTypesNode.item(0);
         NodeList fileFormatTypeNodes = fileFormatTypesElement.getElementsByTagName("xmd:fileFormatType");
-
+        
         for (int i = 0; i < fileFormatTypeNodes.getLength(); i++)
         {
             Element typeElement = (Element) fileFormatTypeNodes.item(i);
-
+            
             FileFormatType type = new FileFormatType();
             String code = getAttribute(typeElement, "code");
             type.setCode(code);
@@ -219,7 +253,7 @@ public class MasterDataParser
             fileFormatTypes.put(code, type);
         }
     }
-
+    
     private void validateElementNode(NodeList nodeList, String tagName) throws XPathExpressionException
     {
         if (nodeList.getLength() != 1)
@@ -257,7 +291,7 @@ public class MasterDataParser
 
     private void parseVocabularyTerms(Element vocabElement, NewVocabulary newVocabulary)
     {
-        NodeList termNodes = vocabElement.getElementsByTagName("term");
+        NodeList termNodes = vocabElement.getElementsByTagName("xmd:term");
         for (int i = 0; i < termNodes.getLength(); i++)
         {
             Element termElement = (Element) termNodes.item(i);
@@ -423,12 +457,14 @@ public class MasterDataParser
             assignment.setMandatory(Boolean.valueOf(getAttribute(propertyAssignmentElement, "mandatory")));
             assignment.setDefaultValue(ERROR_PROPERTY_PREFIX);
             assignment.setSection(getAttribute(propertyAssignmentElement, "section"));
-            assignment.setOrdinal(Long.valueOf(getAttribute(propertyAssignmentElement, "ordinal")));
+            // ch.systemsx.cisd.openbis.generic.server.business.bo.EntityTypePropertyTypeBO.createAssignment() increases
+            // the provided ordinal by one. Thus, we have to subtract 1 in order to get the same ordinal.
+            assignment.setOrdinal(Long.valueOf(getAttribute(propertyAssignmentElement, "ordinal")) - 1);
             assignment.setShownInEditView(Boolean.valueOf(getAttribute(propertyAssignmentElement, "showInEdit")));
             String pluginId = getAttribute(propertyAssignmentElement, "plugin");
             if (pluginId != null)
             {
-                assignment.setScriptName(pluginId);
+                assignment.setScriptName(nameTranslator.translate(pluginId));
                 String pluginType = getAttribute(propertyAssignmentElement, "pluginType");
                 assignment.setDynamic(ch.ethz.sis.openbis.generic.asapi.v3.dto.plugin.PluginType.DYNAMIC_PROPERTY.toString().equals(pluginType));
                 assignment.setManaged(ch.ethz.sis.openbis.generic.asapi.v3.dto.plugin.PluginType.MANAGED_PROPERTY.toString().equals(pluginType));
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterDataSynchronizer.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterDataSynchronizer.java
index 136c2475e356b0ea43dc555b5b9580ec9cef89d6..a1458caa4b177fcaa11724eeca31441519452477 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterDataSynchronizer.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterDataSynchronizer.java
@@ -21,10 +21,18 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import org.apache.commons.collections4.map.MultiKeyMap;
 import org.apache.log4j.Logger;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDms;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.create.ExternalDmsCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.fetchoptions.ExternalDmsFetchOptions;
+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.externaldms.update.ExternalDmsUpdate;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.common.ServiceFinderUtils;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.synchronizer.util.Monitor;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
@@ -64,6 +72,8 @@ public class MasterDataSynchronizer
 
     final Map<TechId, String> vocabularyTechIdToCode = new HashMap<TechId, String>();
 
+    private IApplicationServerApi v3api;
+
     public MasterDataSynchronizer(String harvesterUser, String harvesterPassword, boolean dryRun, boolean verbose, Logger operationLog)
     {
         String openBisServerUrl = ServiceProvider.getConfigProvider().getOpenBisServerUrl();
@@ -71,6 +81,7 @@ public class MasterDataSynchronizer
         this.synchronizerFacade = new SynchronizerFacade(openBisServerUrl, harvesterUser, harvesterPassword, dryRun, verbose, operationLog);
         this.commonServer = ServiceFinderUtils.getCommonServer(openBisServerUrl);
         this.sessionToken = ServiceFinderUtils.login(commonServer, harvesterUser, harvesterPassword);
+        v3api = ServiceProvider.getV3ApplicationService();
         vocabularyTermsToBeDeleted = new HashMap<TechId, List<VocabularyTerm>>();
     }
 
@@ -96,6 +107,8 @@ public class MasterDataSynchronizer
         processEntityTypes(masterData.getExperimentTypesToProcess(), propertyAssignmentsToProcess);
         monitor.log("process material type property assignments");
         processDeferredMaterialTypePropertyAssignments(propertyAssignmentsToProcess);
+        monitor.log("process external data management systems");
+        processExternalDataManagementSystems(masterData.getExternalDataManagementSystemsToProcess());
 
         synchronizerFacade.printSummary();
     }
@@ -149,6 +162,43 @@ public class MasterDataSynchronizer
         }
     }
 
+    private void processExternalDataManagementSystems(Map<String, ExternalDms> externalDataManagementSystems)
+    {
+        List<ExternalDmsPermId> ids = externalDataManagementSystems.keySet().stream()
+                .map(ExternalDmsPermId::new).collect(Collectors.toList());
+        Map<IExternalDmsId, ExternalDms> existingDmss 
+            = v3api.getExternalDataManagementSystems(sessionToken,ids, new ExternalDmsFetchOptions());
+        List<ExternalDmsCreation> creations = new ArrayList<>();
+        List<ExternalDmsUpdate> updates = new ArrayList<>();
+        for (ExternalDms externalDms : externalDataManagementSystems.values())
+        {
+            if (existingDmss.containsKey(externalDms.getPermId()))
+            {
+                ExternalDmsUpdate update = new ExternalDmsUpdate();
+                update.setExternalDmsId(externalDms.getPermId());
+                update.setAddress(externalDms.getAddress());
+                update.setLabel(externalDms.getLabel());
+                updates.add(update);
+            } else
+            {
+                ExternalDmsCreation creation = new ExternalDmsCreation();
+                creation.setCode(externalDms.getCode());
+                creation.setLabel(externalDms.getLabel());
+                creation.setAddress(externalDms.getAddress());
+                creation.setAddressType(externalDms.getAddressType());
+                creations.add(creation);
+            }
+        }
+        if (creations.isEmpty() == false && dryRun == false)
+        {
+            v3api.createExternalDataManagementSystems(sessionToken, creations);
+        }
+        if (updates.isEmpty() == false && dryRun == false)
+        {
+            v3api.updateExternalDataManagementSystems(sessionToken, updates);
+        }
+    }
+
     private void processFileFormatTypes(Map<String, FileFormatType> fileFormatTypesToProcess)
     {
         List<FileFormatType> fileFormatTypes = commonServer.listFileFormatTypes(sessionToken);
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/PersonRecord.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/PersonRecord.java
new file mode 100644
index 0000000000000000000000000000000000000000..96e92bccf645be75c2812a21aad4e18f2a6db3d0
--- /dev/null
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/PersonRecord.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 ETH Zuerich, SIS
+ *
+ * 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.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.synchronizer;
+
+/**
+ * @author Franz-Josef Elmer
+ *
+ */
+public class PersonRecord
+{
+    public long id;
+    public String userId;
+}
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/RegistrationDTO.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/RegistrationDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..8aa31fe1a2d140cfd6029fc0d2bb14c27c336bfc
--- /dev/null
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/RegistrationDTO.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019 ETH Zuerich, SIS
+ *
+ * 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.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.synchronizer;
+
+import java.util.Date;
+
+/**
+ * @author Franz-Josef Elmer
+ */
+public class RegistrationDTO
+{
+    private String permId;
+
+    private Date registrationTimestamp;
+
+    private long registratorId;
+
+    public String getPermId()
+    {
+        return permId;
+    }
+
+    public void setPermId(String permId)
+    {
+        this.permId = permId;
+    }
+
+    public Date getRegistrationTimestamp()
+    {
+        return registrationTimestamp;
+    }
+
+    public void setRegistrationTimestamp(Date registrationTimestamp)
+    {
+        this.registrationTimestamp = registrationTimestamp;
+    }
+
+    public long getRegistratorId()
+    {
+        return registratorId;
+    }
+
+    public void setRegistratorId(long registratorId)
+    {
+        this.registratorId = registratorId;
+    }
+}
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/ResourceListParser.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/ResourceListParser.java
index 1f536d72f084b5844e1c5c10feb129cba083adbc..4f45356d029d1e7e5c87ffe8fbe468695e725e83 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/ResourceListParser.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/ResourceListParser.java
@@ -23,6 +23,7 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
@@ -47,6 +48,15 @@ import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
 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.LinkedDataCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.datastore.id.DataStorePermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.EntityKind;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id.EntityTypePermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.id.ExternalDmsPermId;
+import ch.ethz.sis.openbis.generic.dssapi.v3.dto.dataset.create.FullDataSetCreation;
+import ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.create.DataSetFileCreation;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.common.SyncEntityKind;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.synchronizer.translator.DefaultNameTranslator;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.synchronizer.translator.INameTranslator;
@@ -235,6 +245,7 @@ public class ResourceListParser
         masterData.setExperimentTypesToProcess(mdParser.getExperimentTypes());
         masterData.setMaterialTypesToProcess(mdParser.getMaterialTypes());
         masterData.setPropertyAssignmentsToProcess(mdParser.getEntityPropertyAssignments());
+        masterData.setExternalDataManagementSystemsToProcess(mdParser.getExternalDataManagementSystems());
     }
 
     private void parseMetaData(String uri, Date lastModificationDate, Node xdNode) throws XPathExpressionException
@@ -295,30 +306,130 @@ public class ResourceListParser
         String experimentIdentifier = extractAttribute(xdNode, "experiment", true);
         String type = extractType(xdNode);
         String dsKind = extractAttribute(xdNode, "dsKind");
-        NewExternalData ds = null;
+        NewExternalData ds = new NewExternalData();
+        FullDataSetCreation fullDataSet = new FullDataSetCreation();
+        DataSetCreation dataSet = new DataSetCreation();
+        fullDataSet.setMetadataCreation(dataSet);
         if (dsKind.equals(DataSetKind.CONTAINER.toString()))
         {
             ds = new NewContainerDataSet();
             ds.setDataSetKind(ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetKind.CONTAINER);
+            dataSet.setDataSetKind(DataSetKind.CONTAINER);
+        } else if (dsKind.equals(DataSetKind.LINK.toString()))
+        {
+            ds = new NewContainerDataSet();
+            parseLinkDataMetaData(fullDataSet, xdNode);
         } else if (dsKind.equals(DataSetKind.PHYSICAL.toString()))
         {
-            ds = new NewExternalData();
             ds.setDataSetKind(ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetKind.PHYSICAL);
+            dataSet.setDataSetKind(DataSetKind.PHYSICAL);
         } else
         {
             throw new IllegalArgumentException(dsKind + " data sets are currently not supported");
         }
         ds.setCode(code);
+        dataSet.setCode(code);
         ds.setDataSetType(new DataSetType(type));
+        dataSet.setTypeId(new EntityTypePermId(type, EntityKind.DATA_SET));
         ds.setDataStoreCode(this.dataStoreCode);
+        dataSet.setDataStoreId(new DataStorePermId(dataStoreCode));
 
-        ds.setSampleIdentifierOrNull(getSampleIdentifier(sampleIdentifier));
-        ds.setExperimentIdentifierOrNull(getExperimentIdentifier(experimentIdentifier));
+        SampleIdentifier sampleId = getSampleIdentifier(sampleIdentifier);
+        if (sampleId != null)
+        {
+            ds.setSampleIdentifierOrNull(sampleId);
+            dataSet.setSampleId(new ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SampleIdentifier(sampleId.toString()));
+        }
+        ExperimentIdentifier experimentId = getExperimentIdentifier(experimentIdentifier);
+        if (experimentId != null)
+        {
+            ds.setExperimentIdentifierOrNull(experimentId);
+            dataSet.setExperimentId(new ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id.ExperimentIdentifier(experimentId.toString()));
+        }
 
-        IncomingDataSet incomingDataSet = new IncomingDataSet(ds, lastModificationDate);
+        IncomingDataSet incomingDataSet = new IncomingDataSet(ds, fullDataSet, lastModificationDate);
+        setRegistratorAndRegistrationTimestamp(xdNode, incomingDataSet);
         data.getDataSetsToProcess().put(permId, incomingDataSet);
         incomingDataSet.setConnections(parseConnections(xdNode));
-        ds.setDataSetProperties(parseDataSetProperties(xdNode));
+        List<NewProperty> properties = parseDataSetProperties(xdNode);
+        ds.setDataSetProperties(properties);
+        Map<String, String> props = new HashMap<>();
+        for (NewProperty property : properties)
+        {
+            props.put(property.getPropertyCode(), property.getValue());
+        }
+        dataSet.setProperties(props);
+    }
+
+    public void parseLinkDataMetaData(FullDataSetCreation fullDataSet, Node xdNode)
+    {
+        DataSetCreation dataSet = fullDataSet.getMetadataCreation();
+        dataSet.setDataSetKind(DataSetKind.LINK);
+        LinkedDataCreation linkedData = new LinkedDataCreation();
+        Element docElement = (Element) xdNode;
+        NodeList binaryDataNode = docElement.getElementsByTagName("x:binaryData");
+        if (binaryDataNode.getLength() == 1)
+        {
+            Element binaryDataElement = (Element) binaryDataNode.item(0);
+            linkedData.setContentCopies(parseContentCopies(binaryDataElement));
+            fullDataSet.setFileMetadata(parseFileNodes(binaryDataElement));
+        }
+        dataSet.setLinkedData(linkedData);
+    }
+
+    public List<DataSetFileCreation> parseFileNodes(Element binaryDataElement)
+    {
+        List<DataSetFileCreation> fileCreations = new ArrayList<>();
+        NodeList fileNodes = binaryDataElement.getElementsByTagName("x:fileNode");
+        for (int i = 0; i < fileNodes.getLength(); i++)
+        {
+            Element fileElement = (Element) fileNodes.item(i);
+            DataSetFileCreation fileCreation = new DataSetFileCreation();
+            fileCreation.setPath(fileElement.getAttribute("path"));
+            fileCreation.setFileLength(Long.parseLong(fileElement.getAttribute("length")));
+            String crc32Checksum = fileElement.getAttribute("crc32checksum");
+            if (StringUtils.isNotBlank(crc32Checksum))
+            {
+                fileCreation.setChecksumCRC32(Integer.parseUnsignedInt(crc32Checksum));
+            }
+            String checksum = fileElement.getAttribute("checksum");
+            if (StringUtils.isNotBlank(checksum))
+            {
+                String[] splittedChecksum = checksum.split(":");
+                if (splittedChecksum.length == 2)
+                {
+                    fileCreation.setChecksumType(splittedChecksum[0]);
+                    fileCreation.setChecksum(splittedChecksum[1]);
+                }
+            }
+            fileCreations.add(fileCreation);
+        }
+        return fileCreations;
+    }
+
+    public List<ContentCopyCreation> parseContentCopies(Element binaryDataElement)
+    {
+        NodeList contentCopyNodes = binaryDataElement.getElementsByTagName("x:contentCopy");
+        List<ContentCopyCreation> contentCopyCreations = new ArrayList<>();
+        for (int i = 0; i < contentCopyNodes.getLength(); i++)
+        {
+            Element contentCopyElement = (Element) contentCopyNodes.item(i);
+            ContentCopyCreation contentCopyCreation = new ContentCopyCreation();
+            contentCopyCreation.setExternalDmsId(
+                    new ExternalDmsPermId(nameTranslator.translate(contentCopyElement.getAttribute("externalDMS"))));
+            contentCopyCreation.setExternalId(getAttributeOrNull(contentCopyElement, "externalCode"));
+            contentCopyCreation.setGitCommitHash(getAttributeOrNull(contentCopyElement, "gitCommitHash"));
+            contentCopyCreation.setGitRepositoryId(getAttributeOrNull(contentCopyElement, "gitRepositoryId"));
+            contentCopyCreation.setPath(getAttributeOrNull(contentCopyElement, "path"));
+            contentCopyCreations.add(contentCopyCreation);
+        }
+        return contentCopyCreations;
+    }
+
+    private String getAttributeOrNull(Element contentCopyElement, String name)
+    {
+        String attribute = contentCopyElement.getAttribute(name);
+        return StringUtils.isBlank(attribute) ? null : attribute;
     }
 
     private String extractAttribute(Node xdNode, String attrName, boolean nullAllowed)
@@ -333,7 +444,7 @@ public class ResourceListParser
             {
                 return null;
             }
-        } 
+        }
         return val;
     }
 
@@ -395,6 +506,7 @@ public class ResourceListParser
         data.getProjectsToProcess().put(permId, incomingProject);
         incomingProject.setConnections(parseConnections(xdNode));
         incomingProject.setHasAttachments(hasAttachments(xdNode));
+        setRegistratorAndRegistrationTimestamp(xdNode, incomingProject);
     }
 
     private ExperimentIdentifier createExperimentIdentifier(String spaceId, String prjCode, String expCode)
@@ -436,9 +548,9 @@ public class ResourceListParser
         String code = nameTranslator.translate(extractCode(xdNode));
         String type = extractType(xdNode);
         NewMaterialWithType newMaterial = new NewMaterialWithType(code, type);
-        MaterialWithLastModificationDate materialWithLastModDate =
-                new MaterialWithLastModificationDate(newMaterial, lastModificationDate);
-        data.getMaterialsToProcess().put(code, type, materialWithLastModDate);
+        IncomingMaterial incomingMaterial = new IncomingMaterial(newMaterial, lastModificationDate);
+        setRegistratorAndRegistrationTimestamp(xdNode, incomingMaterial);
+        data.getMaterialsToProcess().put(code, type, incomingMaterial);
         newMaterial.setProperties(parseProperties(xdNode));
     }
 
@@ -558,6 +670,7 @@ public class ResourceListParser
         data.getExperimentsToProcess().put(permId, incomingExperiment);
         incomingExperiment.setConnections(parseConnections(xdNode));
         incomingExperiment.setHasAttachments(hasAttachments(xdNode));
+        setRegistratorAndRegistrationTimestamp(xdNode, incomingExperiment);
         newExp.setProperties(parseProperties(xdNode));
     }
 
@@ -599,9 +712,23 @@ public class ResourceListParser
         data.getSamplesToProcess().put(permId, incomingSample);
         incomingSample.setHasAttachments(hasAttachments(xdNode));
         incomingSample.setConnections(parseConnections(xdNode));
+        setRegistratorAndRegistrationTimestamp(xdNode, incomingSample);
         newSample.setProperties(parseProperties(xdNode));
     }
 
+    private void setRegistratorAndRegistrationTimestamp(Node xdNode, AbstractRegistrationHolder registrationHolder)
+    {
+        registrationHolder.setRegistrator(extractAttribute(xdNode, "registrator"));
+        String registrationTimestampAsString = extractAttribute(xdNode, "registration-timestamp");
+        try
+        {
+            registrationHolder.setRegistrationTimestamp(convertFromW3CDate(registrationTimestampAsString));
+        } catch (ParseException e)
+        {
+            throw new IllegalArgumentException("Invalid registration-timestamp: " + registrationTimestampAsString);
+        }
+    }
+
     private String extractType(Node xdNode)
     {
         return nameTranslator.translate(extractAttribute(xdNode, "type"));
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/ResourceListParserData.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/ResourceListParserData.java
index 0d3b44e553a5e1cbbb07c9a4ff18e89aaa5a7723..6f72b6b77692941c840763ba831ca31af7cde2f8 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/ResourceListParserData.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/ResourceListParserData.java
@@ -49,7 +49,7 @@ public class ResourceListParserData
 
     private Map<String, IncomingDataSet> dataSetsToProcess = new HashMap<String, IncomingDataSet>();
 
-    private MultiKeyMap<String, MaterialWithLastModificationDate> materialsToProcess = new MultiKeyMap<String, MaterialWithLastModificationDate>();
+    private MultiKeyMap<String, IncomingMaterial> materialsToProcess = new MultiKeyMap<String, IncomingMaterial>();
 
     public MasterData getMasterData()
     {
@@ -91,20 +91,20 @@ public class ResourceListParserData
         return dataSetsToProcess;
     }
 
-    public MultiKeyMap<String, MaterialWithLastModificationDate> getMaterialsToProcess()
+    public MultiKeyMap<String, IncomingMaterial> getMaterialsToProcess()
     {
         return materialsToProcess;
     }
 
-    public Map<String, IncomingDataSet> filterPhysicalDataSetsByLastModificationDate(Date lastSyncDate, Set<String> dataSetsCodesToRetry,
-            Set<String> blackListedDataSetCodes)
+    public Map<String, IncomingDataSet> filterByDataSetKindAndLastModificationDate(DataSetKind dataSetKind, Date lastSyncDate,
+            Set<String> dataSetsCodesToRetry, Set<String> blackListedDataSetCodes)
     {
         Map<String, IncomingDataSet> dsMap = new HashMap<String, IncomingDataSet>();
         for (String permId : dataSetsToProcess.keySet())
         {
             IncomingDataSet ds = dataSetsToProcess.get(permId);
             String dataSetCode = ds.getDataSet().getCode();
-            if (ds.getKind() == DataSetKind.PHYSICAL
+            if (ds.getKind() == dataSetKind
                     && (ds.lastModificationDate.after(lastSyncDate) == true || dataSetsCodesToRetry.contains(dataSetCode)) == true)
             {
                 if (blackListedDataSetCodes.contains(dataSetCode) == false)
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/SynchronizationContext.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/SynchronizationContext.java
index f93c059ee9291f904e41bc1975e60ca6f563dbcd..bf8ce35efaed4f41b4cf72520b44305af1756ce5 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/SynchronizationContext.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/SynchronizationContext.java
@@ -23,6 +23,7 @@ import java.util.Set;
 import org.apache.log4j.Logger;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
+import ch.ethz.sis.openbis.generic.dssapi.v3.IDataStoreServerApi;
 import ch.ethz.sis.openbis.generic.server.dss.plugins.sync.harvester.config.SyncConfig;
 import ch.systemsx.cisd.openbis.dss.generic.shared.DataSetProcessingContext;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
@@ -35,7 +36,7 @@ public class SynchronizationContext
     private IEncapsulatedOpenBISService service;
 
     private IApplicationServerApi v3Api;
-    
+
     private String dataStoreCode;
 
     private File storeRoot;
@@ -56,6 +57,8 @@ public class SynchronizationContext
 
     private Logger operationLog;
 
+    private IDataStoreServerApi v3DssApi;
+
     public IEncapsulatedOpenBISService getService()
     {
         return service;
@@ -76,6 +79,16 @@ public class SynchronizationContext
         this.v3Api = v3Api;
     }
 
+    public IDataStoreServerApi getV3DssApi()
+    {
+        return v3DssApi;
+    }
+
+    public void setV3DssApi(IDataStoreServerApi v3DssApi)
+    {
+        this.v3DssApi = v3DssApi;
+    }
+
     public String getDataStoreCode()
     {
         return dataStoreCode;
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/ConversionUtils.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/ConversionUtils.java
index 42b0cae42eded4cd05427544ed880cb1858a6f7f..ffb8cd64ee1a1a5a99e5b02f5fbdbd9be84dceff 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/ConversionUtils.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/ConversionUtils.java
@@ -377,7 +377,10 @@ public class ConversionUtils
 
         dataSetUpdate.setDatasetId(new TechId(externalData));
         dataSetUpdate.setVersion(externalData.getVersion());
-        dataSetUpdate.setFileFormatTypeCode(dataSet.getFileFormatType());
+        if (dataSet.isLinkDataSet() == false && dataSet.isContainerDataSet() == false)
+        {
+            dataSetUpdate.setFileFormatTypeCode(dataSet.getFileFormatType());
+        }
         dataSetUpdate.setProperties(externalData.getProperties());
 
         if (externalData.getExperiment() != null)
diff --git a/integration-tests/templates/test_openbis_sync_big/openbis_test_openbis_sync_big_data_source.sql b/integration-tests/templates/test_openbis_sync_big/openbis_test_openbis_sync_big_data_source.sql
index 091738f68559e7c1fe3822be5a553a4aa237ac07..10544541fab5c42a3b339d963cbf5441b18d365a 100644
--- a/integration-tests/templates/test_openbis_sync_big/openbis_test_openbis_sync_big_data_source.sql
+++ b/integration-tests/templates/test_openbis_sync_big/openbis_test_openbis_sync_big_data_source.sql
@@ -3125,6 +3125,7 @@ SELECT pg_catalog.setval('public.code_seq', 73, true);
 --
 
 COPY public.content_copies (id, location_type, data_id, edms_id, external_code, path, git_commit_hash, git_repository_id, location_unique_check, pers_id_registerer, registration_timestamp) FROM stdin;
+1	FILE_SYSTEM_GIT	42	1	\N	/a/b/c	b67c89d23e	git42	42,1,/a/b/c,b67c89d23e,git42	1	2019-01-15 08:09:10
 \.
 
 
@@ -3132,7 +3133,7 @@ COPY public.content_copies (id, location_type, data_id, edms_id, external_code,
 -- Name: content_copies_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
 --
 
-SELECT pg_catalog.setval('public.content_copies_id_seq', 1, false);
+SELECT pg_catalog.setval('public.content_copies_id_seq', 2, false);
 
 
 --
@@ -3733,6 +3734,7 @@ COPY public.data_all (id, code, data_set_kind, dsty_id, dast_id, expe_id, data_p
 14	20181115081714817-36	PHYSICAL	21	1	4	\N	\N	5	2018-11-15 08:17:31.258657+01	3	f	2018-11-15 08:17:31.258657+01	2018-11-28 14:45:42.610377+01	t	\N	\N	3	0
 3	20181115081535015-7	PHYSICAL	6	1	3	\N	\N	\N	2018-11-15 08:15:36.551981+01	3	f	2018-11-15 08:15:36.551981+01	2018-11-28 14:45:42.247368+01	f	\N	\N	3	0
 20	20181115081917453-58	PHYSICAL	11	1	4	\N	\N	7	2018-11-15 08:19:17.691003+01	3	f	2018-11-15 08:19:17.691003+01	2018-11-28 14:45:42.791526+01	t	\N	\N	3	0
+42	20190115081917453-59	LINK	25	1	4	\N	\N	7	2018-11-15 08:19:17.691003+01	3	f	2018-11-15 08:19:17.691003+01	2018-11-28 14:45:42.791526+01	t	\N	\N	3	0
 \.
 
 
@@ -3796,6 +3798,7 @@ COPY public.data_set_properties (id, ds_id, dstpt_id, value, cvte_id, mate_prop_
 31	37	54	<?xml version='1.0' encoding='UTF-8'?>\n<Parameters P10B="32" P10DISPLAY="LOG" P10E_LOG="0.0" P10E_LOGZERO="0.0" P10G="1.0" P10N="488 670/30-H" P10R="262144" P10S="" P10V="595" P11B="32" P11DISPLAY="LIN" P11E_LOG="0.0" P11E_LOGZERO="0.0" P11G="0.01" P11N="Time" P11R="262144" P11S="" P11V="NaN" P1B="32" P1DISPLAY="LIN" P1E_LOG="0.0" P1E_LOGZERO="0.0" P1G="1.0" P1N="FSC-A" P1R="262144" P1S="" P1V="662" P2B="32" P2DISPLAY="LIN" P2E_LOG="0.0" P2E_LOGZERO="0.0" P2G="1.0" P2N="FSC-H" P2R="262144" P2S="" P2V="662" P3B="32" P3DISPLAY="LIN" P3E_LOG="0.0" P3E_LOGZERO="0.0" P3G="1.0" P3N="FSC-W" P3R="262144" P3S="" P3V="662" P4B="32" P4DISPLAY="LIN" P4E_LOG="0.0" P4E_LOGZERO="0.0" P4G="1.0" P4N="SSC-A" P4R="262144" P4S="" P4V="238" P5B="32" P5DISPLAY="LIN" P5E_LOG="0.0" P5E_LOGZERO="0.0" P5G="1.0" P5N="SSC-H" P5R="262144" P5S="" P5V="238" P6B="32" P6DISPLAY="LIN" P6E_LOG="0.0" P6E_LOGZERO="0.0" P6G="1.0" P6N="SSC-W" P6R="262144" P6S="" P6V="238" P7B="32" P7DISPLAY="LOG" P7E_LOG="0.0" P7E_LOGZERO="0.0" P7G="1.0" P7N="488 530/30-A" P7R="262144" P7S="" P7V="458" P8B="32" P8DISPLAY="LOG" P8E_LOG="0.0" P8E_LOGZERO="0.0" P8G="1.0" P8N="488 530/30-H" P8R="262144" P8S="" P8V="458" P9B="32" P9DISPLAY="LOG" P9E_LOG="0.0" P9E_LOGZERO="0.0" P9G="1.0" P9N="488 670/30-A" P9R="262144" P9S="" P9V="595" name="Parameters" numEvents="5000" numParameters="11" />	\N	\N	3	2018-11-15 08:19:25.694726+01	3	2018-11-15 08:19:25.694726+01
 32	38	54	<?xml version='1.0' encoding='UTF-8'?>\n<Parameters P10B="32" P10DISPLAY="LOG" P10E_LOG="0.0" P10E_LOGZERO="0.0" P10G="1.0" P10N="488 670/30-H" P10R="262144" P10S="" P10V="595" P11B="32" P11DISPLAY="LIN" P11E_LOG="0.0" P11E_LOGZERO="0.0" P11G="0.01" P11N="Time" P11R="262144" P11S="" P11V="NaN" P1B="32" P1DISPLAY="LIN" P1E_LOG="0.0" P1E_LOGZERO="0.0" P1G="1.0" P1N="FSC-A" P1R="262144" P1S="" P1V="582" P2B="32" P2DISPLAY="LIN" P2E_LOG="0.0" P2E_LOGZERO="0.0" P2G="1.0" P2N="FSC-H" P2R="262144" P2S="" P2V="582" P3B="32" P3DISPLAY="LIN" P3E_LOG="0.0" P3E_LOGZERO="0.0" P3G="1.0" P3N="FSC-W" P3R="262144" P3S="" P3V="582" P4B="32" P4DISPLAY="LIN" P4E_LOG="0.0" P4E_LOGZERO="0.0" P4G="1.0" P4N="SSC-A" P4R="262144" P4S="" P4V="288" P5B="32" P5DISPLAY="LIN" P5E_LOG="0.0" P5E_LOGZERO="0.0" P5G="1.0" P5N="SSC-H" P5R="262144" P5S="" P5V="288" P6B="32" P6DISPLAY="LIN" P6E_LOG="0.0" P6E_LOGZERO="0.0" P6G="1.0" P6N="SSC-W" P6R="262144" P6S="" P6V="288" P7B="32" P7DISPLAY="LOG" P7E_LOG="0.0" P7E_LOGZERO="0.0" P7G="1.0" P7N="488 530/30-A" P7R="262144" P7S="" P7V="458" P8B="32" P8DISPLAY="LOG" P8E_LOG="0.0" P8E_LOGZERO="0.0" P8G="1.0" P8N="488 530/30-H" P8R="262144" P8S="" P8V="458" P9B="32" P9DISPLAY="LOG" P9E_LOG="0.0" P9E_LOGZERO="0.0" P9G="1.0" P9N="488 670/30-A" P9R="262144" P9S="" P9V="595" name="Parameters" numEvents="5000" numParameters="11" />	\N	\N	3	2018-11-15 08:19:25.694726+01	3	2018-11-15 08:19:25.694726+01
 33	39	54	<?xml version='1.0' encoding='UTF-8'?>\n<Parameters P10B="32" P10DISPLAY="LOG" P10E_LOG="0.0" P10E_LOGZERO="0.0" P10G="1.0" P10N="488 670/30-H" P10R="262144" P10S="" P10V="595" P11B="32" P11DISPLAY="LOG" P11E_LOG="0.0" P11E_LOGZERO="0.0" P11G="1.0" P11N="405 450/40-A" P11R="262144" P11S="" P11V="658" P12B="32" P12DISPLAY="LOG" P12E_LOG="0.0" P12E_LOGZERO="0.0" P12G="1.0" P12N="405 450/40-H" P12R="262144" P12S="" P12V="658" P13B="32" P13DISPLAY="LOG" P13E_LOG="0.0" P13E_LOGZERO="0.0" P13G="1.0" P13N="561 610/20-A" P13R="262144" P13S="" P13V="676" P14B="32" P14DISPLAY="LOG" P14E_LOG="0.0" P14E_LOGZERO="0.0" P14G="1.0" P14N="561 610/20-H" P14R="262144" P14S="" P14V="676" P15B="32" P15DISPLAY="LOG" P15E_LOG="0.0" P15E_LOGZERO="0.0" P15G="1.0" P15N="488 582/15-A" P15R="262144" P15S="" P15V="466" P16B="32" P16DISPLAY="LOG" P16E_LOG="0.0" P16E_LOGZERO="0.0" P16G="1.0" P16N="488 582/15-H" P16R="262144" P16S="" P16V="466" P17B="32" P17DISPLAY="LOG" P17E_LOG="0.0" P17E_LOGZERO="0.0" P17G="1.0" P17N="445 475/40-A" P17R="262144" P17S="" P17V="308" P18B="32" P18DISPLAY="LOG" P18E_LOG="0.0" P18E_LOGZERO="0.0" P18G="1.0" P18N="445 475/40-H" P18R="262144" P18S="" P18V="308" P19B="32" P19DISPLAY="LOG" P19E_LOG="0.0" P19E_LOGZERO="0.0" P19G="1.0" P19N="488 610/20-A" P19R="262144" P19S="" P19V="459" P1B="32" P1DISPLAY="LIN" P1E_LOG="0.0" P1E_LOGZERO="0.0" P1G="1.0" P1N="FSC-A" P1R="262144" P1S="" P1V="582" P20B="32" P20DISPLAY="LOG" P20E_LOG="0.0" P20E_LOGZERO="0.0" P20G="1.0" P20N="488 610/20-H" P20R="262144" P20S="" P20V="459" P21B="32" P21DISPLAY="LOG" P21E_LOG="0.0" P21E_LOGZERO="0.0" P21G="1.0" P21N="488  695/40-A" P21R="262144" P21S="" P21V="716" P22B="32" P22DISPLAY="LOG" P22E_LOG="0.0" P22E_LOGZERO="0.0" P22G="1.0" P22N="488  695/40-H" P22R="262144" P22S="" P22V="716" P23B="32" P23DISPLAY="LOG" P23E_LOG="0.0" P23E_LOGZERO="0.0" P23G="1.0" P23N="488 780/60-A" P23R="262144" P23S="" P23V="615" P24B="32" P24DISPLAY="LOG" P24E_LOG="0.0" P24E_LOGZERO="0.0" P24G="1.0" P24N="488 780/60-H" P24R="262144" P24S="" P24V="615" P25B="32" P25DISPLAY="LOG" P25E_LOG="0.0" P25E_LOGZERO="0.0" P25G="1.0" P25N="405 SSC 405/20-A" P25R="262144" P25S="" P25V="301" P26B="32" P26DISPLAY="LOG" P26E_LOG="0.0" P26E_LOGZERO="0.0" P26G="1.0" P26N="405 SSC 405/20-H" P26R="262144" P26S="" P26V="301" P27B="32" P27DISPLAY="LOG" P27E_LOG="0.0" P27E_LOGZERO="0.0" P27G="1.0" P27N="405 525/50-A" P27R="262144" P27S="" P27V="442" P28B="32" P28DISPLAY="LOG" P28E_LOG="0.0" P28E_LOGZERO="0.0" P28G="1.0" P28N="405 525/50-H" P28R="262144" P28S="" P28V="442" P29B="32" P29DISPLAY="LOG" P29E_LOG="0.0" P29E_LOGZERO="0.0" P29G="1.0" P29N="405 560/40-A" P29R="262144" P29S="" P29V="486" P2B="32" P2DISPLAY="LIN" P2E_LOG="0.0" P2E_LOGZERO="0.0" P2G="1.0" P2N="FSC-H" P2R="262144" P2S="" P2V="582" P30B="32" P30DISPLAY="LOG" P30E_LOG="0.0" P30E_LOGZERO="0.0" P30G="1.0" P30N="405 560/40-H" P30R="262144" P30S="" P30V="486" P31B="32" P31DISPLAY="LOG" P31E_LOG="0.0" P31E_LOGZERO="0.0" P31G="1.0" P31N="640 670/30-A" P31R="262144" P31S="" P31V="508" P32B="32" P32DISPLAY="LOG" P32E_LOG="0.0" P32E_LOGZERO="0.0" P32G="1.0" P32N="640 670/30-H" P32R="262144" P32S="" P32V="508" P33B="32" P33DISPLAY="LOG" P33E_LOG="0.0" P33E_LOGZERO="0.0" P33G="1.0" P33N="640 780/60-A" P33R="262144" P33S="" P33V="402" P34B="32" P34DISPLAY="LOG" P34E_LOG="0.0" P34E_LOGZERO="0.0" P34G="1.0" P34N="640 780/60-H" P34R="262144" P34S="" P34V="402" P35B="32" P35DISPLAY="LOG" P35E_LOG="0.0" P35E_LOGZERO="0.0" P35G="1.0" P35N="561 SSC 560/40-A" P35R="262144" P35S="" P35V="300" P36B="32" P36DISPLAY="LOG" P36E_LOG="0.0" P36E_LOGZERO="0.0" P36G="1.0" P36N="561 SSC 560/40-H" P36R="262144" P36S="" P36V="300" P37B="32" P37DISPLAY="LOG" P37E_LOG="0.0" P37E_LOGZERO="0.0" P37G="1.0" P37N="561 586/15-A" P37R="262144" P37S="" P37V="411" P38B="32" P38DISPLAY="LOG" P38E_LOG="0.0" P38E_LOGZERO="0.0" P38G="1.0" P38N="561 586/15-H" P38R="262144" P38S="" P38V="411" P39B="32" P39DISPLAY="LOG" P39E_LOG="0.0" P39E_LOGZERO="0.0" P39G="1.0" P39N="561 695/40-A" P39R="262144" P39S="" P39V="682" P3B="32" P3DISPLAY="LIN" P3E_LOG="0.0" P3E_LOGZERO="0.0" P3G="1.0" P3N="FSC-W" P3R="262144" P3S="" P3V="582" P40B="32" P40DISPLAY="LOG" P40E_LOG="0.0" P40E_LOGZERO="0.0" P40G="1.0" P40N="561 695/40-H" P40R="262144" P40S="" P40V="682" P41B="32" P41DISPLAY="LOG" P41E_LOG="0.0" P41E_LOGZERO="0.0" P41G="1.0" P41N="445 515/20-A" P41R="262144" P41S="" P41V="417" P42B="32" P42DISPLAY="LOG" P42E_LOG="0.0" P42E_LOGZERO="0.0" P42G="1.0" P42N="445 515/20-H" P42R="262144" P42S="" P42V="417" P43B="32" P43DISPLAY="LIN" P43E_LOG="0.0" P43E_LOGZERO="0.0" P43G="0.01" P43N="Time" P43R="262144" P43S="" P43V="NaN" P4B="32" P4DISPLAY="LIN" P4E_LOG="0.0" P4E_LOGZERO="0.0" P4G="1.0" P4N="SSC-A" P4R="262144" P4S="" P4V="288" P5B="32" P5DISPLAY="LIN" P5E_LOG="0.0" P5E_LOGZERO="0.0" P5G="1.0" P5N="SSC-H" P5R="262144" P5S="" P5V="288" P6B="32" P6DISPLAY="LIN" P6E_LOG="0.0" P6E_LOGZERO="0.0" P6G="1.0" P6N="SSC-W" P6R="262144" P6S="" P6V="288" P7B="32" P7DISPLAY="LOG" P7E_LOG="0.0" P7E_LOGZERO="0.0" P7G="1.0" P7N="488 530/30-A" P7R="262144" P7S="" P7V="458" P8B="32" P8DISPLAY="LOG" P8E_LOG="0.0" P8E_LOGZERO="0.0" P8G="1.0" P8N="488 530/30-H" P8R="262144" P8S="" P8V="458" P9B="32" P9DISPLAY="LOG" P9E_LOG="0.0" P9E_LOGZERO="0.0" P9G="1.0" P9N="488 670/30-A" P9R="262144" P9S="" P9V="595" name="Parameters" numEvents="5000" numParameters="43" />	\N	\N	3	2018-11-15 08:19:25.694726+01	3	2018-11-15 08:19:25.694726+01
+34	42	37	test link data	\N	\N	3	2019-01-15 08:19:18.83131+01	3	2019-01-15 08:19:18.83131+01
 \.
 
 
@@ -3811,7 +3814,7 @@ COPY public.data_set_properties_history (id, ds_id, dstpt_id, value, vocabulary_
 -- Name: data_set_property_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
 --
 
-SELECT pg_catalog.setval('public.data_set_property_id_seq', 34, true);
+SELECT pg_catalog.setval('public.data_set_property_id_seq', 35, true);
 
 
 --
@@ -3831,6 +3834,8 @@ COPY public.data_set_relationships_all (data_id_parent, data_id_child, relations
 6	5	3	1	\N	3	2018-11-15 08:15:36.551981+01	2018-11-15 08:15:36.551981+01
 26	24	3	0	\N	3	2018-11-15 08:19:19.181357+01	2018-11-15 08:19:19.181357+01
 26	25	3	1	\N	3	2018-11-15 08:19:19.181357+01	2018-11-15 08:19:19.181357+01
+26	42	1	0	\N	3	2019-01-15 08:19:19.181357+01	2019-01-15 08:19:19.181357+01
+42	25	1	0	\N	3	2019-01-15 08:19:19.181357+01	2019-01-15 08:19:19.181357+01
 \.
 
 
@@ -24652,7 +24657,7 @@ COPY public.external_data (id, share_id, size, location, ffty_id, loty_id, cvte_
 -- Name: external_data_management_system_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
 --
 
-SELECT pg_catalog.setval('public.external_data_management_system_id_seq', 1, false);
+SELECT pg_catalog.setval('public.external_data_management_system_id_seq', 2, false);
 
 
 --
@@ -24660,6 +24665,7 @@ SELECT pg_catalog.setval('public.external_data_management_system_id_seq', 1, fal
 --
 
 COPY public.external_data_management_systems (id, code, label, address, address_type) FROM stdin;
+1	DMS-1	DMS one	blabla:blub	FILE_SYSTEM
 \.
 
 
@@ -24724,6 +24730,7 @@ SELECT pg_catalog.setval('public.grid_custom_columns_id_seq', 1, false);
 --
 
 COPY public.link_data (id) FROM stdin;
+42
 \.
 
 
diff --git a/integration-tests/templates/test_openbis_sync_big/pathinfo_test_openbis_sync_big_data_source.sql b/integration-tests/templates/test_openbis_sync_big/pathinfo_test_openbis_sync_big_data_source.sql
index 2f1eee627c9e7dd7ae5b6613bd86f6a11ecce7ca..894a4f14028fc286283cf94eb014ae2cf8597fed 100644
--- a/integration-tests/templates/test_openbis_sync_big/pathinfo_test_openbis_sync_big_data_source.sql
+++ b/integration-tests/templates/test_openbis_sync_big/pathinfo_test_openbis_sync_big_data_source.sql
@@ -14381,6 +14381,8 @@ COPY public.data_set_files (id, dase_id, parent_id, relative_path, file_name, si
 14681	39	13819	original/PLATONIC/bPLATE_wH9_s7_cRGB.png	bPLATE_wH9_s7_cRGB.png	17400	515711153	\N	f	2018-11-28 14:37:07+01
 14682	39	13819	original/PLATONIC/bPLATE_wH9_s8_cRGB.png	bPLATE_wH9_s8_cRGB.png	18393	510932630	\N	f	2018-11-28 14:37:08+01
 14683	39	13819	original/PLATONIC/bPLATE_wH9_s9_cRGB.png	bPLATE_wH9_s9_cRGB.png	18337	1246483998	\N	f	2018-11-28 14:37:08+01
+15000	40	\N		20190115081917453-59	1000000	\N	\N	t	2019-01-15 08:09:10+01
+15001	40	15000	big_file	big_file	1000000	1345294785	SHA256:d29751f2649b32ff572b5e0a9f541ea660a50f94ff0beedfb0b692b924cc8025	f	2019-01-15 08:09:10+01
 \.
 
 
@@ -14434,6 +14436,7 @@ COPY public.data_sets (id, code, location) FROM stdin;
 36	20181115081925421-92	5CAACD17-1024-4446-87B8-9202D483B5EB/25/8b/fe/20181115081925421-92
 37	20181115081925421-94	5CAACD17-1024-4446-87B8-9202D483B5EB/73/b7/59/20181115081925421-94
 39	20181128143724485-20155	5CAACD17-1024-4446-87B8-9202D483B5EB/0f/c2/9a/20181128143724485-20155
+40	20190115081917453-59	
 \.
 
 
@@ -14441,7 +14444,7 @@ COPY public.data_sets (id, code, location) FROM stdin;
 -- Name: data_sets_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
 --
 
-SELECT pg_catalog.setval('public.data_sets_id_seq', 41, true);
+SELECT pg_catalog.setval('public.data_sets_id_seq', 42, true);
 
 
 --
diff --git a/integration-tests/test_openbis_sync_big.py b/integration-tests/test_openbis_sync_big.py
index 30fbbe07e03493714017142ae21dc354625cb0b0..970df5bfa9674fa2945ecb4daeca394c5f05d566 100755
--- a/integration-tests/test_openbis_sync_big.py
+++ b/integration-tests/test_openbis_sync_big.py
@@ -151,21 +151,57 @@ class TestCase(systemtest.testcase.TestCase):
                                "select '{0}' || t.code as code, t.description, '{0}' || s.name as validation_script "
                                + "from material_types t left join scripts s on t.validation_script_id = s.id "
                                + "where t.code like '{1}%' order by t.code")
+        self._compareDataBases("Material type property assignments", openbis_data_source, openbis_harvester, "openbis",
+                                "select '{0}' || et.code as material_type, '{0}' || pt.code as property_type, "
+                                + "etpt.is_mandatory, etpt. is_managed_internally, etpt.ordinal, etpt.section, "
+                                + "etpt.is_shown_edit, etpt.show_raw_value, '{0}' || s.name as script "
+                                + "from material_type_property_types etpt "
+                                + "join material_types et on etpt.maty_id = et.id "
+                                + "join property_types pt on etpt.prty_id = pt.id "
+                                + "left join scripts s on etpt.script_id = s.id "
+                                + "where et.code like '{1}%' order by et.code, pt.code")
         self._compareDataBases("Experiment types", openbis_data_source, openbis_harvester, "openbis", 
                                "select '{0}' || t.code as code, t.description, '{0}' || s.name as validation_script "
                                + "from experiment_types t left join scripts s on t.validation_script_id = s.id "
                                + "where t.code like '{1}%' order by t.code")
+        self._compareDataBases("Experiment type property assignments", openbis_data_source, openbis_harvester, "openbis",
+                                "select '{0}' || et.code as experiment_type, '{0}' || pt.code as property_type, "
+                                + "etpt.is_mandatory, etpt. is_managed_internally, etpt.ordinal, etpt.section, "
+                                + "etpt.is_shown_edit, etpt.show_raw_value, '{0}' || s.name as script "
+                                + "from experiment_type_property_types etpt "
+                                + "join experiment_types et on etpt.exty_id = et.id "
+                                + "join property_types pt on etpt.prty_id = pt.id "
+                                + "left join scripts s on etpt.script_id = s.id "
+                                + "where et.code like '{1}%' order by et.code, pt.code")
         self._compareDataBases("Sample types", openbis_data_source, openbis_harvester, "openbis", 
                                "select '{0}' || code as code, t.description, is_listable, generated_from_depth, part_of_depth "
                                + "  is_auto_generated_code, generated_code_prefix, is_subcode_unique, inherit_properties, "
                                + "  show_parent_metadata, '{0}' || s.name as validation_script "
                                + "from sample_types t left join scripts s on t.validation_script_id = s.id "
                                + "where code like '{1}%' order by code")
+        self._compareDataBases("Sample type property assignments", openbis_data_source, openbis_harvester, "openbis",
+                                "select '{0}' || et.code as sample_type, '{0}' || pt.code as property_type, "
+                                + "etpt.is_mandatory, etpt. is_managed_internally, etpt.ordinal, etpt.section, "
+                                + "etpt.is_shown_edit, etpt.show_raw_value, '{0}' || s.name as script "
+                                + "from sample_type_property_types etpt "
+                                + "join sample_types et on etpt.saty_id = et.id "
+                                + "join property_types pt on etpt.prty_id = pt.id "
+                                + "left join scripts s on etpt.script_id = s.id "
+                                + "where et.code like '{1}%' order by et.code, pt.code")
         self._compareDataBases("Data set types", openbis_data_source, openbis_harvester, "openbis", 
                                "select '{0}' || code as code, t.description, main_ds_pattern, main_ds_path, "
                                + "  deletion_disallow, '{0}' || s.name as validation_script "
                                + "from data_set_types t left join scripts s on t.validation_script_id = s.id "
                                + "where code like '{1}%' order by code")
+        self._compareDataBases("Data set type property assignments", openbis_data_source, openbis_harvester, "openbis",
+                                "select '{0}' || et.code as data_set_type, '{0}' || pt.code as property_type, "
+                                + "etpt.is_mandatory, etpt. is_managed_internally, etpt.ordinal, etpt.section, "
+                                + "etpt.is_shown_edit, etpt.show_raw_value, '{0}' || s.name as script "
+                                + "from data_set_type_property_types etpt "
+                                + "join data_set_types et on etpt.dsty_id = et.id "
+                                + "join property_types pt on etpt.prty_id = pt.id "
+                                + "left join scripts s on etpt.script_id = s.id "
+                                + "where et.code like '{1}%' order by et.code, pt.code")
         self._compareDataBases("Plugins", openbis_data_source, openbis_harvester, "openbis", 
                                "select '{0}' || name as name, description, script_type, plugin_type, entity_kind, is_available, "
                                + "  length(script) as script_length, md5(script) as script_hash "
diff --git a/microscopy-migration-tool/migration_report_scu.txt b/microscopy-migration-tool/migration_report_scu.txt
index 06a0a336bdda458318489708bbb164168316496f..fa2020ac24c76ca93d82f3e5c6a415f727bfcfc9 100644
--- a/microscopy-migration-tool/migration_report_scu.txt
+++ b/microscopy-migration-tool/migration_report_scu.txt
@@ -1,11 +1,15 @@
 Example: java -jar microscopy_migration_tool.jar https://openbis-domain.ethz.ch user password
 Migration Started
+2019-01-28 15:08:00.063:INFO::main: Logging initialized @756ms to org.eclipse.jetty.util.log.StdErrLog
+2019-01-28 15:08:00.226:WARN:oejusS.config:main: Trusting all certificates configured for SslContextFactory@158da8e[provider=null,keyStore=null,trustStore=null]
+2019-01-28 15:08:00.227:WARN:oejusS.config:main: No Client EndPointIdentificationAlgorithm configured for SslContextFactory@158da8e[provider=null,keyStore=null,trustStore=null]
 Project samples enabled.
 Installing Missing ELN Types.
 ELN Types installed.
-1. Installing new sample types
+1. Installing types
 MICROSCOPY_EXPERIMENT Sample Type installed.
 ORGANIZATION_UNIT Sample Type installed.
+ATTACHMENT DataSet Type installed.
 2. Creating new ORGANIZATION_UNITS_COLLECTION and MICROSCOPY_EXPERIMENTS_COLLECTION
 Space Project Created: /BEERENWINKEL_GROUP/COMMON_ORGANIZATION_UNITS
 Space Experiment Created: /BEERENWINKEL_GROUP/COMMON_ORGANIZATION_UNITS/ORGANIZATION_UNITS_COLLECTION
@@ -484,45 +488,76 @@ Space Experiment Created: /TAY_GROUP/COMMON_ORGANIZATION_UNITS/ORGANIZATION_UNIT
 3. Preparing copy of experiment of type MICROSCOPY_EXPERIMENT to samples of type MICROSCOPY_EXPERIMENT
 Found 253 MICROSCOPY_EXPERIMENT to migrate 
 4. Copy of experiments of type MICROSCOPY_EXPERIMENT to samples of type MICROSCOPY_EXPERIMENT
-Tags readed - 703 tags for 258 entities.
-Tags Found: 2 Tags Missing: 2 Tags Created: 2
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 2 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 2 Tags Missing: 2 Tags Created: 2
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 2 Tags Missing: 2 Tags Created: 2
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 5 Tags Missing: 4 Tags Created: 4
-Tags Found: 2 Tags Missing: 2 Tags Created: 2
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 2 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 2 Tags Missing: 1 Tags Created: 1
-Tags Found: 2 Tags Missing: 1 Tags Created: 1
-Tags Found: 3 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 1 Tags Missing: 1 Tags Created: 1
-Tags Found: 2 Tags Missing: 2 Tags Created: 2
+Tags readed - 85 tags for 64 entities.
+Tags Found: 2 Tags Missing: 2
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 1
+Tags Found: 2 Tags Missing: 1
+Tags Found: 2 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 0
+Tags Found: 2 Tags Missing: 2
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 1
+Tags Found: 2 Tags Missing: 2
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 5 Tags Missing: 4
+Tags Found: 1 Tags Missing: 0
+Tags Found: 2 Tags Missing: 2
+Tags Found: 1 Tags Missing: 1
+Tags Found: 2 Tags Missing: 1
+Tags Found: 2 Tags Missing: 0
+Tags Found: 2 Tags Missing: 0
+Tags Found: 2 Tags Missing: 0
+Tags Found: 1 Tags Missing: 1
+Tags Found: 3 Tags Missing: 0
+Tags Found: 2 Tags Missing: 1
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 2 Tags Missing: 1
+Tags Found: 3 Tags Missing: 1
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 1
+Tags Found: 1 Tags Missing: 0
+Tags Found: 1 Tags Missing: 1
+Tags Found: 2 Tags Missing: 2
 Created 253 MICROSCOPY_EXPERIMENT Samples.
 4.2 Write SQL Audit Update
 Writing openbis_audit_data_update.sql
 openbis_audit_data_update.sql writen.
+4.3 Migrate Attachments
 5. Moving Samples out from MICROSCOPY_EXPERIMENT Experiments to MICROSCOPY_EXPERIMENT Samples
 Moving 15571 Samples from Experiment MICROSCOPY_EXPERIMENT to COLLECTION.
 Updated 1000/15571
diff --git a/microscopy-migration-tool/src/ethz/ch/DatasetCreationHelper.java b/microscopy-migration-tool/src/ethz/ch/DatasetCreationHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..c98312b68bbd63197229ed0ee7d98243da4dce84
--- /dev/null
+++ b/microscopy-migration-tool/src/ethz/ch/DatasetCreationHelper.java
@@ -0,0 +1,116 @@
+package ethz.ch;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.commons.io.FileUtils;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.MultiPartContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.EntityKind;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id.EntityTypePermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SampleIdentifier;
+import ch.ethz.sis.openbis.generic.dssapi.v3.IDataStoreServerApi;
+import ch.ethz.sis.openbis.generic.dssapi.v3.dto.dataset.create.UploadedDataSetCreation;
+import ch.systemsx.cisd.common.http.JettyHttpClientFactory;
+
+public class DatasetCreationHelper
+{
+    private static String serviceURL;
+    
+    public static void setDssURL(String dssURL) {
+        serviceURL = dssURL + "/datastore_server/store_share_file_upload";
+    }
+    
+    public static final String SESSION_ID_PARAM = "sessionID";
+
+    public static final String DATA_SET_TYPE_PARAM = "dataSetType";
+
+    public static final String IGNORE_FILE_PATH_PARAM = "ignoreFilePath";
+
+    public static final String FOLDER_PATH_PARAM = "folderPath";
+
+    public static final String UPLOAD_ID_PARAM = "uploadID";
+    
+    public static void createDataset(IDataStoreServerApi v3dss, String sessionToken, String sampleIdentifier, String dataSetType, String fileName, byte[] content, Map<String,String> properties) {
+        String uploadId = UUID.randomUUID().toString();
+        String folder = sampleIdentifier.substring(1).replace('/', '+');
+        try
+        {
+            uploadFile(sessionToken, uploadId, dataSetType, false, "O+" + folder + "+ATTACHMENT" , fileName, content);
+            
+            UploadedDataSetCreation uploadedDataSetCreation = new UploadedDataSetCreation();
+            uploadedDataSetCreation.setTypeId(new EntityTypePermId(dataSetType, EntityKind.DATA_SET));
+            uploadedDataSetCreation.setSampleId(new SampleIdentifier(sampleIdentifier));
+            uploadedDataSetCreation.setUploadId(uploadId);
+            uploadedDataSetCreation.setProperties(properties);
+            
+            v3dss.createUploadedDataSet(sessionToken, uploadedDataSetCreation);
+        } catch (Exception e)
+        {
+            System.out.println("Exception creating dataset, will be stored to upload latter using the eln-lims dropbox: " + e);
+            e.printStackTrace();
+            saveForLatter(folder, fileName, content, properties);
+        }
+    }
+    
+    private static void saveForLatter(String folderPath, String fileName, byte[] content, Map<String,String> properties) {
+          File parentFolder = new File("./attachments/" + folderPath);
+          parentFolder.mkdirs();
+          File attachmentFile = new File(parentFolder.getAbsolutePath() + "/" + fileName);
+          
+          try
+          {
+              FileUtils.writeByteArrayToFile(attachmentFile, content);
+              for(String key: properties.keySet()) {
+                  File metadataFile = new File(parentFolder.getAbsolutePath() + "/" + key+ ".txt");
+                  FileUtils.writeByteArrayToFile(metadataFile, properties.get(key).getBytes());
+              }
+          } catch (IOException e)
+          {
+              System.out.println("Failed to write file");
+          }
+    }
+    private static ContentResponse uploadFile(String sessionToken, String uploadId, String dataSetType, Boolean ignoreFilePath, String folderPath,
+            String fileName, byte[] content)
+            throws InterruptedException, TimeoutException, ExecutionException
+    {
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        MultiPartContentProvider multiPart = new MultiPartContentProvider();
+        multiPart.addFilePart(fileName, fileName, new BytesContentProvider(content), null);
+        multiPart.close();
+
+        Request request = client.newRequest(serviceURL).method(HttpMethod.POST);
+
+        if (sessionToken != null)
+        {
+            request.param(SESSION_ID_PARAM, sessionToken);
+        }
+        if (uploadId != null)
+        {
+            request.param(UPLOAD_ID_PARAM, uploadId);
+        }
+        if (ignoreFilePath != null)
+        {
+            request.param(IGNORE_FILE_PATH_PARAM, String.valueOf(ignoreFilePath));
+        }
+        if (folderPath != null)
+        {
+            request.param(FOLDER_PATH_PARAM, String.valueOf(folderPath));
+        }
+        if (dataSetType != null)
+        {
+            request.param(DATA_SET_TYPE_PARAM, dataSetType);
+        }
+        request.content(multiPart);
+        return request.send();
+    }
+}
diff --git a/microscopy-migration-tool/src/ethz/ch/Migration.java b/microscopy-migration-tool/src/ethz/ch/Migration.java
index 993aa6da0b939f065d1e9e8093d48ea6740c5b62..49de2407fd506f99f27e80e392ecda2160e3d1eb 100644
--- a/microscopy-migration-tool/src/ethz/ch/Migration.java
+++ b/microscopy-migration-tool/src/ethz/ch/Migration.java
@@ -10,8 +10,12 @@ import java.util.List;
 import java.util.Map;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.attachment.Attachment;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSet;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSetType;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.DataSetTypeFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.search.DataSetTypeSearchCriteria;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.update.DataSetUpdate;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.deletion.id.IDeletionId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id.EntityTypePermId;
@@ -42,12 +46,14 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.Space;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.fetchoptions.SpaceFetchOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.id.SpacePermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.search.SpaceSearchCriteria;
+import ch.ethz.sis.openbis.generic.dssapi.v3.IDataStoreServerApi;
 import ch.systemsx.cisd.common.spring.HttpInvokerUtils;
 
 public class Migration
 {
     
     private static final String OPENBIS_LOCAL_DEV = "http://localhost:8888";
+    private static final String DSS_LOCAL_DEV = "http://localhost:8889";
 //    private static final String OPENBIS_LOCAL_PROD = "https://localhost:8443";
 //    private static final String OPENBIS_SCU = "https://openbis-scu.ethz.ch";
 //    private static final String OPENBIS_SCU_TEST = "https://bs-lamp09.ethz.ch:8443/";
@@ -60,20 +66,28 @@ public class Migration
     {
         if(args.length == 7) {
             boolean COMMIT_CHANGES_TO_OPENBIS = Boolean.parseBoolean(args[0]);
-            String URL = args[1] + "/openbis/openbis" + IApplicationServerApi.SERVICE_URL;
-            String user = args[2];
-            String pass = args[3];
-            doTheWork(COMMIT_CHANGES_TO_OPENBIS, URL, user, pass, true, true, true);
+            String AS_URL = args[1] + "/openbis/openbis" + IApplicationServerApi.SERVICE_URL;
+            String DSS_URL = args[2] + "/datastore_server" + IDataStoreServerApi.SERVICE_URL;
+            DatasetCreationHelper.setDssURL(args[2]);
+            String user = args[3];
+            String pass = args[4];
+            doTheWork(COMMIT_CHANGES_TO_OPENBIS, AS_URL, DSS_URL, user, pass, true, true, true);
         } else {
             System.out.println("Example: java -jar microscopy_migration_tool.jar https://openbis-domain.ethz.ch user password");
-            doTheWork(true, OPENBIS_LOCAL_DEV + "/openbis/openbis" + IApplicationServerApi.SERVICE_URL, "pontia", "migrationtool", true, true, true);
+            DatasetCreationHelper.setDssURL(DSS_LOCAL_DEV);
+            doTheWork(true, 
+                    OPENBIS_LOCAL_DEV + "/openbis/openbis" + IApplicationServerApi.SERVICE_URL, 
+                    DSS_LOCAL_DEV + "/datastore_server" + IDataStoreServerApi.SERVICE_URL, 
+                    "pontia", "migrationtool", true, true, true);
         }
     }
     
-    private static void doTheWork(boolean COMMIT_CHANGES_TO_OPENBIS, String URL, String userId, String pass, boolean installELNTypes, boolean migrateData, boolean deleteOldExperiments) {
+    private static void doTheWork(boolean COMMIT_CHANGES_TO_OPENBIS, String AS_URL, String DSS_URL, String userId, String pass, boolean installELNTypes, boolean migrateData, boolean deleteOldExperiments) {
         System.out.println("Migration Started");
-        SslCertificateHelper.trustAnyCertificate(URL);
-        IApplicationServerApi v3 = HttpInvokerUtils.createServiceStub(IApplicationServerApi.class, URL, TIMEOUT);
+        SslCertificateHelper.trustAnyCertificate(AS_URL);
+        SslCertificateHelper.trustAnyCertificate(DSS_URL);
+        IApplicationServerApi v3 = HttpInvokerUtils.createServiceStub(IApplicationServerApi.class, AS_URL, TIMEOUT);
+        IDataStoreServerApi v3dss = HttpInvokerUtils.createServiceStub(IDataStoreServerApi.class, DSS_URL, TIMEOUT);
         String sessionToken = v3.login(userId, pass);
         Map<String, String> serverInfo = v3.getServerInformation(sessionToken);
         
@@ -88,7 +102,7 @@ public class Migration
             MigrationMasterdataHelper.installELNTypes(sessionToken, v3, COMMIT_CHANGES_TO_OPENBIS);
         }
         if(migrateData) {
-            migrate(sessionToken, v3, COMMIT_CHANGES_TO_OPENBIS);
+            migrate(sessionToken, v3, v3dss, COMMIT_CHANGES_TO_OPENBIS);
         }
         if(deleteOldExperiments) {
             deleteMICROSCOPY_EXPERIMENTExperiments(sessionToken, v3, COMMIT_CHANGES_TO_OPENBIS);
@@ -97,12 +111,12 @@ public class Migration
         System.out.println("Migration Finished");
     }
     
-    private static void migrate(String sessionToken, IApplicationServerApi v3, boolean COMMIT_CHANGES_TO_OPENBIS) {
+    private static void migrate(String sessionToken, IApplicationServerApi v3, IDataStoreServerApi v3dss,boolean COMMIT_CHANGES_TO_OPENBIS) {
         
         //
         // 1. Installing new sample types
         //
-        System.out.println("1. Installing new sample types");
+        System.out.println("1. Installing types");
         
         // Install Sample Type MICROSCOPY_EXPERIMENT
         if(COMMIT_CHANGES_TO_OPENBIS && !doSampleTypeExist(v3, sessionToken, "MICROSCOPY_EXPERIMENT")) {
@@ -116,6 +130,12 @@ public class Migration
         }
         System.out.println("ORGANIZATION_UNIT Sample Type installed.");
         
+        if(COMMIT_CHANGES_TO_OPENBIS && !doDataSetTypeExist(v3, sessionToken, "ATTACHMENT")) {
+            v3.createDataSetTypes(sessionToken, Collections.singletonList(MigrationMasterdataHelper.getDataSetTypeATTACHMENT()));
+        }
+        
+        System.out.println("ATTACHMENT DataSet Type installed.");
+        
         //
         // 2. Creating new ORGANIZATION_UNITS_COLLECTION and MICROSCOPY_EXPERIMENTS_COLLECTION
         //
@@ -178,6 +198,7 @@ public class Migration
         experimentFetchOptions.withDataSets().withSample();
         experimentFetchOptions.withRegistrator();
         experimentFetchOptions.withModifier();
+        experimentFetchOptions.withAttachments().withContent();
         
         SearchResult<Experiment> experiments = v3.searchExperiments(sessionToken, experimentSearchCriteria, experimentFetchOptions);
         Map<SampleCreation, Experiment> experimentsToMigrateBySampleCreation = new HashMap<>();
@@ -229,6 +250,9 @@ public class Migration
             
             // SQL Audit data update
             AuditDataHelper.addAuditData(sampleIdentifier, experiment);
+            
+            // Save Attachments
+            MigrationAttachmentsHelper.addAttachmentData(experiment);
         }
         
         //
@@ -259,6 +283,21 @@ public class Migration
         
         AuditDataHelper.writeSQLAuditUpdate();
         
+        System.out.println("4.3 Migrate Attachments");
+        
+        Map<String, List<Attachment>> attachments = MigrationAttachmentsHelper.getAttachments();
+        for(String sampleIdentifier :attachments.keySet()) {
+            for(Attachment attachment:attachments.get(sampleIdentifier)) {
+                if(COMMIT_CHANGES_TO_OPENBIS) {
+                    Map<String, String> properties = new HashMap<>();
+                    if(attachment.getDescription() != null) {
+                        properties.put("NOTES", attachment.getDescription());
+                    }
+                    DatasetCreationHelper.createDataset(v3dss, sessionToken, sampleIdentifier, "ATTACHMENT", attachment.getFileName(), attachment.getContent(), properties);
+                }
+            }
+        }
+            
         //
         // 5. Moving Samples out from MICROSCOPY_EXPERIMENT Experiments to MICROSCOPY_EXPERIMENT Samples
         //
@@ -362,6 +401,15 @@ public class Migration
     //
     // Helper functions
     //
+    private static boolean doDataSetTypeExist(IApplicationServerApi v3, String sessionToken, String dataSetTypeCode) {
+        DataSetTypeSearchCriteria criteria = new DataSetTypeSearchCriteria();
+        criteria.withCode().thatEquals(dataSetTypeCode);
+
+        SearchResult<DataSetType> type = v3.searchDataSetTypes(sessionToken, criteria, new DataSetTypeFetchOptions());
+
+        return !type.getObjects().isEmpty();
+    }
+    
     private static boolean doSampleTypeExist(IApplicationServerApi v3, String sessionToken, String sampleTypeCode) {
         SampleTypeSearchCriteria criteria = new SampleTypeSearchCriteria();
         criteria.withCode().thatEquals(sampleTypeCode);
diff --git a/microscopy-migration-tool/src/ethz/ch/MigrationAttachmentsHelper.java b/microscopy-migration-tool/src/ethz/ch/MigrationAttachmentsHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..07cc28ffa211f56f169bc504d7418585244164d5
--- /dev/null
+++ b/microscopy-migration-tool/src/ethz/ch/MigrationAttachmentsHelper.java
@@ -0,0 +1,33 @@
+package ethz.ch;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.Experiment;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.attachment.Attachment;
+
+public class MigrationAttachmentsHelper
+{   
+    private static Map<String, List<Attachment>> attachments = new HashMap<>();
+    
+    public static void addAttachmentData(Experiment experiment) {
+        if(!experiment.getAttachments().isEmpty()) {
+            String newSampleIdentifier = "/" + experiment.getIdentifier().getIdentifier().split("/")[1] + "/" + 
+                    experiment.getIdentifier().getIdentifier().split("/")[2] + "/" +
+                    experiment.getCode();
+            attachments.put(newSampleIdentifier, experiment.getAttachments());
+        }
+    }
+    
+    public static Map<String, List<Attachment>> getAttachments() {
+        return attachments;
+    }
+    
+    
+    
+}
diff --git a/microscopy-migration-tool/src/ethz/ch/MigrationMasterdataHelper.java b/microscopy-migration-tool/src/ethz/ch/MigrationMasterdataHelper.java
index f96831cd1f669740d8004d7c336abb0f32d470c4..8743ba41b3273dd1783f008c2fb4842895605e4d 100644
--- a/microscopy-migration-tool/src/ethz/ch/MigrationMasterdataHelper.java
+++ b/microscopy-migration-tool/src/ethz/ch/MigrationMasterdataHelper.java
@@ -5,6 +5,7 @@ import java.util.Arrays;
 import java.util.List;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.create.DataSetTypeCreation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.ExperimentType;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.create.ExperimentTypeCreation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.fetchoptions.ExperimentTypeFetchOptions;
@@ -35,11 +36,12 @@ public class MigrationMasterdataHelper
     
     public static void createPropertiesIfMissing(String sessionToken, IApplicationServerApi v3) {
         PropertyTypeSearchCriteria propertyTypeSearchCriteria = new PropertyTypeSearchCriteria();
-        propertyTypeSearchCriteria.withCodes().setFieldValue(Arrays.asList("$NAME", "$DEFAULT_OBJECT_TYPE", "$XMLCOMMENTS", "$ANNOTATIONS_STATE"));
+        propertyTypeSearchCriteria.withCodes().setFieldValue(Arrays.asList("$NAME", "NOTES", "$DEFAULT_OBJECT_TYPE", "$XMLCOMMENTS", "$ANNOTATIONS_STATE"));
         propertyTypeSearchCriteria.withOrOperator();
         
         List<PropertyType> propertyTypes = v3.searchPropertyTypes(sessionToken, propertyTypeSearchCriteria, new PropertyTypeFetchOptions()).getObjects();
         boolean name = false;
+        boolean notes = false;
         boolean default_object_type = false;
         boolean xmlcomments = false;
         boolean annotations_state = false;
@@ -48,6 +50,9 @@ public class MigrationMasterdataHelper
             if(propertyType.getCode().equals("$NAME")) {
                 name = true;
             }
+            if(propertyType.getCode().equals("NOTES")) {
+                notes = true;
+            }
             if(propertyType.getCode().equals("$DEFAULT_OBJECT_TYPE")) {
                 default_object_type = true;
             }
@@ -71,6 +76,15 @@ public class MigrationMasterdataHelper
             toCreate.add(NAME);
         }
         
+        if(!notes) {
+            PropertyTypeCreation NOTES = new PropertyTypeCreation();
+            NOTES.setCode("NOTES");
+            NOTES.setLabel("Notes");
+            NOTES.setDescription("Notes");
+            NOTES.setDataType(DataType.VARCHAR);
+            toCreate.add(NOTES);
+        }
+        
         if(!default_object_type) {
             PropertyTypeCreation DEFAULT_OBJECT_TYPE = new PropertyTypeCreation();
             DEFAULT_OBJECT_TYPE.setCode("$DEFAULT_OBJECT_TYPE");
@@ -129,6 +143,30 @@ public class MigrationMasterdataHelper
         }
     }
     
+    //
+    // NEW TYPE TO MIGRATE ATTACHMENTS
+    //
+    
+    public static DataSetTypeCreation getDataSetTypeATTACHMENT() {
+        PropertyAssignmentCreation NAME = new PropertyAssignmentCreation();
+        NAME.setPropertyTypeId(new PropertyTypePermId("$NAME"));
+        
+        PropertyAssignmentCreation NOTES = new PropertyAssignmentCreation();
+        NOTES.setPropertyTypeId(new PropertyTypePermId("NOTES"));
+        
+        PropertyAssignmentCreation XMLCOMMENTS = new PropertyAssignmentCreation();
+        XMLCOMMENTS.setPropertyTypeId(new PropertyTypePermId("$XMLCOMMENTS"));
+        XMLCOMMENTS.setShowInEditView(Boolean.FALSE);
+        
+        
+        DataSetTypeCreation creation = new DataSetTypeCreation();
+        creation.setCode("ATTACHMENT");
+        creation.setDescription("Used to attach files to entities.");
+        creation.setPropertyAssignments(Arrays.asList(NAME, NOTES, XMLCOMMENTS));
+        
+        return creation;
+    }
+    
     //
     // NEW TYPE TO MIGRATE TAGS
     //
diff --git a/obis/src/python/obis/dm/checksum.py b/obis/src/python/obis/dm/checksum.py
index aaa64942fbaf16be0f1503ba795044bfb5edeec5..f3ba05be4eb770d24f8e71ee4e5fd3026fe4f0e5 100644
--- a/obis/src/python/obis/dm/checksum.py
+++ b/obis/src/python/obis/dm/checksum.py
@@ -6,6 +6,8 @@ from abc import ABC, abstractmethod
 from .utils import run_shell, cd
 from .command_result import CommandResult, CommandException
 
+# We generate checksums for small files according to what is used by git annex,
+# This ensures that all files in a data set have the same checksum type.
 
 def get_checksum_generator(checksum_type, data_path, metadata_path, default=None):
     if checksum_type == "SHA256":
@@ -29,6 +31,7 @@ def validate_checksum(openbis, files, data_set_id, data_path, metadata_path):
     for filename in files:
         dataset_file = dataset_files_by_path[filename]
         checksum_generator = None
+        # data set files have either checksumCRC32 or checksumType and checksum.
         if dataset_file['checksumCRC32'] is not None and dataset_file['checksumCRC32'] > 0:
             checksum_generator = ChecksumGeneratorCrc32(data_path, metadata_path)
             expected_checksum = dataset_file['checksumCRC32']
@@ -72,9 +75,11 @@ class ChecksumGeneratorCrc32(ChecksumGenerator):
 class ChecksumGeneratorHashlib(ChecksumGenerator):
 
     def hash_function(self):
+        """ Implemented in subclass. """
         pass
 
     def hash_type(self):
+        """ Implemented in subclass. """
         pass
 
     def _get_checksum(self, file):
@@ -122,6 +127,7 @@ class ChecksumGeneratorWORM(ChecksumGenerator):
 
 
 class ChecksumGeneratorGitAnnex(ChecksumGenerator):
+    """ This class generates checksums according to the git annex backend configuration. """
 
     def __init__(self, data_path, metadata_path):
         self.data_path = data_path
diff --git a/obis/src/python/obis/dm/command_log.py b/obis/src/python/obis/dm/command_log.py
index a8ad203d00b5144cfe6591ecdabbb5efe481ab9f..a503d828a17e17504683a6b75893270a853194e7 100644
--- a/obis/src/python/obis/dm/command_log.py
+++ b/obis/src/python/obis/dm/command_log.py
@@ -3,6 +3,12 @@ import os
 
 
 class CommandLog(object):
+    """ CommandLog can write a log for a command with multiple steps.
+    The idea is that, when a later step fails, we want to know what happened 
+    before. This can be useful for cases where no automatic recovery can 
+    be done. If the success method is called, the log is removed.
+    If there is an existing log, the user must first make sure everything 
+    is in order and delete the log before using obis """
 
     def __init__(self):
         self.folder_path = os.path.join(os.path.expanduser('~'), ".obis", "log")
diff --git a/obis/src/python/obis/dm/command_result.py b/obis/src/python/obis/dm/command_result.py
index 37bd5794323e02d6c50e410b103299b270290097..cb33fb9afae26947d6fb5f0eb903c98fe0e08279 100644
--- a/obis/src/python/obis/dm/command_result.py
+++ b/obis/src/python/obis/dm/command_result.py
@@ -29,6 +29,8 @@ class CommandResult(object):
 
 
 class CommandException(Exception):
+    """ Instead of returning a CommandResult for all actions, we can also return 
+    a result normally and throw a CommandException instead on error. """
     
     def __init__(self, command_result):
         self.command_result = command_result
diff --git a/obis/src/python/obis/dm/commands/clone.py b/obis/src/python/obis/dm/commands/clone.py
index 0dd0689024df527223ca0107c5cce089491ecf98..dd7a36c79e4e324c83cadad6cb7f355ee156674b 100644
--- a/obis/src/python/obis/dm/commands/clone.py
+++ b/obis/src/python/obis/dm/commands/clone.py
@@ -14,9 +14,17 @@ class Clone(OpenbisCommand):
     """
     Implements the clone command. Copies a repository from a content copy of a data set
     and adds the local copy as a new content copy using the addref command.
+    Validates the checksums of the copied files unless skip_integrity_check is True.
     """
 
     def __init__(self, dm, data_set_id, ssh_user, content_copy_index, skip_integrity_check):
+        """
+        :param dm: data management
+        :param data_set_id: permId of the data set to be cloned
+        :param ssh_user: user for remote access
+        :param content_copy_index: in case of multiple content copied
+        :param skip_integrity_check: Checksums are not validated if True
+        """
         if dm.data_path != dm.metadata_path:
             raise CommandException(CommandResult(returncode=-1, output='Clone/move not supported with obis_metadata_folder.'))
         self.data_set_id = data_set_id
diff --git a/obis/src/python/obis/dm/commands/download.py b/obis/src/python/obis/dm/commands/download.py
index 0c3bf4e662b2b22e7bd0dc323d74f634718adbd7..a41fde6d35d0b112ab30b1ca0d66da65d14229ca 100644
--- a/obis/src/python/obis/dm/commands/download.py
+++ b/obis/src/python/obis/dm/commands/download.py
@@ -7,13 +7,19 @@ from ..utils import cd
 
 class Download(OpenbisCommand):
     """
-    Command to download files of a data set. Uses the microservice server to access the files.
+    Command to download files of a data set. Uses the big data link server to access the files.
     As opposed to the clone, the user does not need to be able to access the files via ssh 
     and no new content copy is created in openBIS.
     """
 
 
     def __init__(self, dm, data_set_id, content_copy_index, file, skip_integrity_check):
+        """
+        :param dm: data management
+        :param data_set_id: permId of the data set to be cloned
+        :param content_copy_index: in case of multiple content copied
+        :param skip_integrity_check: Checksums are not validated if True
+        """
         self.data_set_id = data_set_id
         self.content_copy_index = content_copy_index
         self.files = [file] if file is not None else None
diff --git a/obis/src/python/obis/dm/commands/move.py b/obis/src/python/obis/dm/commands/move.py
index 95510c88b79e847a77ad9f4138397eec6c23f9ff..d3afd82093534b2ec7c85bdbd51158f7cbb4fb61 100644
--- a/obis/src/python/obis/dm/commands/move.py
+++ b/obis/src/python/obis/dm/commands/move.py
@@ -13,10 +13,18 @@ from ... import dm
 
 class Move(OpenbisCommand):
     """
-    Implements the move command. Uses other commands for implementation.
+    Implements the move command. Uses clone, then deletes the old content 
+    copy in openBIS and deletes the repository from the old location.
     """
 
     def __init__(self, dm, data_set_id, ssh_user, content_copy_index, skip_integrity_check):
+        """
+        :param dm: data management
+        :param data_set_id: permId of the data set to be cloned
+        :param ssh_user: user for remote access
+        :param content_copy_index: in case of multiple content copied
+        :param skip_integrity_check: Checksums are not validated if True
+        """
         self.data_set_id = data_set_id
         self.ssh_user = ssh_user
         self.content_copy_index = content_copy_index
diff --git a/obis/src/python/obis/dm/commands/openbis_command.py b/obis/src/python/obis/dm/commands/openbis_command.py
index 029fe6778e66e81584d69990b21929e21da74430..ca1c8344efe76e437be986188f59a55b3d8304f7 100644
--- a/obis/src/python/obis/dm/commands/openbis_command.py
+++ b/obis/src/python/obis/dm/commands/openbis_command.py
@@ -11,6 +11,8 @@ from ...scripts import cli
 
 
 class OpenbisCommand(object):
+    """ Superclass for commands connecting to openBIS.
+    """
 
     def __init__(self, dm):
         self.data_mgmt = dm
@@ -127,6 +129,8 @@ class OpenbisCommand(object):
 
 
     def login(self):
+        """ Checks for valid session and asks user for password
+        if login is needed. """
         user = self.user()
         if self.openbis.is_session_active():
             if self.openbis.token.startswith(user):
@@ -216,7 +220,8 @@ class OpenbisCommand(object):
 
 
 class ContentCopySelector(object):
-
+    """ In case a command needs information from a content copy, this class
+    asks the user to pick one if there are multiple. """
 
     def __init__(self, data_set, content_copy_index=None, get_index=False):
         self.data_set = data_set
diff --git a/obis/src/python/obis/dm/commands/openbis_sync.py b/obis/src/python/obis/dm/commands/openbis_sync.py
index 4b01c85610d2f10b81ce78c6adf4821063e014d7..34e5446d257119c54a067b564ce146b4ec086b46 100644
--- a/obis/src/python/obis/dm/commands/openbis_sync.py
+++ b/obis/src/python/obis/dm/commands/openbis_sync.py
@@ -7,10 +7,22 @@ from .openbis_command import OpenbisCommand
 
 
 class OpenbisSync(OpenbisCommand):
-    """A command object for synchronizing with openBIS."""
+    """A command object for synchronizing with openBIS.
+    1) Checks that the previous data set exists.
+    2) Does nothing if the current git hash is already tracked in openBIS.
+    3) Ensures the repository id.
+    4) Ensures the external data management system.
+    5) Creates the data set in openBIS.
+    6) Updates the obis metadata.
+    """
 
 
     def __init__(self, dm, ignore_missing_parent=False):
+        """
+        :param ignore_missing_parent: Normally, there is an error if the data_set_id 
+            of the repository doesn't exist. If this flag is true, this error is ignored 
+            and the a new data set can be created.
+        """
         self.ignore_missing_parent = ignore_missing_parent
         super(OpenbisSync, self).__init__(dm)
 
@@ -133,6 +145,10 @@ class OpenbisSync(OpenbisCommand):
 
 
     def run(self, info_only=False):
+        """
+        :param info_only: If true, nothing is actually synced. We only get the info 
+            whether or not a sync is needed.
+        """
 
         ignore_parent = False
 
diff --git a/obis/src/python/obis/dm/commands/removeref.py b/obis/src/python/obis/dm/commands/removeref.py
index ffc7481de635554e7cd1faf7796be7ffb21b4a22..a145a80dd1d40e6a4959f27e175982815098770d 100644
--- a/obis/src/python/obis/dm/commands/removeref.py
+++ b/obis/src/python/obis/dm/commands/removeref.py
@@ -7,8 +7,8 @@ from ..utils import complete_openbis_config
 
 class Removeref(OpenbisCommand):
     """
-    Command to add the current folder, which is supposed to be an obis repository, as 
-    a new content copy to openBIS.
+    Command to remove the content copy corresponding to the
+    obis repository from openBIS.
     """
 
     def __init__(self, dm, data_set_id=None):
diff --git a/obis/src/python/obis/dm/data_mgmt.py b/obis/src/python/obis/dm/data_mgmt.py
index b8a2f6b2556c0b13f917a95b887254ae5229cb61..c99c52d81405d64051d0946e42e398bfeaf19d0d 100644
--- a/obis/src/python/obis/dm/data_mgmt.py
+++ b/obis/src/python/obis/dm/data_mgmt.py
@@ -51,6 +51,8 @@ def DataMgmt(echo_func=None, settings_resolver=None, openbis_config={}, git_conf
     complete_git_config(git_config)
     git_wrapper = GitWrapper(**git_config)
     if not git_wrapper.can_run():
+        # TODO We could just as well throw an error here instead of creating
+        #      creating the NoGitDataMgmt which will fail later.
         return NoGitDataMgmt(settings_resolver, None, git_wrapper, openbis, log, data_path, metadata_path, invocation_path)
 
     if settings_resolver is None:
@@ -239,6 +241,7 @@ def restore_signal_handler(data_mgmt):
 
 
 def with_log(f):
+    """ To be used with commands that use the CommandLog. """
     def f_with_log(self, *args):
         try:
             result = f(self, *args)
@@ -254,6 +257,7 @@ def with_log(f):
 
 
 def with_restore(f):
+    """ Sets the restore point and restores on error. """
     def f_with_restore(self, *args):
         self.set_restorepoint()
         try:
@@ -360,6 +364,7 @@ class GitDataMgmt(AbstractDataMgmt):
 
     @with_restore
     def commit(self, msg, auto_add=True, sync=True):
+        """ Git add, commit and sync with openBIS. """
         if auto_add:
             result = self.git_wrapper.git_top_level_path()
             if result.failure():
@@ -390,16 +395,19 @@ class GitDataMgmt(AbstractDataMgmt):
         return CommandResult(returncode=0, output=output)
 
     def set_restorepoint(self):
+        """ Stores the git commit hash and copies the obis metadata. """
         self.previous_git_commit_hash = self.git_wrapper.git_commit_hash().output
         self.clear_restorepoint()
         shutil.copytree('.obis', '.obis_restorepoint')
 
     def restore(self):
+        """ Resets to the stored git commit hash and restores the copied obis metadata. """
         self.git_wrapper.git_reset_to(self.previous_git_commit_hash)
         shutil.rmtree('.obis')
         shutil.copytree('.obis_restorepoint', '.obis')
 
     def clear_restorepoint(self):
+        """ Deletes the obis metadata copy. This must always be done. """
         if os.path.exists('.obis_restorepoint'):
             shutil.rmtree('.obis_restorepoint')
 
@@ -429,9 +437,20 @@ class GitDataMgmt(AbstractDataMgmt):
     #
 
     def config(self, category, is_global, is_data_set_property, prop=None, value=None, set=False, get=False, clear=False):
+        """
+        :param category: config, object, collection, data_set or repository
+        :param is_global: act on global settings - local if false
+        :param is_data_set_property: true if prop / value are a data set property
+        :param prop: setting key
+        :param value: setting value
+        :param set: True for setting values
+        :param get: True for getting values
+        :param clear: True for clearing values
+        """
         resolver = self.settings_resolver.get(category)
         if resolver is None:
             raise ValueError('Invalid settings category: ' + category)
+        # we can only do one action at a time
         if set == True:
             assert get == False
             assert clear == False
diff --git a/obis/src/python/obis/dm/git.py b/obis/src/python/obis/dm/git.py
index af3bb7304a80f41703268235536be3a7a4366c2f..db568d12296def18cf5af97d41e7b484bf9dfbd5 100644
--- a/obis/src/python/obis/dm/git.py
+++ b/obis/src/python/obis/dm/git.py
@@ -7,7 +7,7 @@ from .checksum import ChecksumGeneratorCrc32, ChecksumGeneratorGitAnnex
 
 
 class GitWrapper(object):
-    """A wrapper on commands to git."""
+    """A wrapper on commands to git and git annex."""
 
     def __init__(self, git_path=None, git_annex_path=None, find_git=None, data_path=None, metadata_path=None, invocation_path=None):
         self.git_path = git_path
@@ -16,6 +16,9 @@ class GitWrapper(object):
         self.metadata_path = metadata_path
 
     def _git(self, params, strip_leading_whitespace=True, relative_repo_path=''):
+        """ all git invocations need to go through this method
+        since it sets --work-tree and '--git-dir.
+         """
         cmd = [self.git_path]
         if self.data_path is not None and self.metadata_path is not None:
             git_dir = os.path.join(self.metadata_path, relative_repo_path, '.git')
@@ -25,7 +28,7 @@ class GitWrapper(object):
 
 
     def can_run(self):
-        """Return true if the perquisites are satisfied to run"""
+        """Return true if the perquisites are satisfied to run (git and git annex)"""
         if self.git_path is None:
             return False
         if self.git_annex_path is None:
@@ -51,6 +54,10 @@ class GitWrapper(object):
             return self._git(["annex", "status", path], strip_leading_whitespace=False)
 
     def git_annex_init(self, desc, git_annex_backend=None):
+        """ Configures annex in a git repository."""
+
+        # We use annex --version=5 since that works better with big files. Version 
+        # 6 can lead to out of memory errors.
         cmd = ["annex", "init", "--version=5"]
         if desc is not None:
             cmd.append(desc)
@@ -77,6 +84,7 @@ class GitWrapper(object):
         if result.failure():
             return result
 
+        # copy out annex config and change the annex backend according to obis config
         attributes_src = os.path.join(os.path.dirname(__file__), "git-annex-attributes")
         attributes_dst = '.git/info/attributes'
         shutil.copyfile(attributes_src, attributes_dst)
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/dataset/AddContentCopiesToLinkedDataExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/dataset/AddContentCopiesToLinkedDataExecutor.java
index 54ad01be347bb0b7c1175187902d9b4ea9448ec3..0c5e013ab92000ecff716ccf141d7c9e44e02160 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/dataset/AddContentCopiesToLinkedDataExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/dataset/AddContentCopiesToLinkedDataExecutor.java
@@ -124,7 +124,8 @@ public class AddContentCopiesToLinkedDataExecutor implements IAddContentCopiesTo
         {
             return LocationType.FILE_SYSTEM_GIT;
         }
-        throw new UserFailureException("Invalid arguments: external data management system type " + edms.getAddressType()
-                + ", externalId " + externalId + " , path " + path + ", gitCommitHash " + gitCommitHash);
+        throw new UserFailureException("Invalid arguments: external data management system type: " + edms.getAddressType()
+                + ", externalId: " + externalId + " , path: " + path + ", gitCommitHash: " + gitCommitHash 
+                + ", gitRepositoryId: " + gitRepositoryId);
     }
 }
diff --git a/openbis_oai_pmh/sourceTest/java/ch/ethz/sis/openbis/oai_pmh/systemtests/OAIPMHSystemTest.java b/openbis_oai_pmh/sourceTest/java/ch/ethz/sis/openbis/oai_pmh/systemtests/OAIPMHSystemTest.java
index 5240beae2a93a374e594678eb1d68c4d16975ebb..8b683395529dabf7ae9ac0099eaea28ff868912d 100644
--- a/openbis_oai_pmh/sourceTest/java/ch/ethz/sis/openbis/oai_pmh/systemtests/OAIPMHSystemTest.java
+++ b/openbis_oai_pmh/sourceTest/java/ch/ethz/sis/openbis/oai_pmh/systemtests/OAIPMHSystemTest.java
@@ -241,11 +241,27 @@ public abstract class OAIPMHSystemTest extends GenericSystemTest
             String columnNames = "";
             for (QueryTableColumn queryTableColumn : columns)
             {
-                columnNames += queryTableColumn.getTitle() + ",";
+                if (columnNames.length() > 0)
+                {
+                    columnNames += ",";
+                }
+                columnNames += queryTableColumn.getTitle();
             }
 
-            Assert.assertEquals(columns.size(), 1, columnNames);
-            Assert.assertEquals(columns.get(0).getTitle(), "RESULT");
+            if ("RESULT".equals(columnNames) == false)
+            {
+                StringBuilder builder = new StringBuilder();
+                for (Serializable[] row : result.getRows())
+                {
+                    for (Serializable cell : row)
+                    {
+                        builder.append(cell).append(",");
+                    }
+                    builder.append('\n');
+                }
+                Assert.fail("Wrong columns names: " + columnNames+" of table:\n" + builder);
+            }
+            Assert.assertEquals(columnNames, "RESULT");
 
             List<Serializable[]> rows = result.getRows();
             Assert.assertEquals(rows.size(), 1);
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/GenericTechnology.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/GenericTechnology.js
index c584143546b787412059d6b628b6684c451b5a51..0040c1e8a14a38ad46b3cd617237cc4fa6e91c0b 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/GenericTechnology.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/GenericTechnology.js
@@ -47,7 +47,7 @@ $.extend(GenericTechnology.prototype, ELNLIMSPlugin.prototype, {
 			"SAMPLE_PARENTS_HINT" : [{
 				"LABEL" : "Requests",
 				"TYPE": "REQUEST",
-				"MIN_COUNT" : 0,
+				"MIN_COUNT" : 1,
 				"ANNOTATION_PROPERTIES" : []
 			}]
 		},
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js
index eb8087f832730feca2c33c61f63b120830e290d2..336946ffd005d17e22f6279263c0db309e9e6ea1 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js
@@ -510,7 +510,20 @@ var FormUtil = new function() {
 		$btn.click(clickEvent);
 		return $btn;
 	}
-	
+
+	this.getButtonGroup = function(buttons, size) {
+		var styleClass = "btn-group" + (size ? "-" + size : "");
+		var $buttonGroup = $("<div>", {
+			"class": styleClass,
+			"role": "group",
+		});
+		for (var i=0; i<buttons.length; i++) {
+			$buttonGroup.append(buttons[i]);
+		}
+		$buttonGroup.css({ "margin": "3px" });
+		return $buttonGroup;
+	}
+
 	/**
 	 * @param {string} settingLoadedCallback Can be used to avoid flickering. Only called if dontRestoreState is not true.
 	 * @param {string} dontRestoreState Sets the state to collaped and doesn't load it from server.
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormView.js
index e71052d225bb6d9115da60ad9a4b3acc850b472d..6c8940f9cc0584aa40c8b615f26c25aa7ab225b2 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormView.js
@@ -78,17 +78,30 @@ function ExperimentFormView(experimentFormController, experimentFormModel) {
 		var toolbarModel = [];
 		if(this._experimentFormModel.mode === FormMode.VIEW) {
 			//Create Experiment Step
-			if(profile.getSampleTypeForSampleTypeCode("EXPERIMENTAL_STEP")) {
+			var mandatorySampleTypeCode = null;
+			var mandatorySampleType = null;
+			
+			if(this._experimentFormModel.experiment && 
+					this._experimentFormModel.experiment.properties &&
+					this._experimentFormModel.experiment.properties["$DEFAULT_OBJECT_TYPE"]) {
+				mandatorySampleTypeCode = this._experimentFormModel.experiment.properties["$DEFAULT_OBJECT_TYPE"];
+			} else if(profile.getSampleTypeForSampleTypeCode("EXPERIMENTAL_STEP")) {
+				mandatorySampleTypeCode = "EXPERIMENTAL_STEP";
+			}
+			
+			mandatorySampleType = profile.getSampleTypeForSampleTypeCode(mandatorySampleTypeCode);
+			
+			if(mandatorySampleType) {
 				var $createBtn = FormUtil.getButtonWithIcon("glyphicon-plus", function() {
 					var argsMap = {
-							"sampleTypeCode" : "EXPERIMENTAL_STEP",
+							"sampleTypeCode" : mandatorySampleTypeCode,
 							"experimentIdentifier" : _this._experimentFormModel.experiment.identifier
 					}
 					var argsMapStr = JSON.stringify(argsMap);
 					Util.unblockUI();
 					mainController.changeView("showCreateSubExperimentPage", argsMapStr);
 				});
-				toolbarModel.push({ component : $createBtn, tooltip: "Create Experimental Step" });
+				toolbarModel.push({ component : $createBtn, tooltip: "Create " + Util.getDisplayNameFromCode(mandatorySampleTypeCode) });
 			}
 			
 			//Edit
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormController.js
index 0807d14d68b4e1acb29a10c7b9845d006689fb41..ce8566caaa2d5380113c6732b914f08737d66123 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormController.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormController.js
@@ -184,17 +184,23 @@ function SampleFormController(mainController, mode, sample, paginationInfo) {
 			//
 			if(sample.sampleTypeCode === "REQUEST") {
 				var maxProducts;
+				var minProducts;
 				if(profile.sampleTypeDefinitionsExtension && 
 					profile.sampleTypeDefinitionsExtension["REQUEST"] && 
 					profile.sampleTypeDefinitionsExtension["REQUEST"]["SAMPLE_PARENTS_HINT"] && 
 					profile.sampleTypeDefinitionsExtension["REQUEST"]["SAMPLE_PARENTS_HINT"][0]) {
 					maxProducts = profile.sampleTypeDefinitionsExtension["REQUEST"]["SAMPLE_PARENTS_HINT"][0]["MAX_COUNT"];
+					minProducts = profile.sampleTypeDefinitionsExtension["REQUEST"]["SAMPLE_PARENTS_HINT"][0]["MIN_COUNT"];
 				}
 				
 				if(maxProducts && (sampleParentsFinal.length + newSampleParents.length) > maxProducts) {
 					Util.showUserError("There is more than " + maxProducts + " product.");
 					return;
 				}
+				if(minProducts && (sampleParentsFinal.length + newSampleParents.length) < minProducts) {
+					Util.showUserError("There is less than " + maxProducts + " product.");
+					return;
+				}
 			}
 			
 			//
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetController.js
index 996ead115b55e4f98138d2660f09143f0caeaf91..382f9c40ccf7abb43cb2c94913532ad402106222 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetController.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetController.js
@@ -26,6 +26,9 @@ function SideMenuWidgetController(mainController) {
     this._sideMenuWidgetModel = new SideMenuWidgetModel();
     this._sideMenuWidgetView = new SideMenuWidgetView(this, this._sideMenuWidgetModel);
     
+    this._SORT_FIELD_KEY = "side-menu-sort-field";
+
+
     //
     // External API for real time updates
     //
@@ -69,24 +72,30 @@ function SideMenuWidgetController(mainController) {
     // Init method that builds the menu object hierarchy
     //
     this.init = function($container, initCallback) {
-    	this._sideMenuWidgetModel.$container = $container;
         var _this = this;
-        
-        _this._sideMenuWidgetView.repaint($container);
-		
-        var resize = function(event) {
-        	var $elementHead = $("#sideMenuHeader");
-            var sideMenuHeaderHeight = $elementHead.outerHeight();
-            var $elementBody = $("#sideMenuBody");
-            var height = $( window ).height();
-            $elementBody.css('height', height - sideMenuHeaderHeight);
-        }
-        
-        LayoutManager.addResizeEventHandler(resize);
-        
-        initCallback();
+
+        this._mainController.serverFacade.getSetting(this._SORT_FIELD_KEY, function(sortField) {
+            _this._sideMenuWidgetModel.sortField = sortField;
+            _this._sideMenuWidgetModel.$container = $container;
+            
+            _this._sideMenuWidgetView.repaint($container);
+            
+            LayoutManager.addResizeEventHandler(_this.resize);
+            
+            initCallback();    
+        });
     }
-    
+
+    this.resize = function() {
+        var $elementHead = $("#sideMenuHeader");
+        var sideMenuHeaderHeight = $elementHead.outerHeight();
+        var $elementSortField = $("#sideMenuSortBar");
+        var sideMenuSortFieldHeight = $elementSortField.outerHeight();
+        var $elementBody = $("#sideMenuBody");
+        var height = $( window ).height();
+        $elementBody.css('height', height - sideMenuHeaderHeight - sideMenuSortFieldHeight);
+    }
+
     this._showNodeView = function(node) {
 		if(node.data.view) {
 			var viewData =  node.data.viewData;
@@ -110,6 +119,16 @@ function SideMenuWidgetController(mainController) {
         	node.setExpanded(true);
     	}
     }
+
+    //
+    // service calls
+    //
+
+    this.setSortField = function(sortField) {
+        this._sideMenuWidgetModel.sortField = sortField;
+        this._mainController.serverFacade.setSetting(this._SORT_FIELD_KEY, sortField);
+    }
+
 }
 
 function SideMenuWidgetComponent(isSelectable, isTitle, displayName, uniqueId, parent, newMenuIfSelected, newViewIfSelected, newViewIfSelectedData, contextTitle) {
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetModel.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetModel.js
index 9c74bbed6576f0578f204fd543b49a074e9503b2..ad3fa4b22405a6be3ce0a7dc2a3ec7713ef8358a 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetModel.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetModel.js
@@ -26,4 +26,5 @@ function SideMenuWidgetModel() {
     this.$container = null;
     this.tree = null;
     this.selectedNodeData = null;
+    this.sortField = null;
 }
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js
index 1c90942c29b9aea2490b0859f9f73ecf19a3984f..13388da898cf83989797d3e6403ebcc453cb8d93 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js
@@ -27,17 +27,18 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
     var toggleMenuSizeBig = false;
     var DISPLAY_NAME_LENGTH_SHORT = 15;
     var DISPLAY_NAME_LENGTH_LONG = 300;
-    var cutDisplayNameAtLength = DISPLAY_NAME_LENGTH_SHORT; // Fix for long names
+    var cutDisplayNameAtLength = DISPLAY_NAME_LENGTH_SHORT; // Fix for long names
     
     this.repaint = function($container) {
-        var _this = this;
+        var _this = this;
+        this._$container = $container;
         var $widget = $("<div>");
         //
         // Fix Header
         //
         var $header = $("<div>", {"id": "sideMenuHeader"});
-        	$header.css("background-color", "rgb(248, 248, 248)");
-        	$header.css("padding", "10px");
+            $header.css("background-color", "rgb(248, 248, 248)");
+            $header.css("padding", "10px");
         var searchDomains = profile.getSearchDomains();
 
         var searchFunction = function() {
@@ -92,12 +93,12 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
 
         var searchElement = $("<input>", {"id": "search", "type": "text", "class": "form-control search-query", "placeholder": "Global Search"});
         searchElement.keypress(function (e) {
-        	 var key = e.which;
-        	 if(key == 13)  // the enter key code
-        	  {
-        		searchFunction();
-        	    return false;  
-        	  }
+             var key = e.which;
+             if(key == 13)  // the enter key code
+              {
+                searchFunction();
+                return false;  
+              }
         });
         searchElement.css({"display" : "inline", "width" : "50%"});
         searchElement.css({"padding-top" : "2px"});
@@ -105,14 +106,14 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
         searchElement.css({"margin-right" : "2px"});
         
         var logoutButton = FormUtil.getButtonWithIcon("glyphicon-off", function() {
-        	$('body').addClass('bodyLogin');
+            $('body').addClass('bodyLogin');
             mainController.serverFacade.logout();
         });
         
         var $searchForm = $("<form>", { "onsubmit": "return false;" })
-        					.append(logoutButton)
-        					.append(searchElement)
-        					.append(dropDownSearch);
+                            .append(logoutButton)
+                            .append(searchElement)
+                            .append(dropDownSearch);
         $searchForm.css("width", "100%");
         
         $header.append($searchForm);
@@ -121,16 +122,33 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
         $body.css("overflow-y", "auto");
         
         LayoutManager.addResizeEventHandler(function() {
-        		if(LayoutManager.FOUND_SIZE === LayoutManager.MOBILE_SIZE) {
-        			$body.css("-webkit-overflow-scrolling", "auto");
-        		} else {
-        			$body.css("-webkit-overflow-scrolling", "touch");
-        		}
+                if(LayoutManager.FOUND_SIZE === LayoutManager.MOBILE_SIZE) {
+                    $body.css("-webkit-overflow-scrolling", "auto");
+                } else {
+                    $body.css("-webkit-overflow-scrolling", "touch");
+                }
         });
-        
-        $widget.append($header)
-               .append($body);
 
+        // sorting
+        var sortOptions = [
+            {
+                sortField: "displayName",
+                description: "Sort by name",
+                icon: "glyphicon-sort-by-alphabet",
+            },
+            {
+                sortField: "registrationDate",
+                description: "Sort by date",
+                icon: "glyphicon-sort-by-order",
+            },
+        ];
+        var $sortButtonGroup = this._makeSortButtonGroup(sortOptions);
+        var $sortBar = $("<div>", { "id": "sideMenuSortBar" });
+        $sortBar.append($sortButtonGroup);
+
+        $widget.append($header)
+               .append($body)
+               .append($sortBar);
         $container.empty();
         $container.append($widget);
 
@@ -140,23 +158,57 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
         this._sideMenuWidgetModel.menuDOMBody = $body;
         this.repaintTreeMenuDinamic();
     };
-    
-    this.getLinkForNode = function(displayName, menuId, view, viewData) {
-    	var href = Util.getURLFor(menuId, view, viewData);
-    	displayName = String(displayName).replace(/<(?:.|\n)*?>/gm, ''); //Clean any HTML tags
+
+    this._makeSortButtonGroup = function(sortOptions) {
+        var buttons = [];
+        for (var i=0; i<sortOptions.length; i++) {
+            var sortOption = sortOptions[i];
+            var $button = this._makeSortButton(sortOption);
+            buttons.push($button);
+        }
+        var $buttonGroup = FormUtil.getButtonGroup(buttons);
+        return $buttonGroup;
+    }
+
+    this._makeSortButton = function(sortOption) {
+        var _this = this;
+        var $button = FormUtil.getButtonWithIcon(sortOption.icon, function() {
+            _this._sideMenuWidgetController.setSortField(sortOption.sortField);
+            _this.repaint(_this._$container);
+            _this._sideMenuWidgetController.resize();
+        }, null, sortOption.description);
+        if (this._sideMenuWidgetModel.sortField == sortOption.sortField) {
+            $button.addClass("active");
+        }
+        return $button;
+    }
+
+    this.getLinkForNode = function(displayName, menuId, view, viewData) {
+        var href = Util.getURLFor(menuId, view, viewData);
+        displayName = String(displayName).replace(/<(?:.|\n)*?>/gm, ''); //Clean any HTML tags
         var $menuItemLink = $("<a>", {"href": href, "class" : "browser-compatible-javascript-link browser-compatible-javascript-link-tree" }).text(displayName);
         return $menuItemLink[0].outerHTML;
     }
-    
-    this.repaintTreeMenuDinamic = function() {
-    		var _this = this;
+
+
+    this.repaintTreeMenuDinamic = function() {
+        var _this = this;
         this._sideMenuWidgetModel.menuDOMBody.empty();
         var $tree = $("<div>", { "id" : "tree" });
-        
-        var sortByDisplayName = function(resultA, resultB) {
-    			return naturalSort(resultA.displayName, resultB.displayName);
-    		}
-    		
+
+        var sortItems = function(resultA, resultB) {
+            var sortField = _this._sideMenuWidgetModel.sortField;
+            // default to displayName
+            if (sortField == null || !resultA.hasOwnProperty(sortField) == null || !resultB.hasOwnProperty(sortField)) {
+                sortField = "displayName";
+            }
+            // descending order for registrationDate
+            if (sortField == "registrationDate") {
+                return naturalSort(resultB[sortField], resultA[sortField]);
+            }
+            return naturalSort(resultA[sortField], resultB[sortField]);
+        }
+            
         //
         // Body
         //
@@ -164,231 +216,273 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
         var treeModel = [];
         
         if(profile.mainMenu.showLabNotebook) {
-        	var labNotebookLink = _this.getLinkForNode("Lab Notebook", "LAB_NOTEBOOK", "showLabNotebookPage", null);
-        	treeModel.push({ displayName: "Lab Notebook", title : labNotebookLink, entityType: "LAB_NOTEBOOK", key : "LAB_NOTEBOOK", folder : true, lazy : true, view : "showLabNotebookPage", icon : "glyphicon glyphicon-book" });
+            var labNotebookLink = _this.getLinkForNode("Lab Notebook", "LAB_NOTEBOOK", "showLabNotebookPage", null);
+            treeModel.push({ displayName: "Lab Notebook", title : labNotebookLink, entityType: "LAB_NOTEBOOK", key : "LAB_NOTEBOOK", folder : true, lazy : true, view : "showLabNotebookPage", icon : "glyphicon glyphicon-book" });
         }
         
         if(profile.mainMenu.showInventory) {
-        	var inventoryLink = _this.getLinkForNode("Inventory", "INVENTORY", "showInventoryPage", null);
-        	treeModel.push({ displayName: "Inventory", title : inventoryLink, entityType: "INVENTORY", key : "INVENTORY", folder : true, lazy : true, view : "showInventoryPage" });
+            var inventoryLink = _this.getLinkForNode("Inventory", "INVENTORY", "showInventoryPage", null);
+            treeModel.push({ displayName: "Inventory", title : inventoryLink, entityType: "INVENTORY", key : "INVENTORY", folder : true, lazy : true, view : "showInventoryPage" });
         }
         
         if(profile.mainMenu.showStock) {
-        	var inventoryLink = _this.getLinkForNode("Stock", "STOCK", "showStockPage", null);
-        	treeModel.push({ displayName: "Stock", title : inventoryLink, entityType: "STOCK", key : "STOCK", folder : true, lazy : true, view : "showStockPage", icon: "fa fa-shopping-cart" });
+            var inventoryLink = _this.getLinkForNode("Stock", "STOCK", "showStockPage", null);
+            treeModel.push({ displayName: "Stock", title : inventoryLink, entityType: "STOCK", key : "STOCK", folder : true, lazy : true, view : "showStockPage", icon: "fa fa-shopping-cart" });
         }
         
         var treeModelUtils = [];
         
         if(profile.jupyterEndpoint) {
-        	var jupyterLink = _this.getLinkForNode("Jupyter Workspace", "JUPYTER_WORKSPACE", "showJupyterWorkspace", null);
-        	treeModelUtils.push({ title : jupyterLink, entityType: "JUPYTER_WORKSPACE", key : "JUPYTER_WORKSPACE", folder : false, lazy : false, view : "showJupyterWorkspace" });
-        	
-        	var jupyterNotebook = _this.getLinkForNode("New Jupyter Notebook", "NEW_JUPYTER_NOTEBOOK", "showNewJupyterWorkspaceCreator", null);
-        	treeModelUtils.push({ title : jupyterNotebook, entityType: "NEW_JUPYTER_NOTEBOOK", key : "NEW_JUPYTER_NOTEBOOK", folder : false, lazy : false, view : "showNewJupyterNotebookCreator" });
+            var jupyterLink = _this.getLinkForNode("Jupyter Workspace", "JUPYTER_WORKSPACE", "showJupyterWorkspace", null);
+            treeModelUtils.push({ title : jupyterLink, entityType: "JUPYTER_WORKSPACE", key : "JUPYTER_WORKSPACE", folder : false, lazy : false, view : "showJupyterWorkspace" });
+            
+            var jupyterNotebook = _this.getLinkForNode("New Jupyter Notebook", "NEW_JUPYTER_NOTEBOOK", "showNewJupyterWorkspaceCreator", null);
+            treeModelUtils.push({ title : jupyterNotebook, entityType: "NEW_JUPYTER_NOTEBOOK", key : "NEW_JUPYTER_NOTEBOOK", folder : false, lazy : false, view : "showNewJupyterNotebookCreator" });
         }
         
         if(profile.mainMenu.showUserProfile && profile.isFileAuthenticationService && profile.isFileAuthenticationUser) {
-        	var settingsLink = _this.getLinkForNode("User Profile", "USER_PROFILE", "showUserProfilePage", null);
-        	treeModelUtils.push({ title : settingsLink, entityType: "USER_PROFILE", key : "USER_PROFILE", folder : false, lazy : false, view : "showUserProfilePage", icon : "glyphicon glyphicon-user" });
+            var settingsLink = _this.getLinkForNode("User Profile", "USER_PROFILE", "showUserProfilePage", null);
+            treeModelUtils.push({ title : settingsLink, entityType: "USER_PROFILE", key : "USER_PROFILE", folder : false, lazy : false, view : "showUserProfilePage", icon : "glyphicon glyphicon-user" });
         }
 
         if(profile.mainMenu.showDrawingBoard) {
-        	var drawingBoardLink = _this.getLinkForNode("Drawing Board", "DRAWING_BOARD", "showDrawingBoard", null);
-        	treeModelUtils.push({ displayName: "Drawing Board", title : drawingBoardLink, entityType: "DRAWING_BOARD", key : "DRAWING_BOARD", folder : false, lazy : false, view : "showDrawingBoard" });
+            var drawingBoardLink = _this.getLinkForNode("Drawing Board", "DRAWING_BOARD", "showDrawingBoard", null);
+            treeModelUtils.push({ displayName: "Drawing Board", title : drawingBoardLink, entityType: "DRAWING_BOARD", key : "DRAWING_BOARD", folder : false, lazy : false, view : "showDrawingBoard" });
         }
         
         if(profile.mainMenu.showObjectBrowser) {
-        	var sampleBrowserLink = _this.getLinkForNode("" + ELNDictionary.Sample + " Browser", "SAMPLE_BROWSER", "showSamplesPage", null);
-        	treeModelUtils.push({ displayName: "" + ELNDictionary.Sample + " Browser", title : sampleBrowserLink, entityType: "SAMPLE_BROWSER", key : "SAMPLE_BROWSER", folder : false, lazy : false, view : "showSamplesPage", icon : "glyphicon glyphicon-list-alt" });
+            var sampleBrowserLink = _this.getLinkForNode("" + ELNDictionary.Sample + " Browser", "SAMPLE_BROWSER", "showSamplesPage", null);
+            treeModelUtils.push({ displayName: "" + ELNDictionary.Sample + " Browser", title : sampleBrowserLink, entityType: "SAMPLE_BROWSER", key : "SAMPLE_BROWSER", folder : false, lazy : false, view : "showSamplesPage", icon : "glyphicon glyphicon-list-alt" });
         }
         
         if(profile.mainMenu.showVocabularyViewer) {
-        	var vocabularyBrowserLink = _this.getLinkForNode("Vocabulary Browser", "VOCABULARY_BROWSER", "showVocabularyManagerPage", null);
-        	treeModelUtils.push({ displayName: "Vocabulary Browser", title : vocabularyBrowserLink, entityType: "VOCABULARY_BROWSER", key : "VOCABULARY_BROWSER", folder : false, lazy : false, view : "showVocabularyManagerPage", icon : "glyphicon glyphicon-list-alt" });
+            var vocabularyBrowserLink = _this.getLinkForNode("Vocabulary Browser", "VOCABULARY_BROWSER", "showVocabularyManagerPage", null);
+            treeModelUtils.push({ displayName: "Vocabulary Browser", title : vocabularyBrowserLink, entityType: "VOCABULARY_BROWSER", key : "VOCABULARY_BROWSER", folder : false, lazy : false, view : "showVocabularyManagerPage", icon : "glyphicon glyphicon-list-alt" });
         }
         
         if(profile.mainMenu.showAdvancedSearch) {
-        	var advancedSearchLink = _this.getLinkForNode("Advanced Search", "ADVANCED_SEARCH", "showAdvancedSearchPage", null);
-        	treeModelUtils.push({ displayName: "Advanced Search", title : advancedSearchLink, entityType: "ADVANCED_SEARCH", key : "ADVANCED_SEARCH", folder : false, lazy : false, view : "showAdvancedSearchPage", icon : "glyphicon glyphicon-search" });
+            var advancedSearchLink = _this.getLinkForNode("Advanced Search", "ADVANCED_SEARCH", "showAdvancedSearchPage", null);
+            treeModelUtils.push({ displayName: "Advanced Search", title : advancedSearchLink, entityType: "ADVANCED_SEARCH", key : "ADVANCED_SEARCH", folder : false, lazy : false, view : "showAdvancedSearchPage", icon : "glyphicon glyphicon-search" });
         }
         
         if(profile.mainMenu.showExports) {
-        	var exportBuilderLink = _this.getLinkForNode("Export Builder", "EXPORT_BUILDER", "showExportTreePage", null);
-        	treeModelUtils.push({ displayName: "Export Builder", title : exportBuilderLink, entityType: "EXPORT_BUILDER", key : "EXPORT_BUILDER", folder : false, lazy : false, view : "showExportTreePage", icon : "glyphicon glyphicon-export" });
+            var exportBuilderLink = _this.getLinkForNode("Export Builder", "EXPORT_BUILDER", "showExportTreePage", null);
+            treeModelUtils.push({ displayName: "Export Builder", title : exportBuilderLink, entityType: "EXPORT_BUILDER", key : "EXPORT_BUILDER", folder : false, lazy : false, view : "showExportTreePage", icon : "glyphicon glyphicon-export" });
         }
         
         if(profile.mainMenu.showStorageManager) {
-        	var storageManagerLink = _this.getLinkForNode("Storage Manager", "STORAGE_MANAGER", "showStorageManager", null);
-        	treeModelUtils.push({ displayName: "Storage Manager", title : storageManagerLink, entityType: "STORAGE_MANAGER", key : "STORAGE_MANAGER", folder : false, lazy : false, view : "showStorageManager" });
+            var storageManagerLink = _this.getLinkForNode("Storage Manager", "STORAGE_MANAGER", "showStorageManager", null);
+            treeModelUtils.push({ displayName: "Storage Manager", title : storageManagerLink, entityType: "STORAGE_MANAGER", key : "STORAGE_MANAGER", folder : false, lazy : false, view : "showStorageManager" });
         }
         
         if(profile.mainMenu.showUserManager && profile.isAdmin) {
-        	var userManagerLink = _this.getLinkForNode("User Manager", "USER_MANAGER", "showUserManagerPage", null);
-        	treeModelUtils.push({ displayName: "User Manager", title : userManagerLink, entityType: "USER_MANAGER", key : "USER_MANAGER", folder : false, lazy : false, view : "showUserManagerPage", icon : "fa fa-users" });
+            var userManagerLink = _this.getLinkForNode("User Manager", "USER_MANAGER", "showUserManagerPage", null);
+            treeModelUtils.push({ displayName: "User Manager", title : userManagerLink, entityType: "USER_MANAGER", key : "USER_MANAGER", folder : false, lazy : false, view : "showUserManagerPage", icon : "fa fa-users" });
         }
         
         if(profile.mainMenu.showTrashcan) {
-        	var trashCanLink = _this.getLinkForNode("Trashcan", "TRASHCAN", "showTrashcanPage", null);
-        	treeModelUtils.push({ displayName: "Trashcan", title : trashCanLink, entityType: "TRASHCAN", key : "TRASHCAN", folder : false, lazy : false, view : "showTrashcanPage", icon : "glyphicon glyphicon-trash" });
+            var trashCanLink = _this.getLinkForNode("Trashcan", "TRASHCAN", "showTrashcanPage", null);
+            treeModelUtils.push({ displayName: "Trashcan", title : trashCanLink, entityType: "TRASHCAN", key : "TRASHCAN", folder : false, lazy : false, view : "showTrashcanPage", icon : "glyphicon glyphicon-trash" });
         }
         
         if(profile.mainMenu.showSettings) {
-        	var settingsLink = _this.getLinkForNode("Settings", "SETTINGS", "showSettingsPage", null);
-        	treeModelUtils.push({ displayName: "Settings", title : settingsLink, entityType: "SETTINGS", key : "SETTINGS", folder : false, lazy : false, view : "showSettingsPage", icon : "glyphicon glyphicon-cog" });
+            var settingsLink = _this.getLinkForNode("Settings", "SETTINGS", "showSettingsPage", null);
+            treeModelUtils.push({ displayName: "Settings", title : settingsLink, entityType: "SETTINGS", key : "SETTINGS", folder : false, lazy : false, view : "showSettingsPage", icon : "glyphicon glyphicon-cog" });
         }
 
         treeModel.push({ displayName: "Utilities", title : "Utilities", entityType: "UTILITIES", key : "UTILITIES", folder : true, lazy : false, expanded : true, children : treeModelUtils, icon : "glyphicon glyphicon-wrench" });
         treeModel.push({ displayName: "About", title : "About", entityType: "ABOUT", key : "ABOUT", folder : false, lazy : false, view : "showAbout", icon : "glyphicon glyphicon-info-sign" });
         
-		var glyph_opts = {
-        	    map: {
-        	      doc: "glyphicon glyphicon-file",
-        	      docOpen: "glyphicon glyphicon-file",
-        	      checkbox: "glyphicon glyphicon-unchecked",
-        	      checkboxSelected: "glyphicon glyphicon-check",
-        	      checkboxUnknown: "glyphicon glyphicon-share",
-        	      dragHelper: "glyphicon glyphicon-play",
-        	      dropMarker: "glyphicon glyphicon-arrow-right",
-        	      error: "glyphicon glyphicon-warning-sign",
-        	      expanderClosed: "glyphicon glyphicon-plus-sign",
-        	      expanderLazy: "glyphicon glyphicon-plus-sign",  // glyphicon-expand
-        	      expanderOpen: "glyphicon glyphicon-minus-sign",  // glyphicon-collapse-down
-        	      folder: "glyphicon glyphicon-folder-close",
-        	      folderOpen: "glyphicon glyphicon-folder-open",
-        	      loading: "glyphicon glyphicon-refresh"
-        	    }
+        var glyph_opts = {
+                map: {
+                  doc: "glyphicon glyphicon-file",
+                  docOpen: "glyphicon glyphicon-file",
+                  checkbox: "glyphicon glyphicon-unchecked",
+                  checkboxSelected: "glyphicon glyphicon-check",
+                  checkboxUnknown: "glyphicon glyphicon-share",
+                  dragHelper: "glyphicon glyphicon-play",
+                  dropMarker: "glyphicon glyphicon-arrow-right",
+                  error: "glyphicon glyphicon-warning-sign",
+                  expanderClosed: "glyphicon glyphicon-plus-sign",
+                  expanderLazy: "glyphicon glyphicon-plus-sign",  // glyphicon-expand
+                  expanderOpen: "glyphicon glyphicon-minus-sign",  // glyphicon-collapse-down
+                  folder: "glyphicon glyphicon-folder-close",
+                  folderOpen: "glyphicon glyphicon-folder-open",
+                  loading: "glyphicon glyphicon-refresh"
+                }
         };
-    	
-    	var onLazyLoad = function(event, data) {
-    		var dfd = new $.Deferred();
-    	    data.result = dfd.promise();
-    	    var type = data.node.data.entityType;
-    	    var permId = data.node.key;
-    	    
-    	    var showLabNotebooks = function(dfd, showEnabled, showDisabled) {
-    	    		var spaceRules = { entityKind : "SPACE", logicalOperator : "AND", rules : { } };
-    	    		profile.getHomeSpace(function(HOME_SPACE) {
-    	    			mainController.serverFacade.searchForSpacesAdvanced(spaceRules, null, function(searchResult) {
-    	    			var results = [];
-    	    			var spaces = searchResult.objects;
-    	            var nonInventoryNonHiddenSpaces = []; 
-    	                for (var i = 0; i < spaces.length; i++) {
-    	                    var space = spaces[i];
-    	                    var isInventorySpace = profile.isInventorySpace(space.code);
-    	                    var isHiddenSpace = profile.isHiddenSpace(space.code);
-        	                if(!isInventorySpace && (space.code !== HOME_SPACE) && !isHiddenSpace) {
-        	                		nonInventoryNonHiddenSpaces.push(space.code);
-        	                }
-    	                }
-    	                
-    	                mainController.serverFacade.customELNASAPI({
-    	                		"method" : "doSpacesBelongToDisabledUsers",
-    	                		"spaceCodes" : nonInventoryNonHiddenSpaces
-    	                }, function(disabledSpaces) {
-    	                		for(var i = 0; i < nonInventoryNonHiddenSpaces.length; i++) {
-    	                			var spaceCode = nonInventoryNonHiddenSpaces[i];
-    	                			var foundDisabled = $.inArray(spaceCode, disabledSpaces) !== -1;
-    	                			
-    	                			if(!showDisabled && foundDisabled) {
-    	                				continue; //Skip disabled spaces
-    	                			}
-    	                			
-    	                			if(!showEnabled && !foundDisabled) {
-    	                				continue; //Skip enabled spaces
-    	                			}
-    	                			
-    	                			var normalizedSpaceTitle = Util.getDisplayNameFromCode(spaceCode);
-        	                		var spaceLink = _this.getLinkForNode(normalizedSpaceTitle, spaceCode, "showSpacePage", spaceCode);
-        	              		var spaceNode = { displayName: normalizedSpaceTitle, title : spaceLink, entityType: "SPACE", key : spaceCode, folder : true, lazy : true, view : "showSpacePage", viewData: spaceCode };
-        	               		results.push(spaceNode);
-    	                		}
-    	                		results.sort(sortByDisplayName);
-    	                		dfd.resolve(results);
-    	                });
-    	    			});
-    	    		});
-    	    }
-    	    
-    	    switch(type) {
-    	    	case "LAB_NOTEBOOK":
-    	    		var spaceRules = { entityKind : "SPACE", logicalOperator : "AND", rules : { } };
-    	    		profile.getHomeSpace(function(HOME_SPACE) {
-    	    			mainController.serverFacade.searchForSpacesAdvanced(spaceRules, null, function(searchResult) {
-    	    			var results = [];
-    	    			var spaces = searchResult.objects;
-    	                for (var i = 0; i < spaces.length; i++) {
-    	                    var space = spaces[i];
-    	                    var isInventorySpace = profile.isInventorySpace(space.code);
-    	                    var isHiddenSpace = profile.isHiddenSpace(space.code);
-        	                if(!isInventorySpace && (space.code === HOME_SPACE) && !isHiddenSpace) {
-        	                	var normalizedSpaceTitle = Util.getDisplayNameFromCode(space.code);
-        	                	var spaceLink = _this.getLinkForNode("My Space (" + normalizedSpaceTitle + ")", space.getCode(), "showSpacePage", space.getCode());
-        	                    var spaceNode = { displayName: "My Space (" + normalizedSpaceTitle + ")", title : spaceLink, entityType: "SPACE", key : space.getCode(), folder : true, lazy : true, view : "showSpacePage", viewData: space.getCode() };
-        	                    results.push(spaceNode);
-        	                }
-    	                }
-    	                
-	               	results.push({ displayName: "Others", title : "Others", entityType: "LAB_NOTEBOOK_OTHERS", key : "LAB_NOTEBOOK_OTHERS", folder : true, lazy : true, view : "showLabNotebookPage" });
-	               	results.push({ displayName: "Others (disabled)", title : "Others (disabled)", entityType: "LAB_NOTEBOOK_OTHERS_DISABLED", key : "LAB_NOTEBOOK_OTHERS_DISABLED", folder : true, lazy : true, view : "showLabNotebookPage" });
-    	                results.sort(sortByDisplayName);
-    	                dfd.resolve(results);
-    	    			});
-    	    		});
-    	    		break;
-    	    	case "LAB_NOTEBOOK_OTHERS":
-    	        showLabNotebooks(dfd, true, false);
-    	    		break;
-    	    	case "LAB_NOTEBOOK_OTHERS_DISABLED":
-    	        showLabNotebooks(dfd, false, true);
-    	    		break;
-    	    	case "INVENTORY":
-    	    		var spaceRules = { entityKind : "SPACE", logicalOperator : "AND", rules : { } };
-    	    		mainController.serverFacade.searchForSpacesAdvanced(spaceRules, null, function(searchResult) {
-    	    			var results = [];
-    	                var spaces = searchResult.objects;
-    	                for (var i = 0; i < spaces.length; i++) {
-    	                    var space = spaces[i];
-    	                    var isInventorySpace = profile.isInventorySpace(space.code);
-    	                    var isHiddenSpace = profile.isHiddenSpace(space.code);
-        	                if(((type === "LAB_NOTEBOOK" && !isInventorySpace) || (type === "INVENTORY" && isInventorySpace)) && !isHiddenSpace) {
-        	                	var normalizedSpaceTitle = Util.getDisplayNameFromCode(space.code);
-        	                	
-        	                	var spaceLink = _this.getLinkForNode(normalizedSpaceTitle, space.getCode(), "showSpacePage", space.getCode());
-        	                    var spaceNode = { displayName: normalizedSpaceTitle, title : spaceLink, entityType: "SPACE", key : space.getCode(), folder : true, lazy : true, view : "showSpacePage", viewData: space.getCode() };
-        	                    if(!space.getCode().endsWith("STOCK_CATALOG") && !space.getCode().endsWith("STOCK_ORDERS")) {
-        	                    		results.push(spaceNode);
-        	                    }
-        	                }
-    	                }
-    	                results.sort(sortByDisplayName);
-    	                dfd.resolve(results);
-    	    		});
-    	    		break;
-    	    	case "STOCK":
-    	    		var spaceRules = { entityKind : "SPACE", logicalOperator : "AND", rules : { } };
-    	    		mainController.serverFacade.searchForSpacesAdvanced(spaceRules, null, function(searchResult) {
-    	    			var results = [];
-    	                var spaces = searchResult.objects;
-    	                for (var i = 0; i < spaces.length; i++) {
-    	                    var space = spaces[i];
-    	                    if(space.getCode().endsWith("STOCK_CATALOG") || space.getCode().endsWith("STOCK_ORDERS")) {
-    	                    	var normalizedSpaceTitle = Util.getDisplayNameFromCode(space.code);
-        	                	var spaceLink = _this.getLinkForNode(normalizedSpaceTitle, space.getCode(), "showSpacePage", space.getCode());
-        	                    var spaceNode = { displayName: normalizedSpaceTitle, title : spaceLink, entityType: "SPACE", key : space.getCode(), folder : true, lazy : true, view : "showSpacePage", viewData: space.getCode() };
-        	                    spaceNode.icon = "fa fa-shopping-cart";
-        	                    results.push(spaceNode);
-    	                    }
-    	                }
-    	                results.sort(sortByDisplayName);
-    	                dfd.resolve(results);
-    	    		});
-    	    		break;
-    	    	case "SPACE":
-    	    		var projectRules = { "UUIDv4" : { type : "Attribute", name : "SPACE", value : permId } };
-    	    		mainController.serverFacade.searchForProjectsAdvanced({ entityKind : "PROJECT", logicalOperator : "AND", rules : projectRules }, null, function(searchResult) {
-    	    			var results = [];
-    	                var projects = searchResult.objects;
+        
+        var onLazyLoad = function(event, data) {
+            var dfd = new $.Deferred();
+            data.result = dfd.promise();
+            var type = data.node.data.entityType;
+            var permId = data.node.key;
+            
+            var showLabNotebooks = function(dfd, showEnabled, showDisabled) {
+                    var spaceRules = { entityKind : "SPACE", logicalOperator : "AND", rules : { } };
+                    profile.getHomeSpace(function(HOME_SPACE) {
+                        mainController.serverFacade.searchForSpacesAdvanced(spaceRules, null, function(searchResult) {
+                        var results = [];
+                        var spaces = searchResult.objects;
+                    var nonInventoryNonHiddenSpaces = [];
+                    var spacesByCode = {}; 
+                        for (var i = 0; i < spaces.length; i++) {
+                            var space = spaces[i];
+                            var isInventorySpace = profile.isInventorySpace(space.code);
+                            var isHiddenSpace = profile.isHiddenSpace(space.code);
+                            if(!isInventorySpace && (space.code !== HOME_SPACE) && !isHiddenSpace) {
+                                    nonInventoryNonHiddenSpaces.push(space.code);
+                                    spacesByCode[space.code] = space;
+                            }
+                        }
+                        
+                        mainController.serverFacade.customELNASAPI({
+                                "method" : "doSpacesBelongToDisabledUsers",
+                                "spaceCodes" : nonInventoryNonHiddenSpaces
+                        }, function(disabledSpaces) {
+                                for(var i = 0; i < nonInventoryNonHiddenSpaces.length; i++) {
+                                    var spaceCode = nonInventoryNonHiddenSpaces[i];
+                                    var foundDisabled = $.inArray(spaceCode, disabledSpaces) !== -1;
+                                    
+                                    if(!showDisabled && foundDisabled) {
+                                        continue; //Skip disabled spaces
+                                    }
+                                    
+                                    if(!showEnabled && !foundDisabled) {
+                                        continue; //Skip enabled spaces
+                                    }
+                                    
+                                    var normalizedSpaceTitle = Util.getDisplayNameFromCode(spaceCode);
+                                    var spaceLink = _this.getLinkForNode(normalizedSpaceTitle, spaceCode, "showSpacePage", spaceCode);
+                                    var spaceNode = {
+                                        displayName: normalizedSpaceTitle,
+                                        title : spaceLink,
+                                        entityType: "SPACE",
+                                        key : spaceCode,
+                                        folder : true,
+                                        lazy : true,
+                                        view : "showSpacePage",
+                                        viewData: spaceCode,
+                                        registrationDate: spacesByCode[spaceCode].registrationDate,
+                                    };
+                                    results.push(spaceNode);
+                                }
+                                results.sort(sortItems);
+                                dfd.resolve(results);
+                        });
+                        });
+                    });
+            }
+            
+            switch(type) {
+                case "LAB_NOTEBOOK":
+                    var spaceRules = { entityKind : "SPACE", logicalOperator : "AND", rules : { } };
+                    profile.getHomeSpace(function(HOME_SPACE) {
+                        mainController.serverFacade.searchForSpacesAdvanced(spaceRules, null, function(searchResult) {
+                        var results = [];
+                        var spaces = searchResult.objects;
+                        for (var i = 0; i < spaces.length; i++) {
+                            var space = spaces[i];
+                            var isInventorySpace = profile.isInventorySpace(space.code);
+                            var isHiddenSpace = profile.isHiddenSpace(space.code);
+                            if(!isInventorySpace && (space.code === HOME_SPACE) && !isHiddenSpace) {
+                                var normalizedSpaceTitle = Util.getDisplayNameFromCode(space.code);
+                                var spaceLink = _this.getLinkForNode("My Space (" + normalizedSpaceTitle + ")", space.getCode(), "showSpacePage", space.getCode());
+                                var spaceNode = {
+                                    displayName: "My Space (" + normalizedSpaceTitle + ")",
+                                    title : spaceLink,
+                                    entityType: "SPACE",
+                                    key : space.getCode(),
+                                    folder : true,
+                                    lazy : true,
+                                    view : "showSpacePage",
+                                    viewData: space.getCode(),
+                                    registrationDate: space.registrationDate,
+                                };
+                                results.push(spaceNode);
+                            }
+                        }
+                        
+                    results.push({ displayName: "Others", title : "Others", entityType: "LAB_NOTEBOOK_OTHERS", key : "LAB_NOTEBOOK_OTHERS", folder : true, lazy : true, view : "showLabNotebookPage" });
+                    results.push({ displayName: "Others (disabled)", title : "Others (disabled)", entityType: "LAB_NOTEBOOK_OTHERS_DISABLED", key : "LAB_NOTEBOOK_OTHERS_DISABLED", folder : true, lazy : true, view : "showLabNotebookPage" });
+                        results.sort(sortItems);
+                        dfd.resolve(results);
+                        });
+                    });
+                    break;
+                case "LAB_NOTEBOOK_OTHERS":
+                showLabNotebooks(dfd, true, false);
+                    break;
+                case "LAB_NOTEBOOK_OTHERS_DISABLED":
+                showLabNotebooks(dfd, false, true);
+                    break;
+                case "INVENTORY":
+                    var spaceRules = { entityKind : "SPACE", logicalOperator : "AND", rules : { } };
+                    mainController.serverFacade.searchForSpacesAdvanced(spaceRules, null, function(searchResult) {
+                        var results = [];
+                        var spaces = searchResult.objects;
+                        for (var i = 0; i < spaces.length; i++) {
+                            var space = spaces[i];
+                            var isInventorySpace = profile.isInventorySpace(space.code);
+                            var isHiddenSpace = profile.isHiddenSpace(space.code);
+                            if(((type === "LAB_NOTEBOOK" && !isInventorySpace) || (type === "INVENTORY" && isInventorySpace)) && !isHiddenSpace) {
+                                var normalizedSpaceTitle = Util.getDisplayNameFromCode(space.code);
+                                
+                                var spaceLink = _this.getLinkForNode(normalizedSpaceTitle, space.getCode(), "showSpacePage", space.getCode());
+                                var spaceNode = {
+                                    displayName: normalizedSpaceTitle,
+                                    title : spaceLink,
+                                    entityType: "SPACE",
+                                    key : space.getCode(),
+                                    folder : true,
+                                    lazy : true,
+                                    view : "showSpacePage",
+                                    viewData: space.getCode(),
+                                    registrationDate: space.registrationDate,
+                                };
+                                if(!space.getCode().endsWith("STOCK_CATALOG") && !space.getCode().endsWith("STOCK_ORDERS")) {
+                                        results.push(spaceNode);
+                                }
+                            }
+                        }
+                        results.sort(sortItems);
+                        dfd.resolve(results);
+                    });
+                    break;
+                case "STOCK":
+                    var spaceRules = { entityKind : "SPACE", logicalOperator : "AND", rules : { } };
+                    mainController.serverFacade.searchForSpacesAdvanced(spaceRules, null, function(searchResult) {
+                        var results = [];
+                        var spaces = searchResult.objects;
+                        for (var i = 0; i < spaces.length; i++) {
+                            var space = spaces[i];
+                            if(space.getCode().endsWith("STOCK_CATALOG") || space.getCode().endsWith("STOCK_ORDERS")) {
+                                var normalizedSpaceTitle = Util.getDisplayNameFromCode(space.code);
+                                var spaceLink = _this.getLinkForNode(normalizedSpaceTitle, space.getCode(), "showSpacePage", space.getCode());
+                                var spaceNode = {
+                                    displayName: normalizedSpaceTitle,
+                                    title : spaceLink,
+                                    entityType: "SPACE",
+                                    key : space.getCode(),
+                                    folder : true,
+                                    lazy : true,
+                                    view : "showSpacePage",
+                                    viewData: space.getCode(),
+                                    registrationDate: space.registrationDate,
+                                };
+                                spaceNode.icon = "fa fa-shopping-cart";
+                                results.push(spaceNode);
+                            }
+                        }
+                        results.sort(sortItems);
+                        dfd.resolve(results);
+                    });
+                    break;
+                case "SPACE":
+                    var projectRules = { "UUIDv4" : { type : "Attribute", name : "SPACE", value : permId } };
+                    mainController.serverFacade.searchForProjectsAdvanced({ entityKind : "PROJECT", logicalOperator : "AND", rules : projectRules }, null, function(searchResult) {
+                        var results = [];
+                        var projects = searchResult.objects;
                       for (var i = 0; i < projects.length; i++) {
                           var project = projects[i];
                           if (project.code == 'QUERIES' && 
@@ -397,258 +491,323 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
                           }
                           var normalizedProjectTitle = Util.getDisplayNameFromCode(project.code);
                           var projectLink = _this.getLinkForNode(normalizedProjectTitle, project.getPermId().getPermId(), "showProjectPageFromPermId", project.getPermId().getPermId());
-                          results.push({ displayName: normalizedProjectTitle, title : projectLink, entityType: "PROJECT", key : project.getPermId().getPermId(), folder : true, lazy : true, view : "showProjectPageFromPermId", viewData: project.getPermId().getPermId() });
+                          results.push({
+                              displayName: normalizedProjectTitle,
+                              title : projectLink,
+                              entityType: "PROJECT",
+                              key : project.getPermId().getPermId(),
+                              folder : true,
+                              lazy : true,
+                              view : "showProjectPageFromPermId",
+                              viewData: project.getPermId().getPermId(),
+                              registrationDate: project.registrationDate,
+                            });
                           
                       }
-                      results.sort(sortByDisplayName);
+                      results.sort(sortItems);
                       dfd.resolve(results);
-    	    		});
-    	    		break;
-    	    	case "PROJECT":
-    	    		var experimentRules = { "UUIDv4" : { type : "Attribute", name : "PROJECT_PERM_ID", value : permId } };
-    	    		mainController.serverFacade.searchForExperimentsAdvanced({ entityKind : "EXPERIMENT", logicalOperator : "AND", rules : experimentRules }, null, function(searchResult) {
-    	    			var results = [];
-    	                var experiments = searchResult.objects;
-    	                for (var i = 0; i < experiments.length; i++) {
-    	                    var experiment = experiments[i];
-    	                    var experimentDisplayName = experiment.code;
-    	                    if(experiment.properties && experiment.properties[profile.propertyReplacingCode]) {
-    	                    	experimentDisplayName = experiment.properties[profile.propertyReplacingCode];
-    	                    }
-    	                    var isInventorySpace = profile.isInventorySpace(IdentifierUtil.getSpaceCodeFromIdentifier(experiment.getIdentifier().getIdentifier()));
-    	                    var viewToUse = null;
-    	                    var loadSamples = null;
-    	                    if(isInventorySpace) {
-    	                    	viewToUse = "showSamplesPage";
-    	                    	loadSamples = false;
-    	                    } else {
-    	                    	viewToUse = "showExperimentPageFromIdentifier";
-    	                    	loadSamples = true;
-    	                    }
-    	                    
-    	                    var experimentLink = _this.getLinkForNode(experimentDisplayName, experiment.getPermId().getPermId(), viewToUse, experiment.getIdentifier().getIdentifier());
-    	                    results.push({ displayName: experimentDisplayName, title : experimentLink, entityType: "EXPERIMENT", key : experiment.getPermId().getPermId(), folder : true, lazy : loadSamples, view : viewToUse, viewData: experiment.getIdentifier().getIdentifier() });
-    	                }
-    	                results.sort(sortByDisplayName);
-    	                dfd.resolve(results);
-    	    		});
-    	    		break;
-    	    	case "EXPERIMENT":
-    	    		var sampleRules = { "UUIDv4" : { type : "Experiment", name : "ATTR.PERM_ID", value : permId } };
-    	    		mainController.serverFacade.searchForSamplesAdvanced({ entityKind : "SAMPLE", logicalOperator : "AND", rules : sampleRules }, 
-    	    		{ only : true, withProperties : true, withType : true, withExperiment : true, withParents : true, withChildren : true, withParentsType : true, withChildrenType : true},
-    	    		function(searchResult) {
-    	    			var samples = searchResult.objects;
-    	    			var samplesToShow = [];
-    	    			for(var sIdx = 0; sIdx < samples.length; sIdx++) {
-    	    				var sample = samples[sIdx];
-    	    				var sampleIsExperiment = sample.type.code.indexOf("EXPERIMENT") > -1;
-    	    				
-    	    				if(sampleIsExperiment) {
-    	    					var parentIsExperiment = false;
-    	    					if(sample.parents) {
-    	    						for(var pIdx = 0; pIdx < sample.parents.length; pIdx++) {
-        	    						var parentIdentifier = sample.parents[pIdx].identifier.identifier;
-        	    						var parentInELN = profile.isELNIdentifier(parentIdentifier);
-        	    						if(parentInELN) {
-        	    							parentIsExperiment = parentIsExperiment || sample.parents[pIdx].type.code.indexOf("EXPERIMENT") > -1;
-        	    						}
-        	    					}
-    	    					}
-    	    					if(!parentIsExperiment) {
-    	    						samplesToShow.push(sample);
-	    					}
-    	    				}
-    	    			}
-    	    			
-    	    			var getOkResultsFunction = function(dfd, samples) {
-    	                	return function() {
-    	                		var results = [];
-        	                	for (var i = 0; i < samples.length; i++) {
-	        	                    var sample = samples[i];
-	        	                    var sampleDisplayName = sample.code;
-	        	                    if(sample.properties && sample.properties[profile.propertyReplacingCode]) {
-	        	                    		sampleDisplayName = sample.properties[profile.propertyReplacingCode];
-	        	                    }
-	        	                    
-	        	                    var sampleLink = _this.getLinkForNode(sampleDisplayName, sample.getPermId().getPermId(), "showViewSamplePageFromPermId", sample.getPermId().getPermId());
-	        	                    var sampleNode = { displayName: sampleDisplayName, title : sampleLink, entityType: "SAMPLE", key : sample.getPermId().getPermId(), folder : true, lazy : true, view : "showViewSamplePageFromPermId", viewData: sample.getPermId().getPermId(), icon : "fa fa-flask" };
-	        	                    results.push(sampleNode);
-        	                	}
-        	                	
-        	                	var datasetRules = { "UUIDv4" : { type : "Experiment", name : "ATTR.PERM_ID", value : permId } };
-                	    		mainController.serverFacade.searchForDataSetsAdvanced({ entityKind : "DATASET", logicalOperator : "AND", rules : datasetRules }, null, function(searchResult) {
-                	    			
-                	                var datasets = searchResult.objects;
-                	                var experimentDatasets = [];
-                	                for (var i = 0; i < datasets.length; i++) {
-                	                    var dataset = datasets[i];
-                	                    if(!dataset.sample) {
-                	                    		experimentDatasets.push(dataset);
-                	                    }
-                	                }
-                	                
-                	                if(experimentDatasets.length > 50) {
-                	                		Util.showInfo("More than 50 Datasets, please use the dataset viewer on the experiment to navigate them.");
-                	                } else {
-                	                		for (var i = 0; i < experimentDatasets.length; i++) {
-                    	                    var dataset = experimentDatasets[i];
-                    	                    var datasetDisplayName = dataset.code;
-                    	                    if(dataset.properties && dataset.properties[profile.propertyReplacingCode]) {
-                    	                    		datasetDisplayName = dataset.properties[profile.propertyReplacingCode];
-                    	                    }
-                    	                    
-                    	                    var datasetLink = _this.getLinkForNode(datasetDisplayName, dataset.getPermId().getPermId(), "showViewDataSetPageFromPermId", dataset.getPermId().getPermId());
-                    	                    results.push({ displayName: datasetDisplayName, title : datasetLink, entityType: "DATASET", key : dataset.getPermId().getPermId(), folder : true, lazy : false, view : "showViewDataSetPageFromPermId", viewData: dataset.getPermId().getPermId(), icon : "fa fa-database" });
-                    	                }
-                	                }
-                	            
-                	            results.sort(sortByDisplayName);
-                	    			dfd.resolve(results);
-            	                	Util.unblockUI();
-                	    		});
-        	                	
-    	                	}
-    	                }
-    	                
-    	                var getCancelResultsFunction = function(dfd) {
-    	                		return function() {
-    	                			dfd.resolve([]);
-    	                		}
-    	                }
-    	                
-    	                if(samplesToShow.length > 50) {
-    	                		var toExecute = function() {
-	        	                	Util.blockUIConfirm("Do you want to show " + samplesWithoutELNParents.length + " " + ELNDictionary.Samples + " on the tree?", 
-	        	    	                	getOkResultsFunction(dfd, samplesToShow),
-	        	    	                	getCancelResultsFunction(dfd));
-        	                }
-    	                	
-    	                		setTimeout(toExecute, 1000);
-    	                } else {
-    	                		getOkResultsFunction(dfd, samplesToShow)();
-    	                }
-    	    		});
-    	    		break;
-    	    	case "SAMPLE":
-    	    		var sampleRules = { "UUIDv4" : { type : "Attribute", name : "PERM_ID", value : permId } };
-    	    		mainController.serverFacade.searchForSamplesAdvanced({ entityKind : "SAMPLE", logicalOperator : "AND", rules : sampleRules }, 
-    	    		{ only : true, withProperties : true, withType : true, withExperiment : true, withParents : true, withChildren : true, withChildrenProperties : true, withParentsType : true, withChildrenType : true}
-    	    		, function(searchResult) {
-    	    			var results = [];
-    	    			var samples = searchResult.objects;
-    	    			if(samples && samples[0] && samples[0].children) {
-    	    				if(samples[0].children.length > 50) {
-        	                	Util.showInfo("More than 50 Samples, please use the children table to navigate them.");
-        	                } else {
-        	                	for(var cIdx = 0; cIdx < samples[0].children.length; cIdx++) {
-        	    					var sample = samples[0].children[cIdx];
-        	    					if(sample.type.code.indexOf("EXPERIMENT") > -1) {
-        	    						var sampleDisplayName = sample.code;
-                	                if(sample.properties && sample.properties[profile.propertyReplacingCode]) {
-                	                    	sampleDisplayName = sample.properties[profile.propertyReplacingCode];
-                	                }
-            	    					var sampleLink = _this.getLinkForNode(sampleDisplayName, sample.getPermId().getPermId(), "showViewSamplePageFromPermId", sample.getPermId().getPermId());
-                	                var sampleNode = { displayName: sampleDisplayName, title : sampleLink, entityType: "SAMPLE", key : sample.getPermId().getPermId(), folder : true, lazy : true, view : "showViewSamplePageFromPermId", viewData: sample.getPermId().getPermId(), icon : "fa fa-flask" };
-                	                results.push(sampleNode);
-        	    					}
-        	    				}
-        	                }
-    	    			}
-    	    			
-    	    			var datasetRules = { "UUIDv4" : { type : "Sample", name : "ATTR.PERM_ID", value : permId } };
-        	    		mainController.serverFacade.searchForDataSetsAdvanced({ entityKind : "DATASET", logicalOperator : "AND", rules : datasetRules }, null, function(searchResult) {
-        	    			
-        	                var datasets = searchResult.objects;
-        	                if(datasets.length > 50) {
-        	                	Util.showInfo("More than 50 Datasets, please use the dataset viewer on the sample to navigate them.");
-        	                } else {
-        	                	for (var i = 0; i < datasets.length; i++) {
-            	                    var dataset = datasets[i];
-            	                    var datasetDisplayName = dataset.code;
-            	                    if(dataset.properties && dataset.properties[profile.propertyReplacingCode]) {
-            	                    	datasetDisplayName = dataset.properties[profile.propertyReplacingCode];
-            	                    }
-            	                    
-            	                    var datasetLink = _this.getLinkForNode(datasetDisplayName, dataset.getPermId().getPermId(), "showViewDataSetPageFromPermId", dataset.getPermId().getPermId());
-            	                    results.push({ displayName: datasetDisplayName, title : datasetLink, entityType: "DATASET", key : dataset.getPermId().getPermId(), folder : true, lazy : false, view : "showViewDataSetPageFromPermId", viewData: dataset.getPermId().getPermId(), icon : "fa fa-database" });
-            	                }
-        	                }
-        	                
-        	                results.sort(sortByDisplayName);
-        	                dfd.resolve(results);
-        	    		});
-    	    		});
-    	    		
-    	    		break;
-    	    	case "DATASET":
-    	    		break;
-    	    }
-    	};
-    	
-    	var onActivate = function(event, data) {
-    		data.node.setExpanded(true);
-    	};
-    	
-    	var onClick = function(event, data) {
-    		if(data.node.data.view) {
-    			_this._sideMenuWidgetController._showNodeView(data.node);
-    		}
-    	};
-    	
-    	var onCollapse = function(event, data) {
-    		if(data.node.lazy) { //Is going to be collapsed
-    			data.node.removeChildren();
-    			data.node.resetLazy();
-    		}
-    	};
-    	
-    	$tree.fancytree({
-        	extensions: ["dnd", "edit", "glyph"], //, "wide"
-        	glyph: glyph_opts,
-        	source: treeModel,
-        	lazyLoad : onLazyLoad,
-        	click : onClick,
-        	activate: onActivate,
-        	collapse : onCollapse //Yes, for collapsing event we need to use expand 
+                    });
+                    break;
+                case "PROJECT":
+                    var experimentRules = { "UUIDv4" : { type : "Attribute", name : "PROJECT_PERM_ID", value : permId } };
+                    mainController.serverFacade.searchForExperimentsAdvanced({ entityKind : "EXPERIMENT", logicalOperator : "AND", rules : experimentRules }, null, function(searchResult) {
+                        var results = [];
+                        var experiments = searchResult.objects;
+                        for (var i = 0; i < experiments.length; i++) {
+                            var experiment = experiments[i];
+                            var experimentDisplayName = experiment.code;
+                            if(experiment.properties && experiment.properties[profile.propertyReplacingCode]) {
+                                experimentDisplayName = experiment.properties[profile.propertyReplacingCode];
+                            }
+                            var isInventorySpace = profile.isInventorySpace(IdentifierUtil.getSpaceCodeFromIdentifier(experiment.getIdentifier().getIdentifier()));
+                            var viewToUse = null;
+                            var loadSamples = null;
+                            if(isInventorySpace) {
+                                viewToUse = "showSamplesPage";
+                                loadSamples = false;
+                            } else {
+                                viewToUse = "showExperimentPageFromIdentifier";
+                                loadSamples = true;
+                            }
+                            
+                            var experimentLink = _this.getLinkForNode(experimentDisplayName, experiment.getPermId().getPermId(), viewToUse, experiment.getIdentifier().getIdentifier());
+                            results.push({
+                                displayName: experimentDisplayName,
+                                title : experimentLink,
+                                entityType: "EXPERIMENT",
+                                key : experiment.getPermId().getPermId(),
+                                folder : true,
+                                lazy : loadSamples,
+                                view : viewToUse,
+                                viewData: experiment.getIdentifier().getIdentifier(),
+                                registrationDate: experiment.registrationDate,
+                            });
+                        }
+                        results.sort(sortItems);
+                        dfd.resolve(results);
+                    });
+                    break;
+                case "EXPERIMENT":
+                    var sampleRules = { "UUIDv4" : { type : "Experiment", name : "ATTR.PERM_ID", value : permId } };
+                    mainController.serverFacade.searchForSamplesAdvanced({ entityKind : "SAMPLE", logicalOperator : "AND", rules : sampleRules }, 
+                    { only : true, withProperties : true, withType : true, withExperiment : true, withParents : true, withChildren : true, withParentsType : true, withChildrenType : true},
+                    function(searchResult) {
+                        var samples = searchResult.objects;
+                        var samplesToShow = [];
+                        for(var sIdx = 0; sIdx < samples.length; sIdx++) {
+                            var sample = samples[sIdx];
+                            var sampleIsExperiment = sample.type.code.indexOf("EXPERIMENT") > -1;
+                            
+                            if(sampleIsExperiment) {
+                                var parentIsExperiment = false;
+                                if(sample.parents) {
+                                    for(var pIdx = 0; pIdx < sample.parents.length; pIdx++) {
+                                        var parentIdentifier = sample.parents[pIdx].identifier.identifier;
+                                        var parentInELN = profile.isELNIdentifier(parentIdentifier);
+                                        if(parentInELN) {
+                                            parentIsExperiment = parentIsExperiment || sample.parents[pIdx].type.code.indexOf("EXPERIMENT") > -1;
+                                        }
+                                    }
+                                }
+                                if(!parentIsExperiment) {
+                                    samplesToShow.push(sample);
+                            }
+                            }
+                        }
+                        
+                        var getOkResultsFunction = function(dfd, samples) {
+                            return function() {
+                                var results = [];
+                                for (var i = 0; i < samples.length; i++) {
+                                    var sample = samples[i];
+                                    var sampleDisplayName = sample.code;
+                                    if(sample.properties && sample.properties[profile.propertyReplacingCode]) {
+                                            sampleDisplayName = sample.properties[profile.propertyReplacingCode];
+                                    }
+                                    
+                                    var sampleLink = _this.getLinkForNode(sampleDisplayName, sample.getPermId().getPermId(), "showViewSamplePageFromPermId", sample.getPermId().getPermId());
+                                    var sampleNode = {
+                                        displayName: sampleDisplayName,
+                                        title : sampleLink,
+                                        entityType: "SAMPLE",
+                                        key : sample.getPermId().getPermId(),
+                                        folder : true,
+                                        lazy : true,
+                                        view : "showViewSamplePageFromPermId",
+                                        viewData: sample.getPermId().getPermId(),
+                                        icon : "fa fa-flask",
+                                        registrationDate: sample.registrationDate,
+                                    };
+                                    results.push(sampleNode);
+                                }
+                                
+                                var datasetRules = { "UUIDv4" : { type : "Experiment", name : "ATTR.PERM_ID", value : permId } };
+                                mainController.serverFacade.searchForDataSetsAdvanced({ entityKind : "DATASET", logicalOperator : "AND", rules : datasetRules }, null, function(searchResult) {
+                                    
+                                    var datasets = searchResult.objects;
+                                    var experimentDatasets = [];
+                                    for (var i = 0; i < datasets.length; i++) {
+                                        var dataset = datasets[i];
+                                        if(!dataset.sample) {
+                                                experimentDatasets.push(dataset);
+                                        }
+                                    }
+                                    
+                                    if(experimentDatasets.length > 50) {
+                                            Util.showInfo("More than 50 Datasets, please use the dataset viewer on the experiment to navigate them.");
+                                    } else {
+                                            for (var i = 0; i < experimentDatasets.length; i++) {
+                                            var dataset = experimentDatasets[i];
+                                            var datasetDisplayName = dataset.code;
+                                            if(dataset.properties && dataset.properties[profile.propertyReplacingCode]) {
+                                                    datasetDisplayName = dataset.properties[profile.propertyReplacingCode];
+                                            }
+                                            
+                                            var datasetLink = _this.getLinkForNode(datasetDisplayName, dataset.getPermId().getPermId(), "showViewDataSetPageFromPermId", dataset.getPermId().getPermId());
+                                            results.push({
+                                                displayName: datasetDisplayName,
+                                                title : datasetLink,
+                                                entityType: "DATASET",
+                                                key : dataset.getPermId().getPermId(),
+                                                folder : true,
+                                                lazy : false,
+                                                view : "showViewDataSetPageFromPermId",
+                                                viewData: dataset.getPermId().getPermId(),
+                                                icon : "fa fa-database",
+                                                registrationDate: dataset.registrationDate,
+                                            });
+                                        }
+                                    }
+                                
+                                results.sort(sortItems);
+                                    dfd.resolve(results);
+                                    Util.unblockUI();
+                                });
+                                
+                            }
+                        }
+                        
+                        var getCancelResultsFunction = function(dfd) {
+                                return function() {
+                                    dfd.resolve([]);
+                                }
+                        }
+                        
+                        if(samplesToShow.length > 50) {
+                                var toExecute = function() {
+                                    Util.blockUIConfirm("Do you want to show " + samplesWithoutELNParents.length + " " + ELNDictionary.Samples + " on the tree?", 
+                                            getOkResultsFunction(dfd, samplesToShow),
+                                            getCancelResultsFunction(dfd));
+                            }
+                            
+                                setTimeout(toExecute, 1000);
+                        } else {
+                                getOkResultsFunction(dfd, samplesToShow)();
+                        }
+                    });
+                    break;
+                case "SAMPLE":
+                    var sampleRules = { "UUIDv4" : { type : "Attribute", name : "PERM_ID", value : permId } };
+                    mainController.serverFacade.searchForSamplesAdvanced({ entityKind : "SAMPLE", logicalOperator : "AND", rules : sampleRules }, 
+                    { only : true, withProperties : true, withType : true, withExperiment : true, withParents : true, withChildren : true, withChildrenProperties : true, withParentsType : true, withChildrenType : true}
+                    , function(searchResult) {
+                        var results = [];
+                        var samples = searchResult.objects;
+                        if(samples && samples[0] && samples[0].children) {
+                            if(samples[0].children.length > 50) {
+                                Util.showInfo("More than 50 Samples, please use the children table to navigate them.");
+                            } else {
+                                for(var cIdx = 0; cIdx < samples[0].children.length; cIdx++) {
+                                    var sample = samples[0].children[cIdx];
+                                    if(sample.type.code.indexOf("EXPERIMENT") > -1) {
+                                        var sampleDisplayName = sample.code;
+                                    if(sample.properties && sample.properties[profile.propertyReplacingCode]) {
+                                            sampleDisplayName = sample.properties[profile.propertyReplacingCode];
+                                    }
+                                        var sampleLink = _this.getLinkForNode(sampleDisplayName, sample.getPermId().getPermId(), "showViewSamplePageFromPermId", sample.getPermId().getPermId());
+                                    var sampleNode = {
+                                        displayName: sampleDisplayName,
+                                        title : sampleLink,
+                                        entityType: "SAMPLE",
+                                        key : sample.getPermId().getPermId(),
+                                        folder : true,
+                                        lazy : true,
+                                        view : "showViewSamplePageFromPermId",
+                                        viewData: sample.getPermId().getPermId(),
+                                        icon : "fa fa-flask",
+                                        registrationDate: sample.registrationDate,
+                                    };
+                                    results.push(sampleNode);
+                                    }
+                                }
+                            }
+                        }
+                        
+                        var datasetRules = { "UUIDv4" : { type : "Sample", name : "ATTR.PERM_ID", value : permId } };
+                        mainController.serverFacade.searchForDataSetsAdvanced({ entityKind : "DATASET", logicalOperator : "AND", rules : datasetRules }, null, function(searchResult) {
+                            
+                            var datasets = searchResult.objects;
+                            if(datasets.length > 50) {
+                                Util.showInfo("More than 50 Datasets, please use the dataset viewer on the sample to navigate them.");
+                            } else {
+                                for (var i = 0; i < datasets.length; i++) {
+                                    var dataset = datasets[i];
+                                    var datasetDisplayName = dataset.code;
+                                    if(dataset.properties && dataset.properties[profile.propertyReplacingCode]) {
+                                        datasetDisplayName = dataset.properties[profile.propertyReplacingCode];
+                                    }
+                                    
+                                    var datasetLink = _this.getLinkForNode(datasetDisplayName, dataset.getPermId().getPermId(), "showViewDataSetPageFromPermId", dataset.getPermId().getPermId());
+                                    results.push({
+                                        displayName: datasetDisplayName,
+                                        title : datasetLink,
+                                        entityType: "DATASET",
+                                        key : dataset.getPermId().getPermId(),
+                                        folder : true,
+                                        lazy : false,
+                                        view : "showViewDataSetPageFromPermId",
+                                        viewData: dataset.getPermId().getPermId(),
+                                        icon : "fa fa-database",
+                                        registrationDate: dataset.registrationDate,
+                                    });
+                                }
+                            }
+                            
+                            results.sort(sortItems);
+                            dfd.resolve(results);
+                        });
+                    });
+                    
+                    break;
+                case "DATASET":
+                    break;
+            }
+        };
+        
+        var onActivate = function(event, data) {
+            data.node.setExpanded(true);
+        };
+        
+        var onClick = function(event, data) {
+            if(data.node.data.view) {
+                _this._sideMenuWidgetController._showNodeView(data.node);
+            }
+        };
+        
+        var onCollapse = function(event, data) {
+            if(data.node.lazy) { //Is going to be collapsed
+                data.node.removeChildren();
+                data.node.resetLazy();
+            }
+        };
+        
+        $tree.fancytree({
+            extensions: ["dnd", "edit", "glyph"], //, "wide"
+            glyph: glyph_opts,
+            source: treeModel,
+            lazyLoad : onLazyLoad,
+            click : onClick,
+            activate: onActivate,
+            collapse : onCollapse //Yes, for collapsing event we need to use expand 
         });
-		
+        
         this._sideMenuWidgetModel.menuDOMBody.append($tree);
         this._sideMenuWidgetModel.tree = $tree;
         
-		var labNotebook = $tree.fancytree("getTree").getNodeByKey("LAB_NOTEBOOK");
-		if (labNotebook) {
-	        labNotebook.setExpanded(true);
-		}
+        var labNotebook = $tree.fancytree("getTree").getNodeByKey("LAB_NOTEBOOK");
+        if (labNotebook) {
+            labNotebook.setExpanded(true);
+        }
         var inventoryNode = $tree.fancytree("getTree").getNodeByKey("INVENTORY");
-		if (inventoryNode) {
-			inventoryNode.setExpanded(true).done(function(){
-				inventoryNode.visit(function(node){
-					node.setExpanded(true).done(function(){
-						node.visit(function(node2){
-							node2.setExpanded(true);
-						})
-					});
-				})
-			});
-		}
-		var stock = $tree.fancytree("getTree").getNodeByKey("STOCK");
-		if (stock) {
-			stock.setExpanded(true);
-		}
-		
-		setCustomIcon($tree, "JUPYTER_WORKSPACE", "./img/jupyter-icon.png");
-		setCustomIcon($tree, "NEW_JUPYTER_NOTEBOOK", "./img/jupyter-icon.png");
+        if (inventoryNode) {
+            inventoryNode.setExpanded(true).done(function(){
+                inventoryNode.visit(function(node){
+                    node.setExpanded(true).done(function(){
+                        node.visit(function(node2){
+                            node2.setExpanded(true);
+                        })
+                    });
+                })
+            });
+        }
+        var stock = $tree.fancytree("getTree").getNodeByKey("STOCK");
+        if (stock) {
+            stock.setExpanded(true);
+        }
+        
+        setCustomIcon($tree, "JUPYTER_WORKSPACE", "./img/jupyter-icon.png");
+        setCustomIcon($tree, "NEW_JUPYTER_NOTEBOOK", "./img/jupyter-icon.png");
     }
     
     function setCustomIcon($tree, nodeKey, iconImage) {
-		var node = $tree.fancytree("getTree").getNodeByKey(nodeKey);
-		if(node) {
-			var $customIconNode = $("<span>", { class : "fancytree-custom-icon" }).append($("<img>", { "src" : iconImage, 'style' : 'width:16px; height:16px;'}));
-			var $nodeSpan = $(node.span);
-			var $nodeSpanIcon = $nodeSpan.find(".fancytree-icon");
-			$customIconNode.insertAfter($nodeSpanIcon);
-			$nodeSpanIcon.remove();
-		}
+        var node = $tree.fancytree("getTree").getNodeByKey(nodeKey);
+        if(node) {
+            var $customIconNode = $("<span>", { class : "fancytree-custom-icon" }).append($("<img>", { "src" : iconImage, 'style' : 'width:16px; height:16px;'}));
+            var $nodeSpan = $(node.span);
+            var $nodeSpanIcon = $nodeSpan.find(".fancytree-icon");
+            $customIconNode.insertAfter($nodeSpanIcon);
+            $nodeSpanIcon.remove();
+        }
     }
-}
\ No newline at end of file
+}
+
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/StorageManager/widgets/GridView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/StorageManager/widgets/GridView.js
index f5a1a228e4cddbcbfd5bce9edcad7211a2441050..bdc195e6d0e01ec596cfc6a1a9cbe002d1919887 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/StorageManager/widgets/GridView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/StorageManager/widgets/GridView.js
@@ -141,6 +141,10 @@ function GridView(gridModel) {
 					if (labels[i].data && labels[i].data["@type"] && labels[i].data["@type"] === "Sample") {
 						sample = jQuery.extend(true, {}, labels[i].data);
 					}
+					else if (!labels[i].data.size && labels[i].data && labels[i].data.samples && labels[i].data.samples.length > 0) {
+						// sample which is not in a box
+						sample = jQuery.extend(true, {}, labels[i].data.samples[0]);
+					}
 					
 					if(sample && sample.sampleTypeCode === "STORAGE_POSITION" && sample.parents && sample.parents[0]) {
 						if(profile.propertyReplacingCode &&  sample.parents[0].properties &&  sample.parents[0].properties[profile.propertyReplacingCode]) {
@@ -155,9 +159,9 @@ function GridView(gridModel) {
 							
 						var href = Util.getURLFor(null, "showViewSamplePageFromPermId", sample.permId);
 						optSampleTitle = $("<a>", { "href" : href, "class" : "browser-compatible-javascript-link" }).text(labels[i].displayName);
-						optSampleTitle.click(function() {
+						optSampleTitle.click((function(sample) {
 							mainController.changeView("showViewSamplePageFromPermId", sample.permId);
-						});
+						}).bind(this, sample));
 					}
 					
 					var labelContainer = $("<div>", { class: "storageBox", id : Util.guid() }).text(labels[i].displayName);
@@ -269,4 +273,4 @@ function GridView(gridModel) {
 	this.setExtraDragData = function(extraDragData) {
 		this._extraDragData = extraDragData;
 	}
-}
\ No newline at end of file
+}
diff --git a/openbis_standard_technologies/dist/core-plugins/openbis-sync/1/dss/services/resource-sync/data-source-servlet.py b/openbis_standard_technologies/dist/core-plugins/openbis-sync/1/dss/services/resource-sync/data-source-servlet.py
index 62eb241063a41710188d2e5f16e9df00c560162e..22e107f97b9e61b131ec243e048e193470da565b 100644
--- a/openbis_standard_technologies/dist/core-plugins/openbis-sync/1/dss/services/resource-sync/data-source-servlet.py
+++ b/openbis_standard_technologies/dist/core-plugins/openbis-sync/1/dss/services/resource-sync/data-source-servlet.py
@@ -288,14 +288,22 @@ def attachConnections(entity, xd_elm):
 def attachBinaryData(entity, xd_elm):
     entityKind = entity.getEntityKind().toString()
     if entityKind == "DATA_SET":
-        ''' Do not attach binary data if it is not a physical DS'''
-        '''TO-DO How about link DSs?'''
-        if entity.getEntity().getKind().equals(DataSetKind.PHYSICAL) == False:
-            return
         dataSetCode = entity.getCode()
-        binaryDataNode = ET.SubElement(xd_elm, "x:binaryData")
-        for path, crc32_checksm, file_length in getDataSetFileNodes(dataSetCode):
-            linkNode = ET.SubElement(binaryDataNode, "x:fileNode", attrib = {"path": path, "checksum": str(crc32_checksm), "length": str(file_length)})
+        if entity.getEntity().getKind().equals(DataSetKind.PHYSICAL):
+            binaryDataNode = ET.SubElement(xd_elm, "x:binaryData")
+            _addFileNodes(binaryDataNode, dataSetCode)
+        if entity.getEntity().getKind().equals(DataSetKind.LINK):
+            binaryDataNode = ET.SubElement(xd_elm, "x:binaryData")
+            for cc in entity.getEntity().getLinkedData().getContentCopies():
+                attributes = {}
+                _addIfSpecified(attributes, "id", cc.getId().getPermId())
+                _addIfSpecified(attributes, "externalDMS", cc.getExternalDms().getCode())
+                _addIfSpecified(attributes, "externalCode", cc.getExternalCode())
+                _addIfSpecified(attributes, "gitCommitHash", cc.getGitCommitHash())
+                _addIfSpecified(attributes, "gitRepositoryId", cc.getGitRepositoryId())
+                _addIfSpecified(attributes, "path", cc.getPath())
+                ET.SubElement(binaryDataNode, "x:contentCopy", attrib = attributes)
+            _addFileNodes(binaryDataNode, dataSetCode)
     else:
         attachments = entity.getAttachmentsOrNull()
         if not attachments:
@@ -306,6 +314,9 @@ def attachBinaryData(entity, xd_elm):
             desc = attachment.getDescription()
             linkNode = ET.SubElement(binaryDataNode, "x:attachment", attrib = {"fileName": attachment.getFileName(), "title":  title if  title else "", "latestVersion" : str(attachment.getVersion()), "description": desc if desc else "", "permink": attachment.getPermlink()})
 
+def _addIfSpecified(dict, key, value):
+    if value is not None:
+        dict[key] = value
 
 def createEntityMetaData(entity, url_elm, entityKind):
     if entityKind == "PROJECT":
@@ -654,45 +665,36 @@ def getTempDir():
     else:
         raise Exception("No temporary directory has been configured. Please check that 'temp-dir' property has been properly set in the service plugin plugin.properties.")
 
-def getDataSetFileNodes(dataSetCode):
-    paths = []
-    for path, crc32_checksum, file_length in getFilesInDataSet(dataSetCode) :
+def _addFileNodes(binaryDataNode, dataSetCode):
+    for path, crc32_checksum, checksum, file_length in getDataSetFileInfos(dataSetCode):
+        attributes = {"path": path, "length": str(file_length)}
+        if crc32_checksum is not None:
+            attributes["crc32checksum"] = crc32_checksum
+        if checksum is not None:
+            attributes["checksum"] = checksum
+        linkNode = ET.SubElement(binaryDataNode, "x:fileNode", attrib = attributes)
+    
+def getDataSetFileInfos(dataSetCode):
+    infos = []
+    for path, crc32_checksum, checksum, file_length in getFilesInDataSet(dataSetCode) :
         '''TO-DO make below  URL configurable'''
-        paths.append((getDownloadUrl() + "/datastore_server/" + dataSetCode + "/" + path + "?", crc32_checksum, file_length)) # + "?mode=simpleHtml&sessionID=" + userSessionToken 
-    return paths 
+        infos.append((getDownloadUrl() + "/datastore_server/" + dataSetCode + "/" + path + "?", crc32_checksum, checksum, file_length)) # + "?mode=simpleHtml&sessionID=" + userSessionToken 
+    return infos 
 
 def getFilesInDataSet(dataSetCode):
-    ''' TODO For some fileNodes getFileLength and getChecksumCRC32 will throw UnsupportedOperationException
-    when this happens we return UNSUPPORTED as the value.
-    It might be best to re-visit this'''
-    paths = []
+    infos = []
     fileNodes = contentProvider.getContent(dataSetCode).listMatchingNodes(".*\.*")
     if fileNodes:
         for  i, fileNode in enumerate(fileNodes):
             try:
                 if fileNodes[i].isDirectory() == False:
                     relPath = fileNodes[i].getRelativePath();
-                    fileLength = None
-                    crc32checksum = None
-                    try:
-                        fileLength = fileNode.getFileLength()
-                        crc32checksum = fileNode.getChecksumCRC32()
-                    except UnsupportedOperationException, ex:
-                        fileLength = "UNSUPPORTED"
-                        crc32checksum = "UNSUPPORTED"
-                    paths.append((relPath, crc32checksum, fileLength));
+                    fileLength = str(fileNode.getFileLength())
+                    crc32checksum = str(fileNode.getChecksumCRC32()) if fileNode.isChecksumCRC32Precalculated() else None
+                    checksum = fileNode.getChecksum()
+                    infos.append((relPath, crc32checksum, checksum, fileLength));
             except Exception, ex:
                 print "File  node: %d for data set' %s' no longer exists: %s" % (i, str(dataSetCode), ex.getMessage())
     else:
         print "No fileNode found in data set: '%s'" % str(dataSetCode)   
-    return paths
-#===============================================================================
-# def getFileName(dataSetCode):
-#     fileNodes = contentProvider.getContent(dataSetCode).listMatchingNodes(".*\.*")
-#     if fileNodes:
-#         relPath = fileNodes[0].getFile().getAbsolutePath();
-#         print "Found fileNode: %s in dataSet: %s" % (relPath, dataSetCode);
-#         return os.path.basename(relPath);
-#     else:
-#         raise "No fileNode found in data set: '%s'" % str(dataSetCode); 
-#===============================================================================
+    return infos