diff --git a/deep_sequencing_unit/source/java/ch/ethz/bsse/cisd/dsu/tracking/main/TrackingBO.java b/deep_sequencing_unit/source/java/ch/ethz/bsse/cisd/dsu/tracking/main/TrackingBO.java
index bb2e981e421ad6d5595c932742f3fb5298941207..a638dcbb7ebe6025f1fdf67e530747b5c47a7563 100644
--- a/deep_sequencing_unit/source/java/ch/ethz/bsse/cisd/dsu/tracking/main/TrackingBO.java
+++ b/deep_sequencing_unit/source/java/ch/ethz/bsse/cisd/dsu/tracking/main/TrackingBO.java
@@ -17,18 +17,10 @@
 package ch.ethz.bsse.cisd.dsu.tracking.main;
 
 import java.io.File;
+import java.io.IOException;
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.Map.Entry;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
 
 import ch.ethz.bsse.cisd.dsu.tracking.dto.TrackedEntities;
 import ch.ethz.bsse.cisd.dsu.tracking.dto.TrackingStateDTO;
@@ -441,10 +433,12 @@ public class TrackingBO
         if (commandLineMap.containsKey(TrackingClient.CL_PARAMETER_COPY_DATA_SETS))
         {
         	// Data Sets with higher priority get transferred first 
-            extraDataSetCopy(params, toTransferDataSetsHighPriority);
-            extraDataSetCopy(params, toTransferDataSets);
+            // extraSCICOREDataSetListCopy(params, toTransferDataSetsHighPriority);
+            // extraSCICOREDataSetListCopy(params, toTransferDataSets);
         }
 
+        extraSCICOREDataSetListCopy(params, filteredDataSets);
+
         LogUtils.info("Found " + filteredDataSets.size() + " data sets which are connected to samples in " + filterList.toString());       
         setLaneProperties(changedTrackingMap, v3, v3SessionToken);
         
@@ -486,11 +480,100 @@ public class TrackingBO
         return new SimpleDateFormat(DATE_FORMAT_PATTERN).format(Calendar.getInstance().getTime());
     }
 
-    
-    
+    private static final SimpleDateFormat LIST_TIMESTAMP_FORMAT = new SimpleDateFormat("yyyyMMddHHmmssSSSS");
+
+    private static void extraSCICOREDataSetListCopy(Parameters params, List<AbstractExternalData> dataSets) {
+        LogUtils.info("SCICORE dataset listing - Start");
+        String datasetListFileBytes = "";
+        for (AbstractExternalData dataSet:dataSets) {
+            datasetListFileBytes += dataSet.getPermId() + "\n";
+        }
+        LogUtils.info("SCICORE dataset listing - Content : " + datasetListFileBytes);
+
+        String timestamp = LIST_TIMESTAMP_FORMAT.format(new Date());
+        String tempCanonicalPath = null;
+        try {
+            tempCanonicalPath = java.nio.file.Files.createTempDirectory(timestamp + "-tracking-temp-").toFile().getCanonicalPath();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        LogUtils.info("SCICORE dataset listing - temp : " + tempCanonicalPath);
+
+        String datasetTypeCode = "FASTQ_GZ";
+        String datasetName = timestamp + "_LIST";
+        File datasetSource = new File(tempCanonicalPath + "/" + datasetName);
+        datasetSource.mkdirs();
+        LogUtils.info("SCICORE dataset listing - datasetSource : " + datasetSource.getPath());
+
+        File datasetListFile = new File(tempCanonicalPath + "/" + datasetName + "/" + timestamp + ".tsv");
+        try {
+            java.nio.file.Files.write(datasetListFile.toPath(), datasetListFileBytes.getBytes());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        LogUtils.info("SCICORE dataset listing - writing to : " + datasetListFile.getPath());
+
+        File datasetDestination = new File(params.getDestinationFolderMap().get(datasetTypeCode), datasetName);
+        datasetDestination.mkdirs();
+
+        LogUtils.info("SCICORE dataset listing - datasetDestination : " + datasetDestination.getPath());
+
+        RsyncCopier copier = null;
+        File rsyncBinary = new File(params.getRsyncBinary());
+        if (params.getRsyncFlags() != null)
+        {
+            LogUtils.info("SCICORE dataset listing - RSYNC WITH EXTRA PARAMETERS ");
+            List<String> cmdLineOptions = new ArrayList<String>(params.getRsyncFlags().length);
+            Collections.addAll(cmdLineOptions, params.getRsyncFlags());
+            copier = new RsyncCopier(rsyncBinary, null, cmdLineOptions.toArray(new String[cmdLineOptions.size()]));
+        } else
+        {
+            LogUtils.info("SCICORE dataset listing - RSYNC NO EXTRA PARAMETERS ");
+            copier = new RsyncCopier(rsyncBinary, (File) null, "");
+        }
+
+        final long start = System.currentTimeMillis();
+        LogUtils.info("SCICORE dataset listing - BEFORE RSYNC");
+        Status status = copier.copyContent(datasetSource, datasetDestination, null, null);
+        final long end = System.currentTimeMillis();
+        LogUtils.info("SCICORE dataset listing - AFTER RSYNC TIME: " + (end-start) + " millis.");
+        LogUtils.info("SCICORE dataset listing - AFTER RSYNC STATUS: " + status.toString());
+
+        if (status.isError())
+        {
+            String exceptionMsg =
+                    (status == null) ? "" : " Unexpected exception has occured: "
+                            + status.toString();
+
+            List<EMailAddress> adminEmails = new ArrayList<EMailAddress>();
+            for (String adminEmail : params.getAdminEmail().split(","))
+            {
+                adminEmails.add(new EMailAddress(adminEmail.trim()));
+            }
+
+            EnvironmentFailureException ret =
+                    LogUtils.environmentError(
+                            "Data transfer failed for %s. %s",
+                            datasetName, exceptionMsg);
+
+            IMailClient emailClient = params.getMailClient();
+            emailClient.sendEmailMessage("GFB Tracker: Data transfer problem",
+                    ret.getLocalizedMessage(), null,
+                    new EMailAddress(params.getNotificationEmail()),
+                    adminEmails.toArray(new EMailAddress[0]));
+        } else {
+            File datasetDestinationMarkerFile = new File(params.getDestinationFolderMap().get(datasetTypeCode), ".MARKER_is_finished_" + datasetName);
+            try {
+                datasetDestinationMarkerFile.createNewFile();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
 	private static void extraDataSetCopy(Parameters params, List<AbstractExternalData> dataSets)
     {
-
         RsyncCopier copier = null;
         File rsyncBinary = new File(params.getRsyncBinary());
         String base_path_string = params.getDssRoot();
diff --git a/installation/resource/installer/bin/bisup.sh b/installation/resource/installer/bin/bisup.sh
index 4d86fda34dd19a2a2b506698655bf41ed5b79f14..ac4aea8999c6458804d7750c0f459b0fdd8c3d5e 100755
--- a/installation/resource/installer/bin/bisup.sh
+++ b/installation/resource/installer/bin/bisup.sh
@@ -27,6 +27,14 @@ STARTING_MESSAGE="STARTING SERVER"
 STARTED_MESSAGE="SERVER STARTED"
 ERROR_MESSAGE="ERROR"
 
+function assertNoError() {
+    error=`egrep "($STARTING_MESSAGE|$ERROR_MESSAGE)" $1 | egrep -A 1 "$STARTING_MESSAGE" | tail -1 | grep "$ERROR_MESSAGE"`
+    if [ -n "$error" ]; then
+        echo "Failed: $error" 
+        exit 1;
+    fi
+}
+
 if [ -n "$(readlink $0)" ]; then
    # handle symbolic links
    scriptName=$(readlink $0)
@@ -46,10 +54,12 @@ fi
 JETTY_HOME=$BASE/../servers/openBIS-server/jetty
 OPENBIS_LOG=$JETTY_HOME/logs/openbis_log.txt
 JETTY_LOG=$JETTY_HOME/logs/jetty.out
+STARTED_MARKER=$JETTY_HOME/SERVER_STARTED
 TIMEOUT=120
 
 echo Starting openBIS...
 echo $STARTING_MESSAGE >> $OPENBIS_LOG
+rm -f $STARTED_MARKER
 
 # This variable suits as a workaround for cases where newly created files
 # have non-zero age according to our age measuring function
@@ -69,30 +79,13 @@ while [ "$bisLogAgeInSeconds" -lt $TIMEOUT ] || [ "$jettyLogAgeInSeconds" -lt $T
     echo -n "."
     sleep 2
 
-    # get the message "STARTING_SERVER" + the next matching message (i.e. STARTED or ERROR) 
-    bisLogStatus=`egrep "($STARTING_MESSAGE|$STARTED_MESSAGE|$ERROR_MESSAGE)" $OPENBIS_LOG  | egrep -A 1 "$STARTING_MESSAGE" | tail -1`
-    jettyLogStatus=`egrep "($STARTING_MESSAGE|$STARTED_MESSAGE|$ERROR_MESSAGE)" $JETTY_LOG  | egrep -A 1 "$STARTING_MESSAGE" | tail -1`    
-    
-    started=`echo $bisLogStatus | grep "$STARTED_MESSAGE"`
-    if [ -n "$started" ]; then
-        started=`echo $jettyLogStatus | grep "$STARTED_MESSAGE"`
-        if [ -n "$started" ]; then
-            echo "Done."
-            exit 0;
-        fi
-    fi
-    
-    error=`echo $bisLogStatus | grep "$ERROR_MESSAGE"`
-    if [ -n "$error" ]; then
-        echo "Failed: $error" 
-        exit 1;
-    fi
-    
-    error=`echo $jettyLogStatus | grep "$ERROR_MESSAGE"`
-    if [ -n "$error" ]; then
-        echo "Failed: $error" 
-        exit 1;
+    if [ -f $STARTED_MARKER ]; then
+        echo "Done."
+        exit 0;
     fi
+
+    assertNoError $OPENBIS_LOG
+    assertNoError $JETTY_LOG
     
     fileAgeInSeconds $OPENBIS_LOG
     bisLogAgeInSeconds=$(expr $? - $ageOfNewFile)
diff --git a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js
index 56bf7882d6194095a63ea6f39ba9a5a7d22bcdbd..ec8bc0a33c2290a23905b7365e4db615ed47dd7c 100644
--- a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js
+++ b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js
@@ -173,6 +173,8 @@ define([ 'jquery', 'openbis', 'underscore', 'test/dtos' ], function($, defaultOp
 		this.ReportingServiceSearchCriteria = dtos.ReportingServiceSearchCriteria;
 		this.ReportingServiceFetchOptions = dtos.ReportingServiceFetchOptions;
 		this.ReportingServiceExecutionOptions = dtos.ReportingServiceExecutionOptions;
+		this.Rights = dtos.Rights;
+		this.RightsFetchOptions = dtos.RightsFetchOptions;
 		this.ProcessingServiceSearchCriteria = dtos.ProcessingServiceSearchCriteria;
 		this.ProcessingServiceFetchOptions = dtos.ProcessingServiceFetchOptions;
 		this.ProcessingServiceExecutionOptions = dtos.ProcessingServiceExecutionOptions;
@@ -221,6 +223,7 @@ define([ 'jquery', 'openbis', 'underscore', 'test/dtos' ], function($, defaultOp
 		this.GetTagsOperation = dtos.GetTagsOperation;
 		this.GetAuthorizationGroupsOperation = dtos.GetAuthorizationGroupsOperation;
 		this.GetRoleAssignmentsOperation = dtos.GetRoleAssignmentsOperation;
+		this.GetRightsOperation = dtos.GetRightsOperation;
 		this.GetPersonsOperation = dtos.GetPersonsOperation;
 		this.GetExternalDmsOperation = dtos.GetExternalDmsOperation;
 		this.GetSemanticAnnotationsOperation = dtos.GetSemanticAnnotationsOperation;
diff --git a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js
index 5f3f357e7d2d61694772bcdd84c70f7035be9ac5..9e47c88437576755cf1c83b9c81ff6bf922a6cd0 100644
--- a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js
+++ b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js
@@ -79,6 +79,7 @@
 	// 'as/dto/vocabulary/id/IVocabularyId',
 	// 'as/dto/vocabulary/id/IVocabularyTermId',
 	// 'as/dto/roleassignment/id/RoleAssignmentTechId',
+	// 'as/dto/rights/Right',
 
 
 // these are the DTOs that can be "manually" created on the client
@@ -539,6 +540,11 @@ var sources = [
 	'as/dto/roleassignment/RoleLevel',
 	'as/dto/roleassignment/RoleAssignment',
 	
+	'as/dto/rights/fetchoptions/RightsFetchOptions',
+	'as/dto/rights/get/GetRightsOperation',
+	'as/dto/rights/get/GetRightsOperationResult',
+	'as/dto/rights/Rights',
+	
 	'as/dto/plugin/create/PluginCreation',
 	'as/dto/plugin/create/CreatePluginsOperation',
 	'as/dto/plugin/create/CreatePluginsOperationResult',
diff --git a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-get.js b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-get.js
index 0a42cbbdbad41033fb37180a53ab0c500162caf4..a6702146e7f7fcde5bab4823d765ad174457d18b 100644
--- a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-get.js
+++ b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-get.js
@@ -811,6 +811,22 @@ define([ 'jquery', 'underscore', 'openbis', 'test/openbis-execute-operations', '
 			testGet(c, fCreate, fGet, fGetEmptyFetchOptions, fechOptionsTestConfig);
 		});
 
+		QUnit.test("getRights()", function(assert) {
+			var c = new common(assert);
+			var sampleId = new c.SampleIdentifier("/PLATONIC/PLATE-2");
+			c.start();
+			
+			c.createFacadeAndLogin().then(function(facade) {
+				return facade.getRights([sampleId], new c.RightsFetchOptions()).then(function(rights) {
+					c.assertEqual(rights[sampleId].rights, "UPDATE", "Rights");
+					c.finish();
+				});
+			}).fail(function(error) {
+				c.fail(error.message);
+				c.finish();
+			});
+		});
+		
 		QUnit.test("getServerInformation()", function(assert) {
 			var c = new common(assert);
 			c.start();
@@ -827,7 +843,7 @@ define([ 'jquery', 'underscore', 'openbis', 'test/openbis-execute-operations', '
 				c.finish();
 			});
 		});
-
+		
 	}
 
 	return function() {
diff --git a/openbis-common/source/java/ch/systemsx/cisd/openbis/common/spring/MarkerLogApplicationListener.java b/openbis-common/source/java/ch/systemsx/cisd/openbis/common/spring/MarkerLogApplicationListener.java
index 60eb28860824f30e612a52ca9346968e15ca3969..6defe39004a113aa2a4efad4a246cd8866983c5e 100644
--- a/openbis-common/source/java/ch/systemsx/cisd/openbis/common/spring/MarkerLogApplicationListener.java
+++ b/openbis-common/source/java/ch/systemsx/cisd/openbis/common/spring/MarkerLogApplicationListener.java
@@ -16,6 +16,9 @@
 
 package ch.systemsx.cisd.openbis.common.spring;
 
+import java.io.File;
+import java.io.IOException;
+
 import org.apache.log4j.Logger;
 import org.springframework.context.ApplicationEvent;
 import org.springframework.context.ApplicationListener;
@@ -28,13 +31,15 @@ import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 
 /**
- * An application listener, whose sole purpose is to log a marker message immediately after the successful start/stop of the openBIS application
- * server.
+ * An application listener, whose sole purpose is to log a marker message and create a marker file 
+ * immediately after the successful start/stop of the openBIS application server.
  * 
  * @author Kaloyan Enimanev
  */
 public class MarkerLogApplicationListener implements ApplicationListener
 {
+    private static final File STARTED_FILE = new File("SERVER_STARTED");
+
     private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION,
             MarkerLogApplicationListener.class);
 
@@ -52,10 +57,20 @@ public class MarkerLogApplicationListener implements ApplicationListener
                     // root application context has been initialized
                 } else
                 {
-                    // print a marker string to make it possible
+                    // print a marker string and create marker file to make it possible
                     // for log-analyzing software to determine when the application is up and
                     // running
                     operationLog.info("SERVER STARTED");
+                    try
+                    {
+                        STARTED_FILE.createNewFile();
+                        STARTED_FILE.deleteOnExit();
+                        operationLog.info(STARTED_FILE.getAbsolutePath()+" created");
+                    } catch (IOException ex)
+                    {
+                        operationLog.error("Couldn't create marker file " + STARTED_FILE, ex);
+                    }
+
                 }
             } else if (isStoppingEvent(event))
             {
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java
index 0d82e6edafe0fdaa86bdd7aa9d4740b0c3647f5c..e36f64ac0392565ab1e0c1c33d4cf83fbf65fb5a 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java
@@ -44,6 +44,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.update.Update
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.TableModel;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.get.GetServerInformationOperation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.get.GetServerInformationOperationResult;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperationResult;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult;
@@ -298,6 +299,10 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.search.SearchQueriesOperat
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.search.SearchQueriesOperationResult;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.update.QueryUpdate;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.update.UpdateQueriesOperation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.get.GetRightsOperation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.get.GetRightsOperationResult;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.RoleAssignment;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.create.CreateRoleAssignmentsOperation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.create.CreateRoleAssignmentsOperationResult;
@@ -851,6 +856,14 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi>
         executeOperation(sessionToken, new UpdateSemanticAnnotationsOperation(updates));
     }
 
+    @Override
+    @Transactional(readOnly = true)
+    public Map<IObjectId, Rights> getRights(String sessionToken, List<? extends IObjectId> ids, RightsFetchOptions fetchOptions)
+    {
+        GetRightsOperationResult result = executeOperation(sessionToken, new GetRightsOperation(ids, fetchOptions));
+        return result.getObjectMap();
+    }
+
     @Override
     @Transactional(readOnly = true)
     public Map<ISpaceId, Space> getSpaces(String sessionToken, List<? extends ISpaceId> spaceIds, SpaceFetchOptions fetchOptions)
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java
index 97316ab2f980c5894a0fce8243f4d0b3ec5747b8..152e2bbd35ca5cb9e6b099e06a4788ce6b6d6d77 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java
@@ -29,6 +29,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.id.IAuthoriza
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.search.AuthorizationGroupSearchCriteria;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.update.AuthorizationGroupUpdate;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.TableModel;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSet;
@@ -153,6 +154,8 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.id.IQueryId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.id.QueryTechId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.search.QuerySearchCriteria;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.update.QueryUpdate;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.RoleAssignment;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.create.RoleAssignmentCreation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.delete.RoleAssignmentDeletionOptions;
@@ -554,6 +557,13 @@ public class ApplicationServerApiLogger extends AbstractServerLogger implements
         logAccess(sessionToken, "update-semantic-annotations", "SEMANTIC_ANNOTATION_UPDATES(%s)", abbreviate(annotationUpdates));
     }
 
+    @Override
+    public Map<IObjectId, Rights> getRights(String sessionToken, List<? extends IObjectId> ids, RightsFetchOptions fetchOptions)
+    {
+        logAccess(sessionToken, "get-rights", "IDS(%s) FETCH_OPTIONS(%s)", abbreviate(ids), fetchOptions);
+        return null;
+    }
+
     @Override
     public Map<ISpaceId, Space> getSpaces(String sessionToken, List<? extends ISpaceId> spaceIds, SpaceFetchOptions fetchOptions)
     {
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/OperationsExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/OperationsExecutor.java
index 8145b64a39a72bc758879da3ef2de82fe8b7a7d8..61a44a25b8d8e19798399af9a7dcb211d8691b4a 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/OperationsExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/OperationsExecutor.java
@@ -117,6 +117,7 @@ import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.query.IExecuteSqlOpe
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.query.IGetQueriesOperationExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.query.ISearchQueriesOperationExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.query.IUpdateQueriesOperationExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.rights.IGetRightsOperationExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.roleassignment.ICreateRoleAssignmentsOperationExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.roleassignment.IDeleteRoleAssignmentsOperationExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.roleassignment.IGetRoleAssignmentsOperationExecutor;
@@ -392,8 +393,11 @@ public class OperationsExecutor implements IOperationsExecutor
     private IInternalOperationExecutor internalOperationExecutor;
 
     @Autowired
-    private IGetSpacesOperationExecutor getSpacesExecutor;
+    private IGetRightsOperationExecutor getRightsExecutor;
 
+    @Autowired
+    private IGetSpacesOperationExecutor getSpacesExecutor;
+    
     @Autowired
     private IGetProjectsOperationExecutor getProjectsExecutor;
 
@@ -694,6 +698,7 @@ public class OperationsExecutor implements IOperationsExecutor
     private void executeGets(List<? extends IOperation> operations,
             Map<IOperation, IOperationResult> resultMap, IOperationContext context)
     {
+        resultMap.putAll(getRightsExecutor.execute(context, operations));
         resultMap.putAll(getSpacesExecutor.execute(context, operations));
         resultMap.putAll(getProjectsExecutor.execute(context, operations));
         resultMap.putAll(getExperimentsExecutor.execute(context, operations));
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/GetRightsExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/GetRightsExecutor.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a2734fed80916da15e831b4a5470403bae71dbf
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/GetRightsExecutor.java
@@ -0,0 +1,215 @@
+/*
+ * 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.asapi.v3.executor.rights;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.id.IDataSetId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id.IExperimentId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Right;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.ISampleId;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.dataset.IDataSetAuthorizationExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.dataset.IMapDataSetByIdExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.experiment.IExperimentAuthorizationExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.experiment.IMapExperimentByIdExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.sample.IMapSampleByIdExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.sample.ISampleAuthorizationExecutor;
+import ch.systemsx.cisd.common.exceptions.AuthorizationFailureException;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
+
+/**
+ * @author Franz-Josef Elmer
+ */
+@Component
+public class GetRightsExecutor implements IGetRightsExecutor
+{
+    @Autowired
+    private IMapSampleByIdExecutor mapSampleByIdExecutor;
+
+    @Autowired
+    private ISampleAuthorizationExecutor sampleAuthorizationExecutor;
+
+    @Autowired
+    private IMapExperimentByIdExecutor mapExperimentByIdExecutor;
+
+    @Autowired
+    private IExperimentAuthorizationExecutor experimentAuthorizationExecutor;
+
+    @Autowired
+    private IMapDataSetByIdExecutor mapDataSetByIdExecutor;
+
+    @Autowired
+    private IDataSetAuthorizationExecutor dataSetAuthorizationExecutor;
+
+    private List<Handler> handlers;
+
+    public GetRightsExecutor()
+    {
+        handlers = new ArrayList<>();
+        handlers.add(new SampleHandler());
+        handlers.add(new ExperimentHandler());
+        handlers.add(new DataSetHandler());
+    }
+
+    @Override
+    public Map<IObjectId, Rights> getRights(IOperationContext context, List<? extends IObjectId> objectIds, RightsFetchOptions fetchOptions)
+    {
+        Map<IObjectId, Rights> result = new HashMap<>();
+        for (IObjectId id : objectIds)
+        {
+            for (Handler handler : handlers)
+            {
+                handler.handle(id);
+            }
+        }
+        for (Handler handler : handlers)
+        {
+            handler.addRights(context, result);
+        }
+        return result;
+    }
+
+    private static interface Handler
+    {
+        void handle(IObjectId id);
+
+        void addRights(IOperationContext context, Map<IObjectId, Rights> rightsByIds);
+    }
+
+    private class SampleHandler implements Handler
+    {
+        private List<ISampleId> sampleIds = new ArrayList<>();
+
+        @Override
+        public void handle(IObjectId id)
+        {
+            if (id instanceof ISampleId)
+            {
+                sampleIds.add((ISampleId) id);
+            }
+        }
+
+        @Override
+        public void addRights(IOperationContext context, Map<IObjectId, Rights> rightsByIds)
+        {
+            Map<ISampleId, SamplePE> map = mapSampleByIdExecutor.map(context, sampleIds);
+            for (Entry<ISampleId, SamplePE> entry : map.entrySet())
+            {
+                Set<Right> rights = new HashSet<>();
+                ISampleId id = entry.getKey();
+                SamplePE object = entry.getValue();
+
+                try
+                {
+                    sampleAuthorizationExecutor.canUpdate(context, id, object);
+                    rights.add(Right.UPDATE);
+                } catch (AuthorizationFailureException e)
+                {
+                    // silently ignored
+                }
+                rightsByIds.put(id, new Rights(rights));
+            }
+        }
+    }
+
+    private class ExperimentHandler implements Handler
+    {
+        private List<IExperimentId> experimentIds = new ArrayList<>();
+
+        @Override
+        public void handle(IObjectId id)
+        {
+            if (id instanceof IExperimentId)
+            {
+                experimentIds.add((IExperimentId) id);
+            }
+        }
+
+        @Override
+        public void addRights(IOperationContext context, Map<IObjectId, Rights> rightsByIds)
+        {
+            Map<IExperimentId, ExperimentPE> map = mapExperimentByIdExecutor.map(context, experimentIds);
+            for (Entry<IExperimentId, ExperimentPE> entry : map.entrySet())
+            {
+                Set<Right> rights = new HashSet<>();
+                IExperimentId id = entry.getKey();
+                ExperimentPE object = entry.getValue();
+
+                try
+                {
+                    experimentAuthorizationExecutor.canUpdate(context, id, object);
+                    rights.add(Right.UPDATE);
+                } catch (AuthorizationFailureException e)
+                {
+                    // silently ignored
+                }
+                rightsByIds.put(id, new Rights(rights));
+            }
+        }
+    }
+
+    private class DataSetHandler implements Handler
+    {
+        private List<IDataSetId> dataSetIds = new ArrayList<>();
+
+        @Override
+        public void handle(IObjectId id)
+        {
+            if (id instanceof IDataSetId)
+            {
+                dataSetIds.add((IDataSetId) id);
+            }
+        }
+
+        @Override
+        public void addRights(IOperationContext context, Map<IObjectId, Rights> rightsByIds)
+        {
+            Map<IDataSetId, DataPE> map = mapDataSetByIdExecutor.map(context, dataSetIds);
+            for (Entry<IDataSetId, DataPE> entry : map.entrySet())
+            {
+                Set<Right> rights = new HashSet<>();
+                IDataSetId id = entry.getKey();
+                DataPE object = entry.getValue();
+
+                try
+                {
+                    dataSetAuthorizationExecutor.canUpdate(context, id, object);
+                    rights.add(Right.UPDATE);
+                } catch (AuthorizationFailureException e)
+                {
+                    // silently ignored
+                }
+                rightsByIds.put(id, new Rights(rights));
+            }
+        }
+    }
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/GetRightsOperationExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/GetRightsOperationExecutor.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d34b9c783e1b2bf616dc25012c6a86b372c5491
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/GetRightsOperationExecutor.java
@@ -0,0 +1,54 @@
+/*
+ * 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.asapi.v3.executor.rights;
+
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.get.GetRightsOperation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.get.GetRightsOperationResult;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.common.OperationExecutor;
+
+/**
+ * @author Franz-Josef Elmer
+ */
+@Component
+public class GetRightsOperationExecutor extends OperationExecutor<GetRightsOperation, GetRightsOperationResult>
+        implements IGetRightsOperationExecutor
+{
+    @Autowired
+    private IGetRightsExecutor executor;
+
+    @Override
+    protected Class<? extends GetRightsOperation> getOperationClass()
+    {
+        return GetRightsOperation.class;
+    }
+
+    @Override
+    protected GetRightsOperationResult doExecute(IOperationContext context, GetRightsOperation operation)
+    {
+        Map<IObjectId, Rights> map = executor.getRights(context, operation.getObjectIds(), operation.getFetchOptions());
+        return new GetRightsOperationResult(map);
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/IGetRightsExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/IGetRightsExecutor.java
new file mode 100644
index 0000000000000000000000000000000000000000..9e3d1a3b66a37d8f2f137796acd7a37ca64eb9d3
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/IGetRightsExecutor.java
@@ -0,0 +1,34 @@
+/*
+ * 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.asapi.v3.executor.rights;
+
+import java.util.List;
+import java.util.Map;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext;
+
+/**
+ * @author Franz-Josef Elmer
+ */
+public interface IGetRightsExecutor
+{
+
+    public Map<IObjectId, Rights> getRights(IOperationContext context, List<? extends IObjectId> objectIds, RightsFetchOptions fetchOptions);
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/IGetRightsOperationExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/IGetRightsOperationExecutor.java
new file mode 100644
index 0000000000000000000000000000000000000000..f1acb263ae00121d27a2c680b6511aa8570ba129
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/IGetRightsOperationExecutor.java
@@ -0,0 +1,28 @@
+/*
+ * 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.asapi.v3.executor.rights;
+
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.common.IOperationExecutor;
+
+/**
+ * @author Franz-Josef Elmer
+ *
+ */
+public interface IGetRightsOperationExecutor extends IOperationExecutor
+{
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/hotfix/ELNCollectionTypeMigration.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/hotfix/ELNCollectionTypeMigration.java
index f2ea8c6ddce82e2aac1cd5ef716a162bbb53a84b..1b612dc9f08d5d26f3030dba393f5ae93e320446 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/hotfix/ELNCollectionTypeMigration.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/hotfix/ELNCollectionTypeMigration.java
@@ -18,9 +18,13 @@ import ch.systemsx.cisd.openbis.generic.server.CommonServiceProvider;
 import ch.systemsx.cisd.openbis.generic.server.ComponentNames;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.DAOFactory;
 import org.hibernate.Session;
+import org.hibernate.internal.SessionImpl;
 import org.hibernate.query.NativeQuery;
 
 import java.math.BigInteger;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
 import java.util.*;
 
 
@@ -88,6 +92,11 @@ public class ELNCollectionTypeMigration {
         "UPDATE property_types SET code = 'EXPERIMENTAL_STEP.EXPERIMENTAL_DESCRIPTION' WHERE code = 'EXPERIMENTAL_PROCEDURE' and (select count(*) from core_plugins where name = 'eln-lims') > 0;"
     };
 
+    private static final String[] WIDGET_POST_UPDATES = new String[]{
+        "UPDATE property_types SET meta_data = '{ \"custom_widget\" : \"Word Processor\" }'::jsonb WHERE id IN (SELECT id FROM property_types WHERE daty_id = (SELECT id FROM data_types WHERE code = 'MULTILINE_VARCHAR'));",
+        "UPDATE property_types SET meta_data = NULL WHERE id IN (SELECT id FROM property_types WHERE daty_id IN (SELECT id FROM data_types WHERE code NOT IN ('MULTILINE_VARCHAR', 'XML')));"
+    };
+
     private static Set<ExperimentType> getExperimentTypes(String[] experimentCodes) {
         IApplicationServerInternalApi api = CommonServiceProvider.getApplicationServerApi();
         String sessionToken = api.loginAsSystem();
@@ -189,6 +198,18 @@ public class ELNCollectionTypeMigration {
         nativeQuery.executeUpdate();
     }
 
+    private static void executeNativeUpdate(String SQL) {
+        try {
+            DAOFactory daoFactory = (DAOFactory) CommonServiceProvider.getApplicationContext().getBean(ComponentNames.DAO_FACTORY);
+            Session currentSession = daoFactory.getSessionFactory().getCurrentSession();
+            Connection connection = ((SessionImpl) currentSession).connection();
+            PreparedStatement statement = connection.prepareStatement(SQL);
+            statement.execute();
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
     private static Map<BigInteger, BigInteger> getMap(List<Object[]> prty_id_AND_etpt_id) {
         Map<BigInteger, BigInteger> prty_id_2_etpt_id = new HashMap();
         for (Object[] row:prty_id_AND_etpt_id) {
@@ -197,8 +218,8 @@ public class ELNCollectionTypeMigration {
         return prty_id_2_etpt_id;
     }
 
-    public static void migrate() {
-        System.out.println("ELNCollectionTypeMigration START");
+    public static void beforeUpgrade() {
+        System.out.println("ELNCollectionTypeMigration beforeUpgrade START");
         // Obtain property types used by experiments that should be of type COLLECTION
         for (String experimentCode:experimentsOfTypeCollection) {
             Set<ExperimentType> experimentTypes = getExperimentTypes(new String[]{experimentCode});
@@ -261,6 +282,16 @@ public class ELNCollectionTypeMigration {
             executeUpdate(PROPERTY_UPDATE, null, null, null, null);
             System.out.println("PROPERTY_UPDATE DONE");
         }
-        System.out.println("ELNCollectionTypeMigration END");
+        System.out.println("ELNCollectionTypeMigration beforeUpgrade END");
+    }
+
+    public static void afterUpgrade() {
+        System.out.println("ELNCollectionTypeMigration afterUpgrade START");
+        for (String WIDGET_POST_UPDATE:WIDGET_POST_UPDATES) {
+            System.out.println("Going to Execute WIDGET_POST_UPDATES: " + WIDGET_POST_UPDATE);
+            executeNativeUpdate(WIDGET_POST_UPDATE);
+            System.out.println("WIDGET_POST_UPDATE DONE");
+        }
+        System.out.println("ELNCollectionTypeMigration afterUpgrade END");
     }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/Right.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/Right.js
new file mode 100644
index 0000000000000000000000000000000000000000..b9aa090e7d4b5675710ffeaead1525cf595bd063
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/Right.js
@@ -0,0 +1,8 @@
+define([ "stjs", "as/dto/common/Enum" ], function(stjs, Enum) {
+	var Right = function() {
+		Enum.call(this, [ "UPDATE", "CREATE_SAMPLE" ]);
+	};
+	stjs.extend(Right, Enum, [ Enum ], function(constructor, prototype) {
+	}, {});
+	return new Right();
+})
\ No newline at end of file
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/Rights.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/Rights.js
new file mode 100644
index 0000000000000000000000000000000000000000..803b23bf28a867ba4dd2a5460f5a13ed4a335f4c
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/Rights.js
@@ -0,0 +1,22 @@
+define(["stjs"], function(stjs) {
+	var Rights = function() {
+	};
+	stjs.extend(Rights, null, [], function(constructor, prototype) {
+		prototype["@type"] = 'as.dto.rights.Rights';
+		constructor.serialVersionUID = 1;
+		prototype.rights = null;
+		
+		prototype.getRights = function() {
+			return this.rights;
+		};
+		prototype.setRights = function(rights) {
+			this.rights = rights;
+		};
+	}, {
+		rights : {
+			name : "Set",
+			arguments : [ "Right"]
+		}
+	});
+	return Rights;
+})
\ No newline at end of file
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/fetchoptions/RightsFetchOptions.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/fetchoptions/RightsFetchOptions.js
new file mode 100644
index 0000000000000000000000000000000000000000..f5043caad3a1de25fe1e20f756d27df7da52f30b
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/fetchoptions/RightsFetchOptions.js
@@ -0,0 +1,9 @@
+define([ "stjs", "as/dto/common/fetchoptions/EmptyFetchOptions" ], function(stjs, EmptyFetchOptions) {
+	var RightsFetchOptions = function() {
+	};
+	stjs.extend(RightsFetchOptions, EmptyFetchOptions, [ EmptyFetchOptions ], function(constructor, prototype) {
+		prototype['@type'] = 'as.dto.rights.fetchoptions.RightsFetchOptions';
+		constructor.serialVersionUID = 1;
+	}, {});
+	return RightsFetchOptions;
+})
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/get/GetRightsOperation.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/get/GetRightsOperation.js
new file mode 100644
index 0000000000000000000000000000000000000000..fa136600e3e41d7ae8391c09f00de31fdd7af421
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/get/GetRightsOperation.js
@@ -0,0 +1,12 @@
+define([ "stjs", "as/dto/common/get/GetObjectsOperation" ], function(stjs, GetObjectsOperation) {
+	var GetRightsOperation = function(objectIds, fetchOptions) {
+		GetObjectsOperation.call(this, objectIds, fetchOptions);
+	};
+	stjs.extend(GetRightsOperation, GetObjectsOperation, [ GetObjectsOperation ], function(constructor, prototype) {
+		prototype['@type'] = 'as.dto.rights.get.GetRightsOperation';
+		prototype.getMessage = function() {
+			return "GetRightsOperation";
+		};
+	}, {});
+	return GetRightsOperation;
+})
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/get/GetRightsOperationResult.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/get/GetRightsOperationResult.js
new file mode 100644
index 0000000000000000000000000000000000000000..60991ee65f1c24c44b2bf63ec846ea89deae8165
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/get/GetRightsOperationResult.js
@@ -0,0 +1,12 @@
+define([ "stjs", "as/dto/common/get/GetObjectsOperationResult" ], function(stjs, GetObjectsOperationResult) {
+	var GetRightsOperationResult = function(objectMap) {
+		GetObjectsOperationResult.call(this, objectMap);
+	};
+	stjs.extend(GetRightsOperationResult, GetObjectsOperationResult, [ GetObjectsOperationResult ], function(constructor, prototype) {
+		prototype['@type'] = 'as.dto.rights.get.GetRightsOperationResult';
+		prototype.getMessage = function() {
+			return "GetRightsOperationResult";
+		};
+	}, {});
+	return GetRightsOperationResult;
+})
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/openbis.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/openbis.js
index 672ac400beb4e5427f8154a07f98d97679c906e2..9143e0a045f85a94cde9033f34dce92d60709bbf 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/openbis.js
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/openbis.js
@@ -904,6 +904,21 @@ define([ 'jquery', 'util/Json', 'as/dto/datastore/search/DataStoreSearchCriteria
 			});
 		}
 
+		this.getRights = function(ids, fetchOptions) {
+			var thisFacade = this;
+			return thisFacade._private.ajaxRequest({
+				url : openbisUrl,
+				data : {
+					"method" : "getRights",
+					"params" : [ thisFacade._private.sessionToken, ids, fetchOptions ]
+				},
+				returnType : {
+					name : "Map",
+					arguments : [ "IObjectId", "Rights" ]
+				}
+			});
+		}
+
 		this.getSpaces = function(ids, fetchOptions) {
 			var thisFacade = this;
 			return thisFacade._private.ajaxRequest({
@@ -918,7 +933,7 @@ define([ 'jquery', 'util/Json', 'as/dto/datastore/search/DataStoreSearchCriteria
 				}
 			});
 		}
-
+		
 		this.getProjects = function(ids, fetchOptions) {
 			var thisFacade = this;
 			return thisFacade._private.ajaxRequest({
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetRightsTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetRightsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d7245ac439b160245894950d951ff4bca8168176
--- /dev/null
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetRightsTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.systemtest.asapi.v3;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import org.testng.annotations.Test;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.id.DataSetPermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id.ExperimentIdentifier;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SampleIdentifier;
+
+/**
+ * @author Franz-Josef Elmer
+ */
+public class GetRightsTest extends AbstractTest
+{
+    @Test
+    public void testGetSampleRights()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_ROLE_V3, PASSWORD);
+        IObjectId s1 = new SampleIdentifier("/TEST-SPACE/CP-TEST-4");
+        IObjectId s2 = new SampleIdentifier("/CISD/CP-TEST-1");
+
+        // When
+        Map<IObjectId, Rights> map = v3api.getRights(sessionToken, Arrays.asList(s1, s2), new RightsFetchOptions());
+
+        // Then
+        assertEquals(map.get(s1).getRights().toString(), "[]");
+        assertEquals(map.get(s2).getRights().toString(), "[UPDATE]");
+    }
+
+    @Test
+    public void testGetExperimentRights()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_ROLE_V3, PASSWORD);
+        IObjectId s1 = new ExperimentIdentifier("/TEST-SPACE/TEST-PROJECT/EXP-SPACE-TEST");
+        IObjectId s2 = new ExperimentIdentifier("/CISD/NEMO/EXP1");
+
+        // When
+        Map<IObjectId, Rights> map = v3api.getRights(sessionToken, Arrays.asList(s1, s2), new RightsFetchOptions());
+
+        // Then
+        assertEquals(map.get(s1).getRights().toString(), "[]");
+        assertEquals(map.get(s2).getRights().toString(), "[UPDATE]");
+    }
+
+    @Test
+    public void testGetDataSetRights()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_ROLE_V3, PASSWORD);
+        IObjectId s1 = new DataSetPermId("20120619092259000-22");
+        IObjectId s2 = new DataSetPermId("20081105092259000-21");
+
+        // When
+        Map<IObjectId, Rights> map = v3api.getRights(sessionToken, Arrays.asList(s1, s2), new RightsFetchOptions());
+
+        // Then
+        assertEquals(map.get(s1).getRights().toString(), "[]");
+        assertEquals(map.get(s2).getRights().toString(), "[UPDATE]");
+    }
+}
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java
index 6cb34d1533ce27140dabacd9bfd6b19031d8ed58..a6f1246c4378bb12f5a11a50035831cb86e5ea4b 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java
@@ -28,6 +28,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.id.IAuthoriza
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.search.AuthorizationGroupSearchCriteria;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.update.AuthorizationGroupUpdate;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.TableModel;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSet;
@@ -152,6 +153,8 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.id.IQueryId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.id.QueryTechId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.search.QuerySearchCriteria;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.update.QueryUpdate;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.RoleAssignment;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.create.RoleAssignmentCreation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.delete.RoleAssignmentDeletionOptions;
@@ -771,6 +774,14 @@ public interface IApplicationServerApi extends IRpcService
      */
     public void updateQueries(String sessionToken, List<QueryUpdate> queryUpdates);
 
+    /**
+     * Gets authorization rights for the provided {@link IObjectId} ids. A result map contains an entry for a given id 
+     * only if an object for that id has been found and that object can be accessed by the user.
+     * 
+     * @throws UserFailureException in case of any problems
+     */
+    public Map<IObjectId, Rights> getRights(String sessionToken, List<? extends IObjectId> ids, RightsFetchOptions fetchOptions);
+
     /**
      * Gets spaces for the provided {@code ISpaceId} ids. A result map contains an entry for a given id only if a space for that id has been found and
      * that space can be accessed by the user.
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/Right.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/Right.java
new file mode 100644
index 0000000000000000000000000000000000000000..6cc6dcd9abe33f10ad25a2f69c3630da8ecb3b78
--- /dev/null
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/Right.java
@@ -0,0 +1,28 @@
+/*
+ * 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.asapi.v3.dto.rights;
+
+import ch.systemsx.cisd.base.annotation.JsonObject;
+
+/**
+ * @author Franz-Josef Elmer
+ */
+@JsonObject("as.dto.rights.Right")
+public enum Right
+{
+    UPDATE, CREATE_SAMPLE
+}
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/Rights.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/Rights.java
new file mode 100644
index 0000000000000000000000000000000000000000..14e612b7ac8f55dbf4136ee523b75338242a3f63
--- /dev/null
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/Rights.java
@@ -0,0 +1,63 @@
+/*
+ * 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.asapi.v3.dto.rights;
+
+import java.io.Serializable;
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import ch.systemsx.cisd.base.annotation.JsonObject;
+
+/**
+ * @author Franz-Josef Elmer
+ */
+@JsonObject("as.dto.rights.Rights")
+public class Rights implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    @JsonProperty
+    private Set<Right> rights;
+
+    @SuppressWarnings("unused")
+    private Rights()
+    {
+    }
+
+    public Rights(Set<Right> rights)
+    {
+        this.rights = rights;
+    }
+
+    public Set<Right> getRights()
+    {
+        return rights;
+    }
+
+    public void setRights(Set<Right> rights)
+    {
+        this.rights = rights;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "Rights: " + rights;
+    }
+
+}
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/fetchoptions/RightsFetchOptions.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/fetchoptions/RightsFetchOptions.java
new file mode 100644
index 0000000000000000000000000000000000000000..952d1abd691d0f73f94f62a65bff72d9c13b4d5d
--- /dev/null
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/fetchoptions/RightsFetchOptions.java
@@ -0,0 +1,30 @@
+/*
+ * 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.asapi.v3.dto.rights.fetchoptions;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.fetchoptions.EmptyFetchOptions;
+import ch.systemsx.cisd.base.annotation.JsonObject;
+
+/**
+ * @author Franz-Josef Elmer
+ *
+ */
+@JsonObject("as.dto.rights.fetchoptions.RightsFetchOptions")
+public class RightsFetchOptions extends EmptyFetchOptions
+{
+    private static final long serialVersionUID = 1L;
+}
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/get/GetRightsOperation.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/get/GetRightsOperation.java
new file mode 100644
index 0000000000000000000000000000000000000000..c7049d1e333c242cfaa3020ce625e93e2bf150c0
--- /dev/null
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/get/GetRightsOperation.java
@@ -0,0 +1,43 @@
+/*
+ * 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.asapi.v3.dto.rights.get;
+
+import java.util.List;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.get.GetObjectsOperation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions;
+import ch.systemsx.cisd.base.annotation.JsonObject;
+
+/**
+ * @author Franz-Josef Elmer
+ */
+@JsonObject("as.dto.rights.get.GetRightsOperation")
+public class GetRightsOperation extends GetObjectsOperation<IObjectId, RightsFetchOptions>
+{
+    private static final long serialVersionUID = 1L;
+
+    @SuppressWarnings("unused")
+    private GetRightsOperation()
+    {
+    }
+
+    public GetRightsOperation(List<? extends IObjectId> ids, RightsFetchOptions fetchOptions)
+    {
+        super(ids, fetchOptions);
+    }
+}
\ No newline at end of file
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/get/GetRightsOperationResult.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/get/GetRightsOperationResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e3bc7b0d9f518e16be036347563fde3c2b26a82
--- /dev/null
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/get/GetRightsOperationResult.java
@@ -0,0 +1,45 @@
+/*
+ * 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.asapi.v3.dto.rights.get;
+
+import java.util.Map;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.get.GetObjectsOperationResult;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights;
+import ch.systemsx.cisd.base.annotation.JsonObject;
+
+/**
+ * @author Franz-Josef Elmer
+ *
+ */
+@JsonObject("as.dto.rights.get.GetRightsOperationResult")
+public class GetRightsOperationResult extends GetObjectsOperationResult<IObjectId, Rights>
+{
+
+    private static final long serialVersionUID = 1L;
+
+    @SuppressWarnings("unused")
+    private GetRightsOperationResult()
+    {
+    }
+
+    public GetRightsOperationResult(Map<IObjectId, Rights> objectMap)
+    {
+        super(objectMap);
+    }
+}
diff --git a/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt b/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt
index 002d2d0c0b66fe9f7f02de0a45830a6ba9447794..d789dda8868bd61bd555440bc7041ccfe56a76d0 100644
--- a/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt
+++ b/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt
@@ -2322,3 +2322,12 @@ get Meta Data
 set Meta Data
 set Meta Data Actions
 
+get Rights
+Get Rights Operation
+Get Rights Operation Result
+Right
+Rights
+Rights Fetch Options
+set Rights
+CREATE_SAMPLE
+
diff --git a/openbis_ng_ui/package.json b/openbis_ng_ui/package.json
index 2ebec20c513a647d3b9291b8f888a3ddcaf5b0d0..074ed6cbb6f59e342288b496997ceb4660b6841a 100644
--- a/openbis_ng_ui/package.json
+++ b/openbis_ng_ui/package.json
@@ -12,6 +12,7 @@
     "npm": "^6.11.3",
     "path-to-regexp": "^3.1.0",
     "prop-types": "^15.7.2",
+    "re-resizable": "^6.1.0",
     "react": "^16.10.1",
     "react-beautiful-dnd": "^11.0.5",
     "react-dom": "^16.10.1",
diff --git a/openbis_ng_ui/src/js/components/common/browser/Browser.jsx b/openbis_ng_ui/src/js/components/common/browser/Browser.jsx
index b5c1f23e5757dddc2aa8431da7e91c6d8ff9c8f9..6bf2924c95ae6e1959c363301d26d2f156a0778c 100644
--- a/openbis_ng_ui/src/js/components/common/browser/Browser.jsx
+++ b/openbis_ng_ui/src/js/components/common/browser/Browser.jsx
@@ -1,6 +1,7 @@
 import React from 'react'
 import _ from 'lodash'
 import Paper from '@material-ui/core/Paper'
+import { Resizable } from 're-resizable'
 import { connect } from 'react-redux'
 import { withStyles } from '@material-ui/core/styles'
 import logger from '../../../common/logger.js'
@@ -11,10 +12,12 @@ import FilterField from './../form/FilterField.jsx'
 import BrowserNodes from './BrowserNodes.jsx'
 
 const styles = {
-  container: {
+  resizable: {
     display: 'flex',
-    flexDirection: 'column',
-    minWidth: '300px'
+    flexDirection: 'column'
+  },
+  paper: {
+    height: '100%'
   }
 }
 
@@ -59,19 +62,33 @@ class Browser extends React.PureComponent {
     const classes = this.props.classes
 
     return (
-      <Paper square={true} elevation={3} classes={{ root: classes.container }}>
-        <FilterField
-          filter={this.props.filter}
-          filterChange={this.props.filterChange}
-        />
-        <BrowserNodes
-          nodes={this.props.nodes}
-          nodeSelect={this.props.nodeSelect}
-          nodeExpand={this.props.nodeExpand}
-          nodeCollapse={this.props.nodeCollapse}
-          level={0}
-        />
-      </Paper>
+      <Resizable
+        enable={{
+          right: true,
+          left: false,
+          top: false,
+          bottom: false,
+          topRight: false,
+          bottomRight: false,
+          bottomLeft: false,
+          topLeft: false
+        }}
+        className={classes.resizable}
+      >
+        <Paper square={true} elevation={3} classes={{ root: classes.paper }}>
+          <FilterField
+            filter={this.props.filter}
+            filterChange={this.props.filterChange}
+          />
+          <BrowserNodes
+            nodes={this.props.nodes}
+            nodeSelect={this.props.nodeSelect}
+            nodeExpand={this.props.nodeExpand}
+            nodeCollapse={this.props.nodeCollapse}
+            level={0}
+          />
+        </Paper>
+      </Resizable>
     )
   }
 }
diff --git a/openbis_ng_ui/src/js/components/common/form/CheckboxField.jsx b/openbis_ng_ui/src/js/components/common/form/CheckboxField.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..62a39657b87ca1a54c520f47536faa70c1fb41f0
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/common/form/CheckboxField.jsx
@@ -0,0 +1,23 @@
+import React from 'react'
+import { withStyles } from '@material-ui/core/styles'
+import Checkbox from '@material-ui/core/Checkbox'
+import FormField from './FormField.jsx'
+import logger from '../../../common/logger.js'
+
+const styles = () => ({})
+
+class CheckboxFormField extends React.PureComponent {
+  render() {
+    logger.log(logger.DEBUG, 'CheckboxFormField.render')
+
+    const { value, disabled } = this.props
+
+    return (
+      <FormField {...this.props}>
+        <Checkbox checked={value} disabled={disabled} />
+      </FormField>
+    )
+  }
+}
+
+export default withStyles(styles)(CheckboxFormField)
diff --git a/openbis_ng_ui/src/js/components/common/form/FormField.jsx b/openbis_ng_ui/src/js/components/common/form/FormField.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..4b605bcc1e929c0553fe8363e46c5c4281f0ece7
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/common/form/FormField.jsx
@@ -0,0 +1,88 @@
+import React from 'react'
+import { withStyles } from '@material-ui/core/styles'
+import FormControl from '@material-ui/core/FormControl'
+import FormHelperText from '@material-ui/core/FormHelperText'
+import Typography from '@material-ui/core/Typography'
+import logger from '../../../common/logger.js'
+
+const styles = theme => ({
+  labelContainer: {
+    display: 'flex',
+    alignItems: 'stretch',
+    '& div': {
+      flex: '0 0 auto',
+      paddingRight: theme.spacing(1)
+    },
+    '& b': {
+      fontWeight: 'bold',
+      color: theme.palette.error.main
+    },
+    '& pre': {
+      flex: '0 0 auto',
+      margin: 0,
+      marginLeft: theme.spacing(1),
+      color: theme.palette.grey.main
+    }
+  },
+  controlContainer: {
+    width: '100%'
+  }
+})
+
+class FormField extends React.PureComponent {
+  constructor(props) {
+    super(props)
+  }
+
+  render() {
+    logger.log(logger.DEBUG, 'FormField.render')
+
+    const { description, onClick, styles = {} } = this.props
+
+    return (
+      <div onClick={onClick} className={styles.container}>
+        <FormControl fullWidth={true}>
+          <Typography component='label'>{this.renderLabel()}</Typography>
+          <div>{this.renderControl()}</div>
+          <FormHelperText>
+            <span data-part='description' className={styles.description}>
+              {description}
+            </span>
+          </FormHelperText>
+        </FormControl>
+      </div>
+    )
+  }
+
+  renderLabel() {
+    const { label, mandatory, metadata, classes, styles = {} } = this.props
+    return (
+      <div className={classes.labelContainer}>
+        <div>
+          <span data-part='label' className={styles.label}>
+            {label}
+          </span>{' '}
+          {mandatory && (
+            <b data-part='mandatory' className={styles.mandatory}>
+              *
+            </b>
+          )}
+        </div>
+        <pre data-part='metadata' className={styles.metadata}>
+          {metadata}
+        </pre>
+      </div>
+    )
+  }
+
+  renderControl() {
+    const { children, classes, styles = {} } = this.props
+    return (
+      <div data-part='control' className={classes.controlContainer}>
+        <div className={styles.control}>{children}</div>
+      </div>
+    )
+  }
+}
+
+export default withStyles(styles)(FormField)
diff --git a/openbis_ng_ui/src/js/components/common/form/SelectField.jsx b/openbis_ng_ui/src/js/components/common/form/SelectField.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..6bf390ed039c6ae9b7cdac06bca5c22707a36b94
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/common/form/SelectField.jsx
@@ -0,0 +1,31 @@
+import React from 'react'
+import { withStyles } from '@material-ui/core/styles'
+import TextField from '@material-ui/core/TextField'
+import MenuItem from '@material-ui/core/MenuItem'
+import FormField from './FormField.jsx'
+import logger from '../../../common/logger.js'
+
+const styles = () => ({})
+
+class SelectFormField extends React.PureComponent {
+  render() {
+    logger.log(logger.DEBUG, 'SelectFormField.render')
+
+    const { value, disabled, options } = this.props
+
+    return (
+      <FormField {...this.props}>
+        <TextField select value={value} disabled={disabled} fullWidth={true}>
+          {options &&
+            options.map(option => (
+              <MenuItem key={option.value} value={option.value}>
+                {option.label || option.value}
+              </MenuItem>
+            ))}
+        </TextField>
+      </FormField>
+    )
+  }
+}
+
+export default withStyles(styles)(SelectFormField)
diff --git a/openbis_ng_ui/src/js/components/common/form/TextField.jsx b/openbis_ng_ui/src/js/components/common/form/TextField.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..40e3b611f2e2e32889e1551c9482e26c945d0810
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/common/form/TextField.jsx
@@ -0,0 +1,28 @@
+import React from 'react'
+import { withStyles } from '@material-ui/core/styles'
+import TextField from '@material-ui/core/TextField'
+import FormField from './FormField.jsx'
+import logger from '../../../common/logger.js'
+
+const styles = () => ({})
+
+class SelectFormField extends React.PureComponent {
+  render() {
+    logger.log(logger.DEBUG, 'SelectFormField.render')
+
+    const { type, value, disabled } = this.props
+
+    return (
+      <FormField {...this.props}>
+        <TextField
+          type={type}
+          value={value}
+          disabled={disabled}
+          fullWidth={true}
+        />
+      </FormField>
+    )
+  }
+}
+
+export default withStyles(styles)(SelectFormField)
diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectType.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectType.jsx
index 2fe17b80c545825ce5fcf6fa6bfd2692f1d08de2..6ac80111e6609176597d704668b9ee0f4db51443 100644
--- a/openbis_ng_ui/src/js/components/types/objectType/ObjectType.jsx
+++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectType.jsx
@@ -1,6 +1,7 @@
 import _ from 'lodash'
 import React from 'react'
 import { withStyles } from '@material-ui/core/styles'
+import { Resizable } from 're-resizable'
 import ObjectTypePreview from './ObjectTypePreview.jsx'
 import ObjectTypeParameters from './ObjectTypeParameters.jsx'
 import ObjectTypeButtons from './ObjectTypeButtons.jsx'
@@ -44,6 +45,10 @@ class ObjectType extends React.PureComponent {
     this.handleOrderChange = this.handleOrderChange.bind(this)
     this.handleSelectionChange = this.handleSelectionChange.bind(this)
     this.handleChange = this.handleChange.bind(this)
+    this.handleAddSection = this.handleAddSection.bind(this)
+    this.handleAddProperty = this.handleAddProperty.bind(this)
+    this.handleRemove = this.handleRemove.bind(this)
+    this.handleSave = this.handleSave.bind(this)
   }
 
   componentDidMount() {
@@ -63,27 +68,16 @@ class ObjectType extends React.PureComponent {
         subcodeUnique: loadedType.subcodeUnique
       }
 
-      const sections = loadedType.propertyAssignments.reduce(
-        (sections, assignment, index) => {
-          let section = sections[sections.length - 1]
-          if (section.name === assignment.section) {
-            section.properties.push('property-' + index)
-          } else {
-            let newSection = {
-              id: 'section-' + sections.length,
-              name: assignment.section,
-              properties: ['property-' + index]
-            }
-            sections.push(newSection)
-          }
-          return sections
-        },
-        [{ id: 'section-0', name: null, properties: [] }]
-      )
+      const sections = []
+      const properties = []
+      let currentSection = null
+      let currentProperty = null
+      let sectionsCounter = 0
+      let propertiesCounter = 0
 
-      const properties = loadedType.propertyAssignments.map(
-        (assignment, index) => ({
-          id: 'property-' + index,
+      loadedType.propertyAssignments.forEach(assignment => {
+        currentProperty = {
+          id: 'property-' + propertiesCounter++,
           code: assignment.propertyType.code,
           label: assignment.propertyType.label,
           description: assignment.propertyType.description,
@@ -95,16 +89,31 @@ class ObjectType extends React.PureComponent {
             ? assignment.propertyType.materialType.code
             : null,
           visible: assignment.showInEditView,
-          mandatory: assignment.mandatory,
-          section: _.find(sections, ['name', assignment.section]).id
-        })
-      )
+          mandatory: assignment.mandatory
+        }
+
+        if (currentSection && currentSection.name === assignment.section) {
+          currentSection.properties.push(currentProperty.id)
+        } else {
+          currentSection = {
+            id: 'section-' + sectionsCounter++,
+            name: assignment.section,
+            properties: [currentProperty.id]
+          }
+          sections.push(currentSection)
+        }
+        currentProperty.section = currentSection.id
+
+        properties.push(currentProperty)
+      })
 
       this.setState(() => ({
         loaded: true,
         type,
         properties,
+        propertiesCounter,
         sections,
+        sectionsCounter,
         selection: null
       }))
     })
@@ -289,6 +298,184 @@ class ObjectType extends React.PureComponent {
     }))
   }
 
+  handleAddSection() {
+    let { sections, sectionsCounter, selection } = this.state
+
+    let newSections = Array.from(sections)
+    let newSection = {
+      id: 'section-' + sectionsCounter++,
+      name: null,
+      properties: []
+    }
+    let newSelection = {
+      type: 'section',
+      params: {
+        id: newSection.id
+      }
+    }
+
+    if (selection) {
+      if (selection.type === 'section') {
+        let index = sections.findIndex(
+          section => section.id === selection.params.id
+        )
+        newSections.splice(index + 1, 0, newSection)
+      } else if (selection.type === 'property') {
+        let index = sections.findIndex(
+          section => section.properties.indexOf(selection.params.id) !== -1
+        )
+        newSections.splice(index + 1, 0, newSection)
+      } else {
+        newSections.push(newSection)
+      }
+    } else {
+      newSections.push(newSection)
+    }
+
+    this.setState(state => ({
+      ...state,
+      sections: newSections,
+      sectionsCounter,
+      selection: newSelection
+    }))
+  }
+
+  handleAddProperty() {
+    let { sections, properties, propertiesCounter, selection } = this.state
+
+    let sectionIndex = null
+    let sectionPropertyIndex = null
+    let propertyIndex = null
+
+    if (selection.type === 'section') {
+      sectionIndex = sections.findIndex(
+        section => section.id === selection.params.id
+      )
+      sectionPropertyIndex = sections[sectionIndex].properties.length
+      propertyIndex = properties.length
+    } else if (selection.type === 'property') {
+      sections.forEach((section, i) => {
+        section.properties.forEach((property, j) => {
+          if (property === selection.params.id) {
+            sectionIndex = i
+            sectionPropertyIndex = j + 1
+          }
+        })
+      })
+      propertyIndex =
+        properties.findIndex(property => property.id === selection.params.id) +
+        1
+    }
+
+    let section = sections[sectionIndex]
+
+    let newProperties = Array.from(properties)
+    let newProperty = {
+      id: 'property-' + propertiesCounter++,
+      code: '',
+      label: '',
+      description: '',
+      dataType: 'VARCHAR',
+      vocabulary: null,
+      materialType: null,
+      visible: true,
+      mandatory: false,
+      section: section.id
+    }
+    newProperties.splice(propertyIndex, 0, newProperty)
+
+    let newSection = {
+      ...section,
+      properties: Array.from(section.properties)
+    }
+    newSection.properties.splice(sectionPropertyIndex, 0, newProperty.id)
+
+    let newSections = Array.from(sections)
+    newSections[sectionIndex] = newSection
+
+    let newSelection = {
+      type: 'property',
+      params: {
+        id: newProperty.id
+      }
+    }
+
+    this.setState(state => ({
+      ...state,
+      sections: newSections,
+      properties: newProperties,
+      propertiesCounter,
+      selection: newSelection
+    }))
+  }
+
+  handleRemove() {
+    const { selection } = this.state
+
+    if (selection.type === 'section') {
+      this.handleRemoveSection(selection.params.id)
+    } else if (selection.type === 'property') {
+      this.handleRemoveProperty(selection.params.id)
+    }
+  }
+
+  handleRemoveSection(sectionId) {
+    const { sections, properties } = this.state
+
+    const sectionIndex = sections.findIndex(section => section.id === sectionId)
+    const section = sections[sectionIndex]
+
+    const newProperties = Array.from(properties)
+    _.remove(
+      newProperties,
+      property => section.properties.indexOf(property.id) !== -1
+    )
+
+    const newSections = Array.from(sections)
+    newSections.splice(sectionIndex, 1)
+
+    this.setState(state => ({
+      ...state,
+      sections: newSections,
+      properties: newProperties,
+      selection: null
+    }))
+  }
+
+  handleRemoveProperty(propertyId) {
+    const { sections, properties } = this.state
+
+    const propertyIndex = properties.findIndex(
+      property => property.id === propertyId
+    )
+    const property = properties[propertyIndex]
+
+    const newProperties = Array.from(properties)
+    newProperties.splice(propertyIndex, 1)
+
+    let sectionIndex = sections.findIndex(
+      section => section.id === property.section
+    )
+    let section = sections[sectionIndex]
+    let newSection = {
+      ...section,
+      properties: Array.from(section.properties)
+    }
+    _.remove(newSection.properties, property => property === propertyId)
+
+    const newSections = Array.from(sections)
+    newSections[sectionIndex] = newSection
+
+    this.setState(state => ({
+      ...state,
+      sections: newSections,
+      properties: newProperties,
+      selection: null
+    }))
+  }
+
+  handleSave() {}
+
   render() {
     logger.log(logger.DEBUG, 'ObjectType.render')
 
@@ -313,18 +500,41 @@ class ObjectType extends React.PureComponent {
             />
           </div>
           <div className={classes.buttons}>
-            <ObjectTypeButtons />
+            <ObjectTypeButtons
+              onAddSection={this.handleAddSection}
+              onAddProperty={this.handleAddProperty}
+              onRemove={this.handleRemove}
+              onSave={this.handleSave}
+              addSectionEnabled={true}
+              addPropertyEnabled={selection !== null}
+              removeEnabled={selection !== null}
+              saveEnabled={false}
+            />
           </div>
         </div>
-        <div className={classes.parameters}>
-          <ObjectTypeParameters
-            type={type}
-            properties={properties}
-            sections={sections}
-            selection={selection}
-            onChange={this.handleChange}
-          />
-        </div>
+        <Resizable
+          enable={{
+            left: true,
+            top: false,
+            right: false,
+            bottom: false,
+            topRight: false,
+            bottomRight: false,
+            bottomLeft: false,
+            topLeft: false
+          }}
+        >
+          <div className={classes.parameters}>
+            <ObjectTypeParameters
+              type={type}
+              properties={properties}
+              sections={sections}
+              selection={selection}
+              onChange={this.handleChange}
+              onSelectionChange={this.handleSelectionChange}
+            />
+          </div>
+        </Resizable>
       </div>
     )
   }
diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeButtons.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeButtons.jsx
index bf55e1d7c58be93ed14e0318c94f39bb0d7c9bc1..2a72b1866847be15d5c2c95692ea48148cf47d25 100644
--- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeButtons.jsx
+++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeButtons.jsx
@@ -17,7 +17,17 @@ class ObjectTypeButtons extends React.PureComponent {
   render() {
     logger.log(logger.DEBUG, 'ObjectTypeButtons.render')
 
-    const classes = this.props.classes
+    const {
+      classes,
+      onAddSection,
+      onAddProperty,
+      onRemove,
+      onSave,
+      addSectionEnabled,
+      addPropertyEnabled,
+      removeEnabled,
+      saveEnabled
+    } = this.props
 
     return (
       <div className={classes.container}>
@@ -25,7 +35,8 @@ class ObjectTypeButtons extends React.PureComponent {
           classes={{ root: classes.button }}
           variant='contained'
           color='secondary'
-          onClick={this.props.onAddSection}
+          disabled={!addSectionEnabled}
+          onClick={onAddSection}
         >
           Add Section
         </Button>
@@ -33,15 +44,26 @@ class ObjectTypeButtons extends React.PureComponent {
           classes={{ root: classes.button }}
           variant='contained'
           color='secondary'
-          onClick={this.props.onAddProperty}
+          disabled={!addPropertyEnabled}
+          onClick={onAddProperty}
         >
           Add Property
         </Button>
+        <Button
+          classes={{ root: classes.button }}
+          variant='contained'
+          color='secondary'
+          disabled={!removeEnabled}
+          onClick={onRemove}
+        >
+          Remove
+        </Button>
         <Button
           classes={{ root: classes.button }}
           variant='contained'
           color='primary'
-          onClick={this.props.onSave}
+          disabled={!saveEnabled}
+          onClick={onSave}
         >
           Save
         </Button>
diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParameters.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParameters.jsx
index 8aa4985a05d15d41b1e6d4606f435d4889687ca2..6eeabc6918c48627d4e7695b4096bdf23ddb714c 100644
--- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParameters.jsx
+++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParameters.jsx
@@ -5,7 +5,11 @@ import ObjectTypeParametersProperty from './ObjectTypeParametersProperty.jsx'
 import ObjectTypeParametersSection from './ObjectTypeParametersSection.jsx'
 import logger from '../../../common/logger.js'
 
-const styles = () => ({})
+const styles = () => ({
+  container: {
+    minWidth: '400px'
+  }
+})
 
 class ObjectTypeParameters extends React.PureComponent {
   constructor(props) {
@@ -15,10 +19,18 @@ class ObjectTypeParameters extends React.PureComponent {
   render() {
     logger.log(logger.DEBUG, 'ObjectTypeParameters.render')
 
-    const { type, sections, properties, selection, onChange } = this.props
+    const {
+      type,
+      sections,
+      properties,
+      selection,
+      onChange,
+      onSelectionChange,
+      classes
+    } = this.props
 
     return (
-      <React.Fragment>
+      <div className={classes.container}>
         <ObjectTypeParametersType
           type={type}
           selection={selection}
@@ -33,8 +45,9 @@ class ObjectTypeParameters extends React.PureComponent {
           properties={properties}
           selection={selection}
           onChange={onChange}
+          onSelectionChange={onSelectionChange}
         />
-      </React.Fragment>
+      </div>
     )
   }
 }
diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersProperty.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersProperty.jsx
index 64a5890d5288c8009bca010336bc1015bd0af803..9a28a232926c6a771c7e2603d2d84a4a2e13f4d7 100644
--- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersProperty.jsx
+++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersProperty.jsx
@@ -18,11 +18,21 @@ class ObjectTypeParametersProperty extends React.PureComponent {
   constructor(props) {
     super(props)
     this.state = {}
+    this.references = {
+      code: React.createRef(),
+      label: React.createRef(),
+      description: React.createRef(),
+      dataType: React.createRef(),
+      mandatory: React.createRef()
+    }
+    this.actions = {}
     this.handleChange = this.handleChange.bind(this)
+    this.handleFocus = this.handleFocus.bind(this)
   }
 
   componentDidMount() {
     this.load()
+    this.focus()
   }
 
   componentDidUpdate(prevProps) {
@@ -35,6 +45,13 @@ class ObjectTypeParametersProperty extends React.PureComponent {
     if (prevDataType !== dataType) {
       this.load()
     }
+
+    const prevSelection = prevProps.selection
+    const selection = this.props.selection
+
+    if (prevSelection !== selection) {
+      this.focus()
+    }
   }
 
   load() {
@@ -73,6 +90,23 @@ class ObjectTypeParametersProperty extends React.PureComponent {
     })
   }
 
+  focus() {
+    const property = this.getProperty(this.props)
+    if (property) {
+      const { part } = this.props.selection.params
+      if (part) {
+        const reference = this.references[part]
+        if (reference) {
+          reference.current.focus()
+        }
+        const action = this.actions[part]
+        if (action) {
+          action.focusVisible()
+        }
+      }
+    }
+  }
+
   handleChange(event) {
     const property = this.getProperty(this.props)
 
@@ -95,6 +129,26 @@ class ObjectTypeParametersProperty extends React.PureComponent {
     this.props.onChange('property', params)
   }
 
+  handleFocus(event) {
+    const property = this.getProperty(this.props)
+
+    let params = null
+
+    if (_.has(event.target, 'checked')) {
+      params = {
+        id: property.id,
+        part: event.target.value
+      }
+    } else {
+      params = {
+        id: property.id,
+        part: event.target.name
+      }
+    }
+
+    this.props.onSelectionChange('property', params)
+  }
+
   render() {
     logger.log(logger.DEBUG, 'ObjectTypeParametersProperty.render')
 
@@ -112,9 +166,10 @@ class ObjectTypeParametersProperty extends React.PureComponent {
         <form>
           <div>
             <TextField
-              label='Code'
-              name='code'
-              value={property.code}
+              inputRef={this.references.label}
+              label='Label'
+              name='label'
+              value={property.label}
               fullWidth={true}
               margin='normal'
               variant='filled'
@@ -122,13 +177,15 @@ class ObjectTypeParametersProperty extends React.PureComponent {
                 shrink: true
               }}
               onChange={this.handleChange}
+              onFocus={this.handleFocus}
             />
           </div>
           <div>
             <TextField
-              label='Label'
-              name='label'
-              value={property.label}
+              inputRef={this.references.code}
+              label='Code'
+              name='code'
+              value={property.code}
               fullWidth={true}
               margin='normal'
               variant='filled'
@@ -136,11 +193,12 @@ class ObjectTypeParametersProperty extends React.PureComponent {
                 shrink: true
               }}
               onChange={this.handleChange}
+              onFocus={this.handleFocus}
             />
           </div>
           <div>
             <TextField
-              multiline
+              inputRef={this.references.description}
               label='Description'
               name='description'
               value={property.description}
@@ -151,11 +209,13 @@ class ObjectTypeParametersProperty extends React.PureComponent {
                 shrink: true
               }}
               onChange={this.handleChange}
+              onFocus={this.handleFocus}
             />
           </div>
           <div>
             <TextField
               select
+              inputRef={this.references.dataType}
               label='Data Type'
               name='dataType'
               SelectProps={{
@@ -169,6 +229,7 @@ class ObjectTypeParametersProperty extends React.PureComponent {
                 shrink: true
               }}
               onChange={this.handleChange}
+              onFocus={this.handleFocus}
             >
               {dto.DataType.values.sort().map(dataType => {
                 return (
@@ -196,6 +257,7 @@ class ObjectTypeParametersProperty extends React.PureComponent {
                   shrink: true
                 }}
                 onChange={this.handleChange}
+                onFocus={this.handleFocus}
               >
                 <option key='' value=''></option>
                 {vocabularies &&
@@ -226,6 +288,7 @@ class ObjectTypeParametersProperty extends React.PureComponent {
                   shrink: true
                 }}
                 onChange={this.handleChange}
+                onFocus={this.handleFocus}
               >
                 <option key='' value=''></option>
                 {materialTypes &&
@@ -243,9 +306,14 @@ class ObjectTypeParametersProperty extends React.PureComponent {
             <FormControlLabel
               control={
                 <Checkbox
+                  inputRef={this.references.mandatory}
+                  action={actions => {
+                    this.actions.mandatory = actions
+                  }}
                   value='mandatory'
                   checked={property.mandatory}
                   onChange={this.handleChange}
+                  onFocus={this.handleFocus}
                 />
               }
               label='Mandatory'
@@ -258,6 +326,7 @@ class ObjectTypeParametersProperty extends React.PureComponent {
                   value='visible'
                   checked={property.visible}
                   onChange={this.handleChange}
+                  onFocus={this.handleFocus}
                 />
               }
               label='Visible'
diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreview.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreview.jsx
index 3074fd320adf38b5902f9e5610dcf2d2bdcae4a2..17a63a5b1eba9bc1e6b4428f6baf368ba37b7196 100644
--- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreview.jsx
+++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreview.jsx
@@ -10,10 +10,13 @@ import logger from '../../../common/logger.js'
 
 const styles = theme => ({
   container: {
-    padding: theme.spacing(2),
-    height: '100%',
-    boxSizing: 'border-box'
+    display: 'flex',
+    padding: `${theme.spacing(2)}px ${theme.spacing(4)}px`
+  },
+  header: {
+    marginBottom: theme.spacing(2)
   },
+  form: {},
   droppable: {
     display: 'flex',
     flexDirection: 'column',
@@ -25,11 +28,23 @@ const styles = theme => ({
 class ObjectTypePreview extends React.PureComponent {
   constructor(props) {
     super(props)
+    this.state = {}
+    this.handleDragStart = this.handleDragStart.bind(this)
     this.handleDragEnd = this.handleDragEnd.bind(this)
     this.handleClick = this.handleClick.bind(this)
   }
 
+  handleDragStart(start) {
+    this.setState({ dragging: true })
+
+    this.props.onSelectionChange(start.type, {
+      id: start.draggableId
+    })
+  }
+
   handleDragEnd(result) {
+    this.setState({ dragging: false })
+
     if (!result.destination) {
       return
     }
@@ -50,7 +65,9 @@ class ObjectTypePreview extends React.PureComponent {
   }
 
   handleClick() {
-    this.props.onSelectionChange()
+    if (!this.state.dragging) {
+      this.props.onSelectionChange()
+    }
   }
 
   render() {
@@ -60,24 +77,31 @@ class ObjectTypePreview extends React.PureComponent {
 
     return (
       <div className={classes.container} onClick={this.handleClick}>
-        <Typography variant='h6'>Form Preview</Typography>
-        <ObjectTypePreviewCode type={type} />
-        <DragDropContext onDragEnd={this.handleDragEnd}>
-          <Droppable droppableId='root' type='section'>
-            {provided => (
-              <div
-                ref={provided.innerRef}
-                {...provided.droppableProps}
-                className={classes.droppable}
-              >
-                {sections.map((section, index) =>
-                  this.renderSection(section, index)
-                )}
-                {provided.placeholder}
-              </div>
-            )}
-          </Droppable>
-        </DragDropContext>
+        <div className={classes.form}>
+          <Typography variant='h6' className={classes.header}>
+            Form Preview
+          </Typography>
+          <ObjectTypePreviewCode type={type} />
+          <DragDropContext
+            onDragStart={this.handleDragStart}
+            onDragEnd={this.handleDragEnd}
+          >
+            <Droppable droppableId='root' type='section'>
+              {provided => (
+                <div
+                  ref={provided.innerRef}
+                  {...provided.droppableProps}
+                  className={classes.droppable}
+                >
+                  {sections.map((section, index) =>
+                    this.renderSection(section, index)
+                  )}
+                  {provided.placeholder}
+                </div>
+              )}
+            </Droppable>
+          </DragDropContext>
+        </div>
       </div>
     )
   }
diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewCode.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewCode.jsx
index 1267a39f7a7d9de050f6e1bc6967c091581addbf..77b78c0684f8ec69dbcf6c43ca52f4edc14f689b 100644
--- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewCode.jsx
+++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewCode.jsx
@@ -1,6 +1,6 @@
 import _ from 'lodash'
 import React from 'react'
-import TextField from '@material-ui/core/TextField'
+import TextField from '../../common/form/TextField.jsx'
 import { withStyles } from '@material-ui/core/styles'
 import logger from '../../../common/logger.js'
 
@@ -19,7 +19,6 @@ class ObjectTypePreviewCode extends React.PureComponent {
         label='Code'
         value={this.getValue()}
         disabled={this.getDisabled()}
-        variant='filled'
       />
     )
   }
diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewProperty.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewProperty.jsx
index 089d1ab88c64765a02637dc56c8f0ab3b6fc3a79..0492c52266ac947824a3bd79508d6d078bc7a0cd 100644
--- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewProperty.jsx
+++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewProperty.jsx
@@ -2,36 +2,160 @@ import _ from 'lodash'
 import React from 'react'
 import { Draggable } from 'react-beautiful-dnd'
 import { withStyles } from '@material-ui/core/styles'
-import ObjectTypePreviewPropertyVarchar from './ObjectTypePreviewPropertyVarchar.jsx'
-import ObjectTypePreviewPropertyNumber from './ObjectTypePreviewPropertyNumber.jsx'
-import ObjectTypePreviewPropertyBoolean from './ObjectTypePreviewPropertyBoolean.jsx'
-import ObjectTypePreviewPropertyVocabulary from './ObjectTypePreviewPropertyVocabulary.jsx'
-import ObjectTypePreviewPropertyMaterial from './ObjectTypePreviewPropertyMaterial.jsx'
+import CheckboxField from '../../common/form/CheckboxField.jsx'
+import TextField from '../../common/form/TextField.jsx'
+import SelectField from '../../common/form/SelectField.jsx'
+import { facade, dto } from '../../../services/openbis.js'
 import logger from '../../../common/logger.js'
 import * as util from '../../../common/util.js'
 
-const styles = () => ({
-  container: {
-    padding: '10px'
+const styles = theme => ({
+  draggable: {
+    width: '100%',
+    padding: theme.spacing(2),
+    boxSizing: 'border-box',
+    '&:last-child': {
+      marginBottom: 0
+    },
+    '&:hover': {
+      backgroundColor: theme.palette.background.primary
+    }
+  },
+  selected: {
+    backgroundColor: theme.palette.background.secondary,
+    '&:hover': {
+      backgroundColor: theme.palette.background.secondary
+    }
+  },
+  hidden: {
+    opacity: 0.4
+  },
+  partEmpty: {
+    fontStyle: 'italic',
+    opacity: 0.7,
+    color: theme.palette.grey.main,
+    '&:after': {
+      content: '"empty"'
+    }
   },
-  selected: {}
+  partSelected: {
+    cursor: 'pointer',
+    paddingBottom: '1px',
+    borderColor: theme.palette.secondary.main,
+    borderStyle: 'solid',
+    borderWidth: '0px 0px 2px 0px',
+    '&:hover': {
+      borderColor: theme.palette.secondary.main
+    }
+  },
+  partNotSelected: {
+    cursor: 'pointer',
+    paddingBottom: '1px',
+    '&:hover': {
+      borderStyle: 'solid',
+      borderWidth: '0px 0px 2px 0px',
+      borderColor: theme.palette.primary.main
+    }
+  }
 })
 
 class ObjectTypePreviewProperty extends React.PureComponent {
   constructor(props) {
     super(props)
-    this.handleClick = this.handleClick.bind(this)
+    this.state = {}
+    this.handleDraggableClick = this.handleDraggableClick.bind(this)
+    this.handlePropertyClick = this.handlePropertyClick.bind(this)
   }
 
-  handleClick(event) {
+  componentDidMount() {
+    const { dataType } = this.props.property
+
+    if (dataType === 'MATERIAL') {
+      this.loadMaterialProperty()
+    } else if (dataType === 'CONTROLLEDVOCABULARY') {
+      this.loadVocabularyProperty()
+    }
+  }
+
+  componentDidUpdate(prevProps) {
+    let { property: prevProperty } = prevProps
+    let { property } = this.props
+
+    if (property.materialType !== prevProperty.materialType) {
+      this.loadMaterialProperty()
+    } else if (property.vocabulary !== prevProperty.vocabulary) {
+      this.loadVocabularyProperty()
+    }
+  }
+
+  loadMaterialProperty() {
+    const materialType = this.props.property.materialType
+
+    if (materialType) {
+      let criteria = new dto.MaterialSearchCriteria()
+      let fo = new dto.MaterialFetchOptions()
+
+      criteria
+        .withType()
+        .withCode()
+        .thatEquals(materialType)
+
+      return facade.searchMaterials(criteria, fo).then(result => {
+        this.setState(() => ({
+          materials: result.objects
+        }))
+      })
+    } else {
+      this.setState(() => ({
+        materials: null
+      }))
+    }
+  }
+
+  loadVocabularyProperty() {
+    const vocabulary = this.props.property.vocabulary
+
+    if (vocabulary) {
+      let criteria = new dto.VocabularyTermSearchCriteria()
+      let fo = new dto.VocabularyTermFetchOptions()
+
+      criteria
+        .withVocabulary()
+        .withCode()
+        .thatEquals(vocabulary)
+
+      return facade.searchVocabularyTerms(criteria, fo).then(result => {
+        this.setState(() => ({
+          terms: result.objects
+        }))
+      })
+    } else {
+      this.setState(() => ({
+        terms: null
+      }))
+    }
+  }
+
+  handleDraggableClick(event) {
+    event.stopPropagation()
+    this.props.onSelectionChange('property', {
+      id: this.props.property.id,
+      part: 'label'
+    })
+  }
+
+  handlePropertyClick(event) {
     event.stopPropagation()
-    this.props.onSelectionChange('property', { id: this.props.property.id })
+    this.props.onSelectionChange('property', {
+      id: this.props.property.id,
+      part: event.target.dataset.part
+    })
   }
 
   render() {
     logger.log(logger.DEBUG, 'ObjectTypePreviewProperty.render')
 
-    let { property, index, selection, classes } = this.props
+    let { property, selection, index, classes } = this.props
 
     const selected =
       selection &&
@@ -46,35 +170,199 @@ class ObjectTypePreviewProperty extends React.PureComponent {
             {...provided.draggableProps}
             {...provided.dragHandleProps}
             className={util.classNames(
-              classes.container,
+              classes.draggable,
               selected ? classes.selected : null
             )}
-            onClick={this.handleClick}
+            onClick={this.handleDraggableClick}
           >
-            {this.renderProperty(property)}
+            {this.renderProperty()}
           </div>
         )}
       </Draggable>
     )
   }
 
-  renderProperty(property) {
-    const dataType = property.dataType
+  renderProperty() {
+    const { dataType } = this.props.property
 
     if (dataType === 'VARCHAR' || dataType === 'MULTILINE_VARCHAR') {
-      return <ObjectTypePreviewPropertyVarchar property={property} />
+      return this.renderVarcharProperty()
     } else if (dataType === 'REAL' || dataType === 'INTEGER') {
-      return <ObjectTypePreviewPropertyNumber property={property} />
+      return this.renderNumberProperty()
     } else if (dataType === 'BOOLEAN') {
-      return <ObjectTypePreviewPropertyBoolean property={property} />
+      return this.renderBooleanProperty()
     } else if (dataType === 'CONTROLLEDVOCABULARY') {
-      return <ObjectTypePreviewPropertyVocabulary property={property} />
+      return this.renderVocabularyProperty()
     } else if (dataType === 'MATERIAL') {
-      return <ObjectTypePreviewPropertyMaterial property={property} />
+      return this.renderMaterialProperty()
     } else {
       return <span>Data type not supported yet</span>
     }
   }
+
+  renderMetadata() {
+    const { code, dataType } = this.props.property
+    const styles = this.createStyles()
+
+    return (
+      <React.Fragment>
+        [
+        <span
+          data-part='code'
+          className={styles.code}
+          onClick={this.handlePropertyClick}
+        >
+          {code}
+        </span>
+        ][
+        <span
+          data-part='dataType'
+          className={styles.dataType}
+          onClick={this.handlePropertyClick}
+        >
+          {dataType}
+        </span>
+        ]
+      </React.Fragment>
+    )
+  }
+
+  renderVarcharProperty() {
+    const { property } = this.props
+    return (
+      <TextField
+        label={property.label}
+        description={property.description}
+        mandatory={property.mandatory}
+        metadata={this.renderMetadata()}
+        styles={this.createStyles()}
+        onClick={this.handlePropertyClick}
+      />
+    )
+  }
+
+  renderNumberProperty() {
+    const { property } = this.props
+    return (
+      <TextField
+        type='number'
+        label={property.label}
+        description={property.description}
+        mandatory={property.mandatory}
+        metadata={this.renderMetadata()}
+        styles={this.createStyles()}
+        onClick={this.handlePropertyClick}
+      />
+    )
+  }
+
+  renderBooleanProperty() {
+    const { property } = this.props
+    return (
+      <div>
+        <CheckboxField
+          label={property.label}
+          description={property.description}
+          mandatory={property.mandatory}
+          metadata={this.renderMetadata()}
+          styles={this.createStyles()}
+          onClick={this.handlePropertyClick}
+        />
+      </div>
+    )
+  }
+
+  renderVocabularyProperty() {
+    const { property } = this.props
+    const { terms } = this.state
+
+    let options = []
+    if (terms) {
+      options = terms.map(term => ({
+        value: term.code,
+        label: term.label
+      }))
+    }
+
+    return (
+      <SelectField
+        label={property.label}
+        description={property.description}
+        mandatory={property.mandatory}
+        options={options}
+        metadata={this.renderMetadata()}
+        styles={this.createStyles()}
+        onClick={this.handlePropertyClick}
+      />
+    )
+  }
+
+  renderMaterialProperty() {
+    const { property } = this.props
+    const { materials } = this.state
+
+    let options = []
+    if (materials) {
+      options = materials.map(material => ({
+        value: material.code
+      }))
+    }
+
+    return (
+      <SelectField
+        label={property.label}
+        description={property.description}
+        mandatory={property.mandatory}
+        options={options}
+        metadata={this.renderMetadata()}
+        styles={this.createStyles()}
+        onClick={this.handlePropertyClick}
+      />
+    )
+  }
+
+  createStyles() {
+    const { property, selection, classes } = this.props
+
+    let styles = {}
+
+    const parts = ['code', 'label', 'dataType', 'mandatory', 'description']
+    const selectedPart =
+      selection &&
+      selection.type === 'property' &&
+      selection.params.id === property.id &&
+      selection.params.part
+
+    parts.forEach(part => {
+      const partStyles = []
+
+      if (part === selectedPart) {
+        partStyles.push(classes.partSelected)
+      } else {
+        partStyles.push(classes.partNotSelected)
+      }
+
+      const partValue = property[part]
+
+      if (!partValue) {
+        partStyles.push(classes.partEmpty)
+      }
+
+      styles = {
+        ...styles,
+        [part]: partStyles.join(' ')
+      }
+    })
+
+    if (!property.visible) {
+      styles = {
+        ...styles,
+        container: classes.hidden
+      }
+    }
+
+    return styles
+  }
 }
 
 export default _.flow(withStyles(styles))(ObjectTypePreviewProperty)
diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyBoolean.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyBoolean.jsx
deleted file mode 100644
index b377b87d11cc6abcfaf0577648227748a30a9b46..0000000000000000000000000000000000000000
--- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyBoolean.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react'
-import FormControl from '@material-ui/core/FormControl'
-import FormControlLabel from '@material-ui/core/FormControlLabel'
-import FormHelperText from '@material-ui/core/FormHelperText'
-import Checkbox from '@material-ui/core/Checkbox'
-import { withStyles } from '@material-ui/core/styles'
-import logger from '../../../common/logger.js'
-
-const styles = () => ({
-  visible: {
-    opacity: 1
-  },
-  hidden: {
-    opacity: 0.5
-  }
-})
-
-class ObjectTypePreviewPropertyBoolean extends React.PureComponent {
-  constructor(props) {
-    super(props)
-  }
-
-  render() {
-    logger.log(logger.DEBUG, 'ObjectTypePreviewPropertyBoolean.render')
-
-    const { property, classes } = this.props
-
-    return (
-      <FormControl
-        className={property.visible ? classes.visible : classes.hidden}
-      >
-        <FormControlLabel control={<Checkbox />} label={property.label} />
-        <FormHelperText>{property.description}</FormHelperText>
-      </FormControl>
-    )
-  }
-}
-
-export default withStyles(styles)(ObjectTypePreviewPropertyBoolean)
diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyMaterial.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyMaterial.jsx
deleted file mode 100644
index 5ed0e1ef494bf28ab20ccf6619b226730ae456e8..0000000000000000000000000000000000000000
--- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyMaterial.jsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import React from 'react'
-import TextField from '@material-ui/core/TextField'
-import { withStyles } from '@material-ui/core/styles'
-import { facade, dto } from '../../../services/openbis.js'
-import logger from '../../../common/logger.js'
-
-const styles = () => ({
-  visible: {
-    opacity: 1
-  },
-  hidden: {
-    opacity: 0.5
-  }
-})
-
-class ObjectTypePreviewPropertyMaterial extends React.PureComponent {
-  constructor(props) {
-    super(props)
-    this.state = {}
-  }
-
-  componentDidMount() {
-    this.load()
-  }
-
-  componentDidUpdate(prevProps) {
-    if (this.props.property.materialType !== prevProps.property.materialType) {
-      this.load()
-    }
-  }
-
-  load() {
-    if (this.props.property.materialType) {
-      let criteria = new dto.MaterialSearchCriteria()
-      let fo = new dto.MaterialFetchOptions()
-
-      criteria
-        .withType()
-        .withCode()
-        .thatEquals(this.props.property.materialType)
-
-      return facade.searchMaterials(criteria, fo).then(result => {
-        this.setState(() => ({
-          materials: result.objects
-        }))
-      })
-    } else {
-      this.setState(() => ({
-        materials: null
-      }))
-    }
-  }
-
-  render() {
-    logger.log(logger.DEBUG, 'ObjectTypePreviewPropertyMaterial.render')
-
-    const { property, classes } = this.props
-    const { materials } = this.state
-
-    return (
-      <TextField
-        select
-        error={property.mandatory}
-        SelectProps={{
-          native: true
-        }}
-        InputLabelProps={{
-          shrink: true
-        }}
-        label={property.label}
-        helperText={property.description}
-        fullWidth={true}
-        className={property.visible ? classes.visible : classes.hidden}
-        variant='filled'
-      >
-        {materials &&
-          materials.map(material => (
-            <option key={material.code} value={material.code}>
-              {material.code}
-            </option>
-          ))}
-        <React.Fragment></React.Fragment>
-      </TextField>
-    )
-  }
-}
-
-export default withStyles(styles)(ObjectTypePreviewPropertyMaterial)
diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyNumber.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyNumber.jsx
deleted file mode 100644
index 659cba4322b41db81266ca05a6f75ea8aa7a3b8c..0000000000000000000000000000000000000000
--- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyNumber.jsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react'
-import TextField from '@material-ui/core/TextField'
-import { withStyles } from '@material-ui/core/styles'
-import logger from '../../../common/logger.js'
-
-const styles = () => ({
-  visible: {
-    opacity: 1
-  },
-  hidden: {
-    opacity: 0.5
-  }
-})
-
-class ObjectTypePreviewPropertyNumber extends React.PureComponent {
-  constructor(props) {
-    super(props)
-  }
-
-  render() {
-    logger.log(logger.DEBUG, 'ObjectTypePreviewPropertyNumber.render')
-
-    const { property, classes } = this.props
-
-    return (
-      <TextField
-        error={property.mandatory}
-        type='number'
-        label={property.label}
-        helperText={property.description}
-        fullWidth={true}
-        InputLabelProps={{
-          shrink: true
-        }}
-        className={property.visible ? classes.visible : classes.hidden}
-        variant='filled'
-      />
-    )
-  }
-}
-
-export default withStyles(styles)(ObjectTypePreviewPropertyNumber)
diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyVarchar.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyVarchar.jsx
deleted file mode 100644
index f60d83024b1c28b2d7ce36d9a7464a71e103c278..0000000000000000000000000000000000000000
--- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyVarchar.jsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react'
-import TextField from '@material-ui/core/TextField'
-import { withStyles } from '@material-ui/core/styles'
-import logger from '../../../common/logger.js'
-
-const styles = () => ({
-  visible: {
-    opacity: 1
-  },
-  hidden: {
-    opacity: 0.5
-  }
-})
-
-class ObjectTypePreviewPropertyVarchar extends React.PureComponent {
-  constructor(props) {
-    super(props)
-  }
-
-  render() {
-    logger.log(logger.DEBUG, 'ObjectTypePreviewPropertyVarchar.render')
-
-    const { property, classes } = this.props
-
-    return (
-      <TextField
-        error={property.mandatory}
-        multiline={property.dataType === 'MULTILINE_VARCHAR'}
-        label={property.label}
-        helperText={property.description}
-        fullWidth={true}
-        InputLabelProps={{
-          shrink: true
-        }}
-        className={property.visible ? classes.visible : classes.hidden}
-        variant='filled'
-      />
-    )
-  }
-}
-
-export default withStyles(styles)(ObjectTypePreviewPropertyVarchar)
diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyVocabulary.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyVocabulary.jsx
deleted file mode 100644
index 5f45d3b4a7a2e68162c727dea44a2010237cfd01..0000000000000000000000000000000000000000
--- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyVocabulary.jsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import React from 'react'
-import TextField from '@material-ui/core/TextField'
-import { withStyles } from '@material-ui/core/styles'
-import { facade, dto } from '../../../services/openbis.js'
-import logger from '../../../common/logger.js'
-
-const styles = () => ({
-  visible: {
-    opacity: 1
-  },
-  hidden: {
-    opacity: 0.5
-  }
-})
-
-class ObjectTypePreviewPropertyVocabulary extends React.PureComponent {
-  constructor(props) {
-    super(props)
-    this.state = {}
-  }
-
-  componentDidMount() {
-    this.load()
-  }
-
-  componentDidUpdate(prevProps) {
-    if (this.props.property.vocabulary !== prevProps.property.vocabulary) {
-      this.load()
-    }
-  }
-
-  load() {
-    if (this.props.property.vocabulary) {
-      let criteria = new dto.VocabularyTermSearchCriteria()
-      let fo = new dto.VocabularyTermFetchOptions()
-
-      criteria
-        .withVocabulary()
-        .withCode()
-        .thatEquals(this.props.property.vocabulary)
-
-      return facade.searchVocabularyTerms(criteria, fo).then(result => {
-        this.setState(() => ({
-          terms: result.objects
-        }))
-      })
-    } else {
-      this.setState(() => ({
-        terms: null
-      }))
-    }
-  }
-
-  render() {
-    logger.log(logger.DEBUG, 'ObjectTypePreviewPropertyVocabulary.render')
-
-    const { property, classes } = this.props
-    const { terms } = this.state
-
-    return (
-      <TextField
-        select
-        error={property.mandatory}
-        SelectProps={{
-          native: true
-        }}
-        InputLabelProps={{
-          shrink: true
-        }}
-        label={property.label}
-        helperText={property.description}
-        fullWidth={true}
-        className={property.visible ? classes.visible : classes.hidden}
-        variant='filled'
-      >
-        {terms &&
-          terms.map(term => (
-            <option key={term.code} value={term.code}>
-              {term.label || term.code}
-            </option>
-          ))}
-        <React.Fragment></React.Fragment>
-      </TextField>
-    )
-  }
-}
-
-export default withStyles(styles)(ObjectTypePreviewPropertyVocabulary)
diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewSection.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewSection.jsx
index 6afa3c11bbb1d0c02ce5e0be3cf41607e3af84e8..d84ead6d773cd610a3745a330b53a178a465a1b6 100644
--- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewSection.jsx
+++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewSection.jsx
@@ -2,15 +2,41 @@ import _ from 'lodash'
 import React from 'react'
 import { Draggable, Droppable } from 'react-beautiful-dnd'
 import { withStyles } from '@material-ui/core/styles'
+import Typography from '@material-ui/core/Typography'
 import logger from '../../../common/logger.js'
 import * as util from '../../../common/util.js'
 
-const styles = () => ({
-  container: {
-    padding: '10px'
+const styles = theme => ({
+  draggable: {
+    width: '100%',
+    marginBottom: theme.spacing(2),
+    backgroundColor: theme.palette.background.paper
+  },
+  droppable: {
+    padding: theme.spacing(2),
+    borderWidth: '2px',
+    borderStyle: 'dashed',
+    borderColor: theme.palette.background.secondary,
+    '&:hover': {
+      borderColor: theme.palette.primary.main
+    }
+  },
+  named: {
+    '& $droppable': {
+      borderStyle: 'solid',
+      borderColor: theme.palette.background.secondary,
+      '&:hover': {
+        borderColor: theme.palette.primary.main
+      }
+    }
   },
   selected: {
-    border: '1px solid red'
+    '& $droppable': {
+      borderColor: theme.palette.secondary.main,
+      '&:hover': {
+        borderColor: theme.palette.secondary.main
+      }
+    }
   }
 })
 
@@ -44,18 +70,25 @@ class ObjectTypePreviewSection extends React.PureComponent {
             {...provided.draggableProps}
             {...provided.dragHandleProps}
             className={util.classNames(
-              classes.container,
+              classes.draggable,
+              name ? classes.named : null,
               selected ? classes.selected : null
             )}
             onClick={this.handleClick}
           >
             <Droppable droppableId={id} type='property'>
               {provided => (
-                <div ref={provided.innerRef} {...provided.droppableProps}>
-                  Section {name}
-                  <div>{children}</div>
-                  {provided.placeholder}
-                </div>
+                <React.Fragment>
+                  <Typography variant='h6'>{name}</Typography>
+                  <div
+                    ref={provided.innerRef}
+                    {...provided.droppableProps}
+                    className={classes.droppable}
+                  >
+                    <div>{children}</div>
+                    {provided.placeholder}
+                  </div>
+                </React.Fragment>
               )}
             </Droppable>
           </div>
diff --git a/openbis_ng_ui/src/js/index.js b/openbis_ng_ui/src/js/index.js
index 031638523f99265a87d5d7fd23def49fcdcc60eb..92361b43b0f2483ec9e290d159046349cc12f0fd 100644
--- a/openbis_ng_ui/src/js/index.js
+++ b/openbis_ng_ui/src/js/index.js
@@ -12,6 +12,9 @@ const theme = createMuiTheme({
     useNextVariants: true
   },
   palette: {
+    grey: {
+      main: '#5d5d5d'
+    },
     primary: {
       main: indigo[700]
     },
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/initialize-master-data.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/initialize-master-data.py
index 4cc88556994c50450e82e53735b63caa07957f2f..1406bea586a53b982e4b516ad3f67b8c636550ca 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/initialize-master-data.py
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/initialize-master-data.py
@@ -23,7 +23,7 @@ import sys
 
 from ch.systemsx.cisd.openbis.generic.server.hotfix import ELNCollectionTypeMigration
 
-ELNCollectionTypeMigration.migrate()
+ELNCollectionTypeMigration.beforeUpgrade()
 
 helper = MasterDataRegistrationHelper(sys.path)
 api = CommonServiceProvider.getApplicationContext().getBean(ApplicationServerApi.INTERNAL_SERVICE_NAME)
@@ -32,6 +32,9 @@ props = CustomASServiceExecutionOptions().withParameter('xls', helper.listXlsByt
     .withParameter('xls_name', 'ELN-LIMS').withParameter('update_mode', 'UPDATE_IF_EXISTS')\
     .withParameter('scripts', helper.getAllScripts())
 result = api.executeCustomASService(sessionToken, CustomASServiceCode("xls-import-api"), props)
+
+ELNCollectionTypeMigration.afterUpgrade()
+
 print("======================== master-data xls ingestion result ========================")
 print(result)
 print("======================== master-data xls ingestion result ========================")
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/custom_widget_migration.sql b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/custom_widget_migration.sql
index ebfaa1ee424c1d2a8e5a982df2e3d2e9d57e0631..ef028f0304e120602e48296337b83d1c5e451b48 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/custom_widget_migration.sql
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/custom_widget_migration.sql
@@ -1 +1,2 @@
-UPDATE property_types SET meta_data = '{ "custom_widget" : "Word Processor" }'::jsonb WHERE id IN (SELECT id FROM property_types WHERE daty_id = (SELECT id FROM data_types WHERE code = 'MULTILINE_VARCHAR'));
\ No newline at end of file
+UPDATE property_types SET meta_data = '{ "custom_widget" : "Word Processor" }'::jsonb WHERE id IN (SELECT id FROM property_types WHERE daty_id = (SELECT id FROM data_types WHERE code = 'MULTILINE_VARCHAR'));
+UPDATE property_types SET meta_data = NULL WHERE id IN (SELECT id FROM property_types WHERE daty_id IN (SELECT id FROM data_types WHERE code NOT IN ('MULTILINE_VARCHAR', 'XML')));
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls
index 09f5c1d3db11b44eb21a8523d9c963ab6a710db2..01598226b0d7d30ba7567a7642944efa899596ed 100644
Binary files a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls and b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls differ
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/etc/config.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/etc/config.js
index 6b8215d9ea593e26998f7914982b68021cbbfaf6..d9bf2469a0f2e7fa3c2ef9c940dac515a0052bb5 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/etc/config.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/etc/config.js
@@ -27,4 +27,8 @@ var onLoadInstanceProfileResorceFunc = function() {
 
 //<PROFILE_PLACEHOLDER>
 loadJSResorce("./etc/InstanceProfile.js", onLoadInstanceProfileResorceFunc);
-//</PROFILE_PLACEHOLDER>
\ No newline at end of file
+//</PROFILE_PLACEHOLDER>
+
+var PLUGINS_CONFIGURATION = {
+    extraPlugins : ["life-sciences", "flow", "microscopy"]
+}
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html
index d13cc2116314d23f6350d2ef18492c97e09547bf..24f2a40f3579c2acc493164265d491d3797270cc 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html
@@ -283,13 +283,13 @@
 	<script type="text/javascript" src="./js/views/ExperimentTable/ExperimentTableModel.js"></script>
 	<script type="text/javascript" src="./js/views/ExperimentTable/ExperimentTableView.js"></script>
 	
-	<script type="text/javascript" src="./js/plugins/ELNLIMSPlugin.js"></script>
-	<script type="text/javascript" src="./js/plugins/GenericTechnology.js"></script>
-	<script type="text/javascript" src="./js/plugins/LifeSciencesTechnology.js"></script>
-	<script type="text/javascript" src="./js/plugins/MicroscopyTechnology.js"></script>
-	<script type="text/javascript" src="./js/plugins/FlowCytometryTechnology.js"></script>
+	<script type="text/javascript" src="./js/config/ELNLIMSPlugin.js"></script>
+	<script type="text/javascript" src="./plugins/generic/plugin.js"></script>
 
 	<script type="text/javascript" src="./js/util/EventUtil.js"></script>
+	<script type="text/javascript" src="./js/test/TestUtil.js"></script>
+	<script type="text/javascript" src="./js/test/UserTests.js"></script>
+	<script type="text/javascript" src="./js/test/AdminTests.js"></script>
 	<script type="text/javascript" src="./js/test/TestProtocol.js"></script>
 
 	<script type="text/javascript">
@@ -373,22 +373,36 @@
 		}
 		var test = queryString.test;
 		if(test == "true") {
-			TestProtocol.startAdmitTests();
+			TestProtocol.startAdminTests();
 		}
 
 		if ($.cookie("suitename") == "testId") {
-			TestProtocol.startTestUserTests();
+			TestProtocol.startUserTests();
 		}
 	}
-	
+
 	$(document).ready(function() {
+		var numPlugins = !PLUGINS_CONFIGURATION?1:1+PLUGINS_CONFIGURATION.extraPlugins.length;
+		var pluginsOnLoad = false;
+
 		var wait = null;
 			wait = function() {
-				var condition = profile !== null;
-				if (!condition){
+				if (!profile) {
 					setTimeout(wait,100);
 				} else {
-					startELNLIMS();
+					if(profile.plugins.length === numPlugins) {
+						startELNLIMS();
+					} else if(!pluginsOnLoad) {
+						if(PLUGINS_CONFIGURATION && PLUGINS_CONFIGURATION.extraPlugins) {
+							for(var pIdx = 0; pIdx < PLUGINS_CONFIGURATION.extraPlugins.length; pIdx++) {
+								loadJSResorce("./plugins/" + PLUGINS_CONFIGURATION.extraPlugins[pIdx] + "/plugin.js");
+							}
+						}
+						pluginsOnLoad = true;
+						setTimeout(wait,100);
+					} else {
+						setTimeout(wait,100);
+					}
 				}
 			}
 			wait();
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/ELNLIMSPlugin.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/ELNLIMSPlugin.js
similarity index 100%
rename from openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/ELNLIMSPlugin.js
rename to openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/ELNLIMSPlugin.js
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/Profile.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/Profile.js
index ae4abd2c14c6877d61a9d2629ced5438dfc07f02..bc6f0493ec6a7faa51755f00738c06a88ac1b85f 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/Profile.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/Profile.js
@@ -140,7 +140,8 @@ $.extend(DefaultProfile.prototype, {
 		
         this.customWidgetSettings = {};
 
-		this.plugins = [new GenericTechnology(), new LifeSciencesTechnology(), new MicroscopyTechnology(), new FlowCytometryTechnology()];
+		this.plugins = [new GenericTechnology()];
+
 		this.sampleFormTop = function($container, model) {
 			for(var i = 0; i < this.plugins.length; i++) {
 				this.plugins[i].sampleFormTop($container, model);
@@ -397,7 +398,7 @@ $.extend(DefaultProfile.prototype, {
 				"experimentTypeCodes" : []
 		}
 		this.hideTypes = {
-				"sampleTypeCodes" : ["GENERAL_ELN_SETTINGS", "STORAGE_POSITION", "STORAGE"],
+				"sampleTypeCodes" : [],
 				"experimentTypeCodes" : []
 		}
 		
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/SettingsManager.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/SettingsManager.js
index 4445b07a03fffc80f9fceca08c12ee706042fbde..fee91797c66186edb6d22a379a65e6bb450ccbee 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/SettingsManager.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/SettingsManager.js
@@ -121,8 +121,14 @@ function SettingsManager(serverFacade) {
 		
 		for (var sampleTypeCode of Object.keys(settings.sampleTypeDefinitionsExtension)) {
 			// sampleTypeDefinitionsExtension gets overwritten with settings if found
-			targetProfile.sampleTypeDefinitionsExtension[sampleTypeCode] = settings.sampleTypeDefinitionsExtension[sampleTypeCode];
-			
+			if(!targetProfile.sampleTypeDefinitionsExtension[sampleTypeCode]) {
+			    targetProfile.sampleTypeDefinitionsExtension[sampleTypeCode] = {};
+			}
+
+			for(var key in settings.sampleTypeDefinitionsExtension[sampleTypeCode]) { // Key by Key, in case there is new Keys not available in the old config
+                targetProfile.sampleTypeDefinitionsExtension[sampleTypeCode][key] = settings.sampleTypeDefinitionsExtension[sampleTypeCode][key];
+            }
+
 			// Remove current profile show config
 			if($.inArray(sampleTypeCode, targetProfile.hideTypes["sampleTypeCodes"]) !== -1) {
 				var indexToRemove = $.inArray(sampleTypeCode, targetProfile.hideTypes["sampleTypeCodes"]);
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js
index cf273afe3bfe7883c1fbe5f6e7775c43a7243742..58d16349d6756ca277b1e2e39887d32da9d2dc19 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js
@@ -370,7 +370,7 @@ function ServerFacade(openbisServer) {
 	//
 	// Research collection export
 	//
-	this.exportRc = function(entities, includeRoot, metadataOnly, submissionUrl, submissionType, userInformation, callbackFunction) {
+	this.exportRc = function(entities, includeRoot, metadataOnly, submissionUrl, submissionType, retentionPeriod, userInformation, callbackFunction) {
 		this.asyncExportRc({
 			"method": "exportAll",
 			"includeRoot": includeRoot,
@@ -378,6 +378,7 @@ function ServerFacade(openbisServer) {
 			"metadataOnly": metadataOnly,
 			"submissionUrl": submissionUrl,
 			"submissionType": submissionType,
+			"retentionPeriod": retentionPeriod,
             "userInformation": userInformation,
 			"originUrl": window.location.origin,
 			"sessionToken": this.openbisServer.getSession(),
@@ -403,6 +404,7 @@ function ServerFacade(openbisServer) {
 				options.withParameter("method", parameters["method"]);
 				options.withParameter("originUrl", parameters["originUrl"]);
 				options.withParameter("submissionType", parameters["submissionType"]);
+				options.withParameter("retentionPeriod", parameters["retentionPeriod"]);
 				options.withParameter("submissionUrl", parameters["submissionUrl"]);
 				options.withParameter("entities", parameters["entities"]);
 				options.withParameter("userId", parameters["userInformation"]["id"]);
@@ -473,6 +475,15 @@ function ServerFacade(openbisServer) {
 			"method": "getSubmissionTypes",
 		}, callbackFunction, "rc-exports-api");
 	};
+
+	//
+	// Gets submission types
+	//
+	this.listSubmissionTypes = function(callbackFunction) {
+		this.customELNApi({
+			"method" : "getSubmissionTypes",
+		}, callbackFunction, "rc-exports-api");
+	};
 	
 	//
 	// Metadata Related Functions
@@ -2406,6 +2417,15 @@ function ServerFacade(openbisServer) {
 	// V3 Functions
 	//
 
+    this.createPermIdStrings = function(count, callbackFunction) {
+        mainController.openbisV3.createPermIdStrings(count)
+        .done(function(result) {
+            callbackFunction(result);
+        }).fail(function(result) {
+            Util.showFailedServerCallError(result);
+        });
+    }
+
 	this.getSpace = function(spaceIdentifier, callbackFunction) {
 		require(["as/dto/space/id/SpacePermId", "as/dto/space/fetchoptions/SpaceFetchOptions"], 
 			function(SpacePermId, SpaceFetchOptions) {
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/AdminTests.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/AdminTests.js
new file mode 100644
index 0000000000000000000000000000000000000000..b9ef1e42f4615e02436a204b9185fbdc3392ff46
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/AdminTests.js
@@ -0,0 +1,137 @@
+var AdminTests = new function() {
+
+    this.startAdminTests = function() {
+        testChain = Promise.resolve();
+                 //1. Login
+        testChain.then(() => TestUtil.login("admin", "a"))
+                 //2. Inventory Space and Sample Types
+                 .then(() => this.inventorySpace())
+                 //3. Settings Form - Enable Sample Types to Show in Drop-downs
+                 .then(() => this.enableBacteriaToShowInDropDowns())
+                 //5. User Manager
+                 .then(() => this.userManager())
+                 .catch(error => { console.log(error) });
+    }
+
+	this.inventorySpace = function() {
+        return new Promise(function executor(resolve, reject) {
+            var e = EventUtil;
+
+            var ids = ["tree",
+                       "LAB_NOTEBOOK",
+                       "INVENTORY",
+                       "MATERIALS",
+                       "_MATERIALS_BACTERIA_BACTERIA_COLLECTION",
+                       "_MATERIALS_CELL_LINES_CELL_LINE_COLLECTION",
+                       "_MATERIALS_FLIES_FLY_COLLECTION",
+                       "_MATERIALS_PLANTS_PLANT_COLLECTION",
+                       "_MATERIALS_PLASMIDS_PLASMID_COLLECTION",
+                       "_MATERIALS_POLYNUCLEOTIDES_OLIGO_COLLECTION",
+                       "_MATERIALS_POLYNUCLEOTIDES_RNA_COLLECTION",
+                       "_MATERIALS_REAGENTS_ANTIBODY_COLLECTION",
+                       "_MATERIALS_REAGENTS_CHEMICAL_COLLECTION",
+                       "_MATERIALS_REAGENTS_ENZYME_COLLECTION",
+                       "_MATERIALS_REAGENTS_MEDIA_COLLECTION",
+                       "_MATERIALS_REAGENTS_SOLUTION_BUFFER_COLLECTION",
+                       "_MATERIALS_YEASTS_YEAST_COLLECTION",
+                       "METHODS",
+                       "_METHODS_PROTOCOLS_GENERAL_PROTOCOLS",
+                       "_METHODS_PROTOCOLS_PCR_PROTOCOLS",
+                       "_METHODS_PROTOCOLS_WESTERN_BLOTTING_PROTOCOLS",
+                       "PUBLICATIONS",
+                       "_PUBLICATIONS_PUBLIC_REPOSITORIES_PUBLICATIONS_COLLECTION",
+                       "STOCK",
+                       "USER_PROFILE",
+                       "SAMPLE_BROWSER",
+                       "VOCABULARY_BROWSER",
+                       "ADVANCED_SEARCH",
+                       "STORAGE_MANAGER",
+                       "USER_MANAGER",
+                       "TRASHCAN",
+                       "SETTINGS"];
+
+            Promise.resolve().then(() => TestUtil.verifyInventory(ids)).then(() => resolve());
+        });
+    }
+
+    this.enableBacteriaToShowInDropDowns = function() {
+        return new Promise(function executor(resolve, reject) {
+            var e = EventUtil;
+
+            testChain = Promise.resolve();
+            testChain.then(() => e.waitForId("SETTINGS"))
+                     .then(() => e.click("SETTINGS"))
+                     .then(() => e.waitForId("settingsDropdown"))
+                     .then(() => e.change("settingsDropdown", "/ELN_SETTINGS/GENERAL_ELN_SETTINGS"))
+                     .then(() => e.waitForId("edit-btn"))
+                     .then(() => e.click("edit-btn"))
+                     // we wait for the save-button, cause page contains settings-section-sample type-BACTERIA
+                     // even when page can't be edit. So we wait when page be reloaded.
+                     .then(() => e.waitForId("save-btn"))
+                     .then(() => e.click("settings-section-sampletype-BACTERIA"))
+                     .then(() => e.waitForId("BACTERIA_show_in_drop_downs"))
+                     .then(() => e.checked("BACTERIA_show_in_drop_downs", true))
+                     .then(() => e.click("save-btn"))
+                     // wait until the save
+                     .then(() => e.waitForId("edit-btn"))
+                     .then(() => resolve())
+                     .catch(() => reject(error));
+        });
+    }
+
+    this.userManager = function() {
+        return new Promise(function executor(resolve, reject) {
+            var e = EventUtil;
+
+            testChain = Promise.resolve();
+            testChain.then(() => e.waitForId("USER_MANAGER"))
+                     .then(() => e.click("USER_MANAGER"))
+                     .then(() => e.waitForId("optionsMenu"))
+                     .then(() => e.click("optionsMenu"))
+                     .then(() => e.waitForId("createUser"))
+                     .then(() => e.click("createUser"))
+                     .then(() => e.waitForId("userId"))
+                     .then(() => e.change("userId", "testId"))
+                     .then(() => e.click("createUserBtn"))
+                     .then(() => AdminTests.createPassword())
+                     .then(() => AdminTests.userExist())
+                     .then(() => TestUtil.setCookies("suitename", "testId"))
+                     .then(() => e.click("logoutBtn"))
+                     .then(() => resolve())
+                     .catch(() => reject(error));
+        });
+    }
+
+    // Sometimes it ask for password. Try to fill it.
+    this.createPassword = function() {
+        return new Promise(function executor(resolve, reject) {
+            var e = EventUtil;
+
+            testChain = Promise.resolve();
+
+            testChain.then(() => e.waitForId("passwordId", true, 2000))
+                     .then(() => e.change("passwordId", "pass", true))
+                     .then(() => e.change("passwordRepeatId", "pass", true))
+                     .then(() => e.click("createUserBtn", true))
+                     .then(() => resolve())
+                     .catch(() => reject(error));
+        });
+    }
+
+    // If the user already exists, we will see an error.
+    // This is not a problem for the script, and we can continue.
+    this.userExist = function() {
+        return new Promise(function executor(resolve, reject) {
+            var e = EventUtil;
+
+            testChain = Promise.resolve();
+
+            testChain.then(() => e.waitForId("jError", true, 2000))
+                     .then(() => e.waitForId("jNotifyDismiss", true, 2000))
+                     .then(() => e.click("jNotifyDismiss", true))
+                     .then(() => e.click("cancelBtn", true))
+                     .then(() => resolve())
+                     .catch(() => reject(error));
+        });
+    }
+}
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestProtocol.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestProtocol.js
index e47c967cb5b47da628eb24ecb02c0eab109546f9..378bdd6848e5a6a05cb905f78a5d7a0329ca309f 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestProtocol.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestProtocol.js
@@ -1,211 +1,14 @@
 var TestProtocol = new function () {
 
-    this.startAdmitTests = function() {
+    this.startAdminTests = function() {
         testChain = Promise.resolve();
-        testChain.then(() => this.login("admin", "a"))
-                 .then(() => this.inventorySpace())
-                 .then(() => this.enableBacteriaToShowInDropDowns())
-                 .then(() => this.userManager())
+        testChain.then(() => AdminTests.startAdminTests())
                  .catch(error => { console.log(error) });
     }
 
-    this.startTestUserTests = function() {
+    this.startUserTests = function() {
         testChain = Promise.resolve();
-        testChain.then(() => this.deleteCookies("suitename"))
-                 .then(() => this.login("testId", "pass"))
-                 .then(() => this.inventorySpaceForTestUser())
+        testChain.then(() => UserTests.startUserTests())
                  .catch(error => { console.log(error) });
     }
-
-    this.login = function(username, password) {
-        return new Promise(function executor(resolve, reject) {
-            var e = EventUtil;
-            testChain = Promise.resolve();
-            testChain.then(() => e.waitForId("username"))
-                     .then(() => e.write("username", username))
-                     .then(() => e.write("password", password))
-                     .then(() => e.click("login-button"))
-                     .then(() => resolve())
-                     .catch(() => reject(error));
-        });
-    }
-
-    this.inventorySpace = function() {
-        return new Promise(function executor(resolve, reject) {
-            var e = EventUtil;
-
-            var ids = ["tree",
-                       "LAB_NOTEBOOK",
-                       "INVENTORY",
-                       "MATERIALS",
-                       "_MATERIALS_BACTERIA_BACTERIA_COLLECTION",
-                       "_MATERIALS_CELL_LINES_CELL_LINE_COLLECTION",
-                       "_MATERIALS_FLIES_FLY_COLLECTION",
-                       "_MATERIALS_PLANTS_PLANT_COLLECTION",
-                       "_MATERIALS_PLASMIDS_PLASMID_COLLECTION",
-                       "_MATERIALS_POLYNUCLEOTIDES_OLIGO_COLLECTION",
-                       "_MATERIALS_POLYNUCLEOTIDES_RNA_COLLECTION",
-                       "_MATERIALS_REAGENTS_ANTIBODY_COLLECTION",
-                       "_MATERIALS_REAGENTS_CHEMICAL_COLLECTION",
-                       "_MATERIALS_REAGENTS_ENZYME_COLLECTION",
-                       "_MATERIALS_REAGENTS_MEDIA_COLLECTION",
-                       "_MATERIALS_REAGENTS_SOLUTION_BUFFER_COLLECTION",
-                       "_MATERIALS_YEASTS_YEAST_COLLECTION",
-                       "METHODS",
-                       "_METHODS_PROTOCOLS_GENERAL_PROTOCOLS",
-                       "_METHODS_PROTOCOLS_PCR_PROTOCOLS",
-                       "_METHODS_PROTOCOLS_WESTERN_BLOTTING_PROTOCOLS",
-                       "PUBLICATIONS",
-                       "_PUBLICATIONS_PUBLIC_REPOSITORIES_PUBLICATIONS_COLLECTION",
-                       "STOCK",
-                       "USER_PROFILE",
-                       "SAMPLE_BROWSER",
-                       "VOCABULARY_BROWSER",
-                       "ADVANCED_SEARCH",
-                       "STORAGE_MANAGER",
-                       "USER_MANAGER",
-                       "TRASHCAN",
-                       "SETTINGS"];
-
-            Promise.resolve().then(() => TestProtocol.verifyInventory(ids)).then(() => resolve());
-        });
-    }
-
-    this.enableBacteriaToShowInDropDowns = function() {
-        return new Promise(function executor(resolve, reject) {
-            var e = EventUtil;
-
-            testChain = Promise.resolve();
-            testChain.then(() => e.waitForId("SETTINGS"))
-                     .then(() => e.click("SETTINGS"))
-                     .then(() => e.waitForId("settingsDropdown"))
-                     .then(() => e.change("settingsDropdown", "/ELN_SETTINGS/GENERAL_ELN_SETTINGS"))
-                     .then(() => e.waitForId("edit-btn"))
-                     .then(() => e.click("edit-btn"))
-                     // we wait for the save-button, cause page contains settings-section-sample type-BACTERIA
-                     // even when page can't be edit. So we wait when page be reloaded.
-                     .then(() => e.waitForId("save-btn"))
-                     .then(() => e.click("settings-section-sampletype-BACTERIA"))
-                     .then(() => e.waitForId("BACTERIA_show_in_drop_downs"))
-                     .then(() => e.checked("BACTERIA_show_in_drop_downs", true))
-                     .then(() => e.click("save-btn"))
-                     // wait until the save
-                     .then(() => e.waitForId("edit-btn"))
-                     .then(() => resolve())
-                     .catch(() => reject(error));
-        });
-    }
-
-    this.userManager = function() {
-        return new Promise(function executor(resolve, reject) {
-            var e = EventUtil;
-
-            testChain = Promise.resolve();
-            testChain.then(() => e.waitForId("USER_MANAGER"))
-                     .then(() => e.click("USER_MANAGER"))
-                     .then(() => e.waitForId("optionsMenu"))
-                     .then(() => e.click("optionsMenu"))
-                     .then(() => e.waitForId("createUser"))
-                     .then(() => e.click("createUser"))
-                     .then(() => e.waitForId("userId"))
-                     .then(() => e.change("userId", "testId"))
-                     .then(() => e.click("createUserBtn"))
-                     .then(() => TestProtocol.createPassword())
-                     .then(() => TestProtocol.userExist())
-                     .then(() => TestProtocol.setCookies("suitename", "testId"))
-                     .then(() => e.click("logoutBtn"))
-                     .then(() => resolve())
-                     .catch(() => reject(error));
-        });
-    }
-
-    this.inventorySpaceForTestUser = function() {
-        return new Promise(function executor(resolve, reject) {
-            var e = EventUtil;
-
-            var ids = ["LAB_NOTEBOOK",
-                       "TESTID",
-                       "MATERIALS",
-                       "_MATERIALS_BACTERIA_BACTERIA_COLLECTION",
-                       "_MATERIALS_CELL_LINES_CELL_LINE_COLLECTION",
-                       "_MATERIALS_FLIES_FLY_COLLECTION",
-                       "_MATERIALS_PLANTS_PLANT_COLLECTION",
-                       "_MATERIALS_PLASMIDS_PLASMID_COLLECTION",
-                       "_MATERIALS_POLYNUCLEOTIDES_OLIGO_COLLECTION",
-                       "_MATERIALS_POLYNUCLEOTIDES_RNA_COLLECTION",
-                       "_MATERIALS_REAGENTS_ANTIBODY_COLLECTION",
-                       "_MATERIALS_REAGENTS_CHEMICAL_COLLECTION",
-                       "_MATERIALS_REAGENTS_ENZYME_COLLECTION",
-                       "_MATERIALS_REAGENTS_MEDIA_COLLECTION",
-                       "_MATERIALS_REAGENTS_SOLUTION_BUFFER_COLLECTION",
-                       "_MATERIALS_YEASTS_YEAST_COLLECTION",
-                       "METHODS",
-                       "_METHODS_PROTOCOLS_GENERAL_PROTOCOLS",
-                       "_METHODS_PROTOCOLS_PCR_PROTOCOLS",
-                       "_METHODS_PROTOCOLS_WESTERN_BLOTTING_PROTOCOLS"];
-
-            Promise.resolve().then(() => TestProtocol.verifyInventory(ids))
-                             .then(() => e.verifyExistence("USER_MANAGER", false))
-                             .then(() => resolve());
-        });
-    }
-
-    this.setCookies = function(key, value) {
-        return new Promise(function executor(resolve, reject) {
-            $.cookie(key, value);
-            resolve();
-        });
-    }
-
-    this.deleteCookies = function(key) {
-        return new Promise(function executor(resolve, reject) {
-            $.removeCookie(key);
-            resolve();
-        });
-    }
-
-    this.verifyInventory  = function(ids) {
-        return new Promise(function executor(resolve, reject) {
-            var e = EventUtil;
-
-            chain = Promise.resolve();
-            for (let i = 0; i < ids.length; i++) {
-                chain = chain.then(() => e.waitForId(ids[i])).catch(error => { reject(error)});
-            }
-            chain.then(() => resolve());
-        });
-    }
-
-    // Sometimes it ask for password. Try to fill it.
-    this.createPassword = function() {
-        return new Promise(function executor(resolve, reject) {
-            var e = EventUtil;
-
-            testChain = Promise.resolve();
-
-            testChain.then(() => e.waitForId("passwordId", true, 2000))
-                     .then(() => e.change("passwordId", "pass", true))
-                     .then(() => e.change("passwordRepeatId", "pass", true))
-                     .then(() => e.click("createUserBtn", true))
-                     .then(() => resolve())
-                     .catch(() => reject(error));
-        });
-    }
-
-    // If the user already exists, we will see an error.
-    // This is not a problem for the script, and we can continue.
-    this.userExist = function() {
-        return new Promise(function executor(resolve, reject) {
-            var e = EventUtil;
-
-            testChain = Promise.resolve();
-
-            testChain.then(() => e.waitForId("jError", true, 2000))
-                     .then(() => e.waitForId("jNotifyDismiss", true, 2000))
-                     .then(() => e.click("jNotifyDismiss", true))
-                     .then(() => e.click("cancelBtn", true))
-                     .then(() => resolve())
-                     .catch(() => reject(error));
-        });
-    }
 }
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestUtil.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestUtil.js
new file mode 100644
index 0000000000000000000000000000000000000000..22d356b17c4252c60b0def1fbb8c06b699c2949b
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestUtil.js
@@ -0,0 +1,41 @@
+var TestUtil = new function() {
+
+	this.login = function(username, password) {
+        return new Promise(function executor(resolve, reject) {
+            var e = EventUtil;
+            testChain = Promise.resolve();
+            testChain.then(() => e.waitForId("username"))
+                     .then(() => e.write("username", username))
+                     .then(() => e.write("password", password))
+                     .then(() => e.click("login-button"))
+                     .then(() => resolve())
+                     .catch(() => reject(error));
+        });
+    }
+
+    this.setCookies = function(key, value) {
+        return new Promise(function executor(resolve, reject) {
+            $.cookie(key, value);
+            resolve();
+        });
+    }
+
+    this.deleteCookies = function(key) {
+        return new Promise(function executor(resolve, reject) {
+            $.removeCookie(key);
+            resolve();
+        });
+    }
+
+    this.verifyInventory  = function(ids) {
+        return new Promise(function executor(resolve, reject) {
+            var e = EventUtil;
+
+            chain = Promise.resolve();
+            for (let i = 0; i < ids.length; i++) {
+                chain = chain.then(() => e.waitForId(ids[i])).catch(error => { reject(error)});
+            }
+            chain.then(() => resolve());
+        });
+    }
+}
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/UserTests.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/UserTests.js
new file mode 100644
index 0000000000000000000000000000000000000000..1a23fac5b3f2122d7925da4b1c359bf73ea3de16
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/UserTests.js
@@ -0,0 +1,56 @@
+var UserTests = new function() {
+
+    this.startUserTests = function() {
+        testChain = Promise.resolve();
+                 //5. User Manager (end of test)
+        testChain.then(() => TestUtil.deleteCookies("suitename"))
+                 .then(() => TestUtil.login("testId", "pass"))
+                 .then(() => this.inventorySpaceForTestUser())
+                 //6. Sample Form - Creation
+                 .then(() => this.creationSampleForm())
+                 .catch(error => { console.log(error) });
+    }
+
+
+    this.inventorySpaceForTestUser = function() {
+        return new Promise(function executor(resolve, reject) {
+            var e = EventUtil;
+
+            var ids = ["LAB_NOTEBOOK",
+                       "TESTID",
+                       "MATERIALS",
+                       "_MATERIALS_BACTERIA_BACTERIA_COLLECTION",
+                       "_MATERIALS_CELL_LINES_CELL_LINE_COLLECTION",
+                       "_MATERIALS_FLIES_FLY_COLLECTION",
+                       "_MATERIALS_PLANTS_PLANT_COLLECTION",
+                       "_MATERIALS_PLASMIDS_PLASMID_COLLECTION",
+                       "_MATERIALS_POLYNUCLEOTIDES_OLIGO_COLLECTION",
+                       "_MATERIALS_POLYNUCLEOTIDES_RNA_COLLECTION",
+                       "_MATERIALS_REAGENTS_ANTIBODY_COLLECTION",
+                       "_MATERIALS_REAGENTS_CHEMICAL_COLLECTION",
+                       "_MATERIALS_REAGENTS_ENZYME_COLLECTION",
+                       "_MATERIALS_REAGENTS_MEDIA_COLLECTION",
+                       "_MATERIALS_REAGENTS_SOLUTION_BUFFER_COLLECTION",
+                       "_MATERIALS_YEASTS_YEAST_COLLECTION",
+                       "METHODS",
+                       "_METHODS_PROTOCOLS_GENERAL_PROTOCOLS",
+                       "_METHODS_PROTOCOLS_PCR_PROTOCOLS",
+                       "_METHODS_PROTOCOLS_WESTERN_BLOTTING_PROTOCOLS"];
+
+            Promise.resolve().then(() => TestUtil.verifyInventory(ids))
+                             .then(() => e.verifyExistence("USER_MANAGER", false))
+                             .then(() => resolve());
+        });
+    }
+
+    this.creationSampleForm = function(key, value) {
+        return new Promise(function executor(resolve, reject) {
+            var e = EventUtil;
+
+            Promise.resolve().then(() => e.waitForId("_MATERIALS_BACTERIA_BACTERIA_COLLECTION"))
+                             .then(() => e.click("_MATERIALS_BACTERIA_BACTERIA_COLLECTION"))
+                             .then(() => resolve());
+        });
+    }
+
+}
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/BarcodeUtil.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/BarcodeUtil.js
index c1cb04b747b1bf9e7d46eb0da22c55e519b639aa..1c588ff7e75307a29b5911656c65606acd738b8d 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/BarcodeUtil.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/BarcodeUtil.js
@@ -1,5 +1,5 @@
 var BarcodeUtil = new function() {
-
+    var MIN_BARCODE_LENGTH = 10;
     var barcodeTimeout = false;
     var barcodeReader = "";
 
@@ -8,16 +8,16 @@ var BarcodeUtil = new function() {
         // permID Format 23 char, 1 hyphen: 20170912112249208-38888
         // UUID Format 36 char, 4 hyphens: 123e4567-e89b-12d3-a456-426655440000
         var rules = {};
-        if(barcodeReader.length === 36) {
-            rules["UUIDv4"] = { type: "Property/Attribute", 	name: "PROP.$BARCODE", operator : "thatEqualsString", value: barcodeReader };
-        } else if(barcodeReader.length > 17) {
-            rules["UUIDv4"] = { type: "Property/Attribute", 	name: "ATTR.PERM_ID", operator : "thatEqualsString", value: barcodeReader };
+
+        if(barcodeReader.length >= MIN_BARCODE_LENGTH) {
+            rules["UUIDv4-1"] = { type: "Property/Attribute", 	name: "PROP.$BARCODE", operator : "thatEqualsString", value: barcodeReader };
+            rules["UUIDv4-2"] = { type: "Property/Attribute", 	name: "ATTR.PERM_ID",  operator : "thatEqualsString", value: barcodeReader };
         }
 
         if(rules) {
             var criteria = {};
             criteria.entityKind = "SAMPLE";
-            criteria.logicalOperator = "AND";
+            criteria.logicalOperator = "OR";
             criteria.rules = rules;
 
             mainController.serverFacade.searchForSamplesAdvanced(criteria, { only : true, withProperties: true },
@@ -87,9 +87,11 @@ var BarcodeUtil = new function() {
         $generateBtn.click(function() {
             views.content.empty();
             var value = parseInt($numberDropdown.val());
-            for(var idx = 0; idx < value; idx++) {
-                _this.addBarcode(views.content, idx, $barcodeTypesDropdown.val());
-            }
+            mainController.serverFacade.createPermIdStrings(value, function(newPermIds) {
+                for(var idx = 0; idx < value; idx++) {
+                    _this.addBarcode(views.content, idx, $barcodeTypesDropdown.val(), newPermIds[idx], newPermIds[idx]);
+                }
+            });
         });
 
         this.preloadLibrary();
@@ -104,11 +106,10 @@ var BarcodeUtil = new function() {
         });
     }
 
-    this.addBarcode = function(content, idx, type) {
-        var uuid = Util.guid();
+    this.addBarcode = function(content, idx, type, text) {
         content.append($('<br>'));
         content.append($('<center>').append($('<canvas>', { id : "barcode-canvas-" + idx, width : 1, height : 1, style : "border:1px solid #fff;visibility:hidden" })));
-        this.generateBarcode("barcode-canvas-" + idx, type, uuid, uuid);
+        this.generateBarcode("barcode-canvas-" + idx, type, text, text);
     }
 
     this.readBarcodeMulti = function(actionLabel, action) {
@@ -122,8 +123,19 @@ var BarcodeUtil = new function() {
         var gatherReaded = function(object) {
             objects.push(object);
             var displayName = "";
-            $readed.append($('<div>').append(object.identifier.identifier));
+            var $container = $('<div>');
+            var $identifier = $('<span>').append(object.identifier.identifier);
+            var $removeBtn = FormUtil.getButtonWithIcon("glyphicon-remove", function() {
+                $container.remove();
+                for(var oIdx = 0; oIdx < objects.length; oIdx++) {
+                    if(objects[oIdx].identifier.identifier === object.identifier.identifier) {
+                        objects.splice(oIdx, 1);
+                    }
+                }
+            });
+            $readed.append($container.append($identifier).append($removeBtn));
         }
+
         var barcodeReaderLocalEventListener = barcodeReaderEventListener(gatherReaded);
         document.addEventListener('keyup', barcodeReaderLocalEventListener);
 
@@ -175,12 +187,12 @@ var BarcodeUtil = new function() {
         });
 
         var $btnAccept = $('<input>', { 'type': 'submit', 'class' : 'btn btn-primary', 'value' : 'Save Barcode' });
-        $btnAccept.prop("disabled",true);
-
+        $btnAccept.prop("disabled",false);
 
         var $barcodeReader = $('<input>', { 'type': 'text', 'placeholder': 'barcode', 'style' : 'min-width: 50%;' });
         $barcodeReader.keyup(function() {
-            if($barcodeReader.val().length === 36) {
+            if($barcodeReader.val().length >= MIN_BARCODE_LENGTH ||
+               $barcodeReader.val().length === 0) {
                 $btnAccept.prop("disabled", false);
             } else {
                 $btnAccept.prop("disabled", true);
@@ -189,20 +201,45 @@ var BarcodeUtil = new function() {
 
         $btnAccept.click(function(event) {
             Util.blockUINoMessage();
-            require([ "as/dto/sample/update/SampleUpdate", "as/dto/sample/id/SamplePermId" ],
-            function(SampleUpdate, SamplePermId) {
-                var sample = new SampleUpdate();
-                sample.setSampleId(new SamplePermId(entity.permId));
-                sample.setProperty("$BARCODE", $barcodeReader.val());
-                mainController.openbisV3.updateSamples([ sample ]).done(function(result) {
-                    Util.unblockUI();
-                    Util.showInfo("Barcode Updated", function() {
-                        mainController.changeView('showViewSamplePageFromPermId', entity.permId);
-                    }, true);
-                }).fail(function(result) {
-                    Util.showFailedServerCallError(result);
+
+            var updateBarcode = function() {
+                require([ "as/dto/sample/update/SampleUpdate", "as/dto/sample/id/SamplePermId" ],
+                    function(SampleUpdate, SamplePermId) {
+                        var sample = new SampleUpdate();
+                        sample.setSampleId(new SamplePermId(entity.permId));
+                        sample.setProperty("$BARCODE", $barcodeReader.val());
+                        mainController.openbisV3.updateSamples([ sample ]).done(function(result) {
+                            Util.unblockUI();
+                            Util.showInfo("Barcode Updated", function() {
+                                mainController.changeView('showViewSamplePageFromPermId', entity.permId);
+                        }, true);
+                    }).fail(function(result) {
+                        Util.showFailedServerCallError(result);
+                    });
                 });
-            });
+            }
+
+            if($barcodeReader.val().length === 0) {
+                updateBarcode();
+            } else {
+                var criteria = {
+			        entityKind : "SAMPLE",
+				    logicalOperator : "OR",
+				    rules : {
+				        "UUIDv4-1": { type: "Property/Attribute", 	name: "PROP.$BARCODE", operator : "thatEqualsString", value: $barcodeReader.val() }
+				    }
+			    };
+                mainController.serverFacade.searchForSamplesAdvanced(criteria, {
+                only : true,
+                withProperties : true
+                }, function(results) {
+                    if(results.objects.length === 0) {
+                        updateBarcode();
+                    } else {
+                        Util.showError("Barcode already in use by " +  results.objects[0].identifier.identifier + " : It will not be assigned.");
+                    }
+                });
+            }
         });
 
         var $btnCancel = $('<input>', { 'type': 'submit', 'class' : 'btn', 'value' : 'Close' });
@@ -212,6 +249,9 @@ var BarcodeUtil = new function() {
 
         $window.append($('<legend>').append("Update Barcode"));
         $window.append($('<br>'));
+        $window.append(FormUtil.getInfoText("A valid barcode need to have " + MIN_BARCODE_LENGTH + " or more characters."));
+        $window.append(FormUtil.getWarningText("An empty barcode will delete the current barcode."));
+        $window.append($('<br>'));
         $window.append($('<center>').append($barcodeReader));
         $window.append($('<br>'));
         $window.append($btnAccept).append('&nbsp;').append($btnCancel);
@@ -294,10 +334,10 @@ var BarcodeUtil = new function() {
                     value : "qrcode",
                     label : "QR Code"
                 },
-//                {
-//                    value : "microqrcode",
-//                    label : "Micro QR Code"
-//                }
+                {
+                    value : "microqrcode",
+                    label : "Micro QR Code"
+                }
             ];
     }
 
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 d62411f7d2f115461c28686c5dda2d8e1bee915d..8880d8124a5a72fa827f92e9fb9a53f39381d194 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
@@ -1233,6 +1233,13 @@ var FormUtil = new function() {
 			.append($("<span>").text(infoText));
 	}
 
+	this.getWarningText = function(infoText) {
+    		return $("<p>")
+    			.append($("<div>", { class : "glyphicon glyphicon-warning-sign" })
+    				.css("margin-right", "3px"))
+    			.append($("<span>").text(infoText));
+    }
+
     //
     // DSS disk space usage dialog
     //
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormController.js
index d5ef0f6b7b0ad91f0d61578278077a66e070ac12..4d6e536903007d92ca685bdc9b2dd0b0b0af8382 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormController.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormController.js
@@ -30,17 +30,20 @@ function DataSetFormController(parentController, mode, entity, dataSet, isMini,
 						if(mode !== FormMode.CREATE) {
 							var datasetPermId = dataSet.code;
 							require([ "as/dto/dataset/id/DataSetPermId", "as/dto/dataset/fetchoptions/DataSetFetchOptions" ],
-									function(DataSetPermId, DataSetFetchOptions) {
-										var ids = [new DataSetPermId(datasetPermId)];
-							            var fetchOptions = new DataSetFetchOptions();
-							            fetchOptions.withLinkedData().withExternalDms();
-							            fetchOptions.withExperiment();
-							            fetchOptions.withSample();
-							            mainController.openbisV3.getDataSets(ids, fetchOptions).done(function(map) {
-							            	_this._dataSetFormModel.v3_dataset = map[datasetPermId];
-							            	_this._dataSetFormModel.linkedData = map[datasetPermId].linkedData;
-							            	_this._dataSetFormView.repaint(views);
-							            });
+								function(DataSetPermId, DataSetFetchOptions) {
+									var ids = [new DataSetPermId(datasetPermId)];
+									var fetchOptions = new DataSetFetchOptions();
+									fetchOptions.withLinkedData().withExternalDms();
+									fetchOptions.withExperiment();
+									fetchOptions.withSample();
+									mainController.openbisV3.getDataSets(ids, fetchOptions).done(function(map) {
+										_this._dataSetFormModel.v3_dataset = map[datasetPermId];
+										_this._dataSetFormModel.linkedData = map[datasetPermId].linkedData;
+										mainController.openbisV3.getRights(ids, null).done(function(rightsByIds) {
+											_this._dataSetFormModel.rights = rightsByIds[datasetPermId];
+											_this._dataSetFormView.repaint(views);
+										});
+									});
 							});
 						} else {
 							_this._dataSetFormView.repaint(views);
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormView.js
index b7ebff7caf5697c5d113ed1ddb4d04bb301bbdcb..e3065cd23f7a0addcc83ba3ea685b5c4fe702d55 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormView.js
@@ -219,6 +219,10 @@ function DataSetFormView(dataSetFormController, dataSetFormModel) {
 		if(!this._dataSetFormModel.isMini) {
 			var $header = views.header;
 			$header.append($title);
+	        var dataSetTypeDefinitionsExtension = profile.dataSetTypeDefinitionsExtension[_this._dataSetFormModel.dataSet.dataSetTypeCode];
+            if(dataSetTypeDefinitionsExtension && dataSetTypeDefinitionsExtension.extraToolbar) {
+                toolbarModel = toolbarModel.concat(dataSetTypeDefinitionsExtension.extraToolbar(_this._dataSetFormModel.mode, _this._dataSetFormModel.dataSet));
+            }
 			$header.append(FormUtil.getToolbar(toolbarModel));
 		}
 		
@@ -803,7 +807,9 @@ function DataSetFormView(dataSetFormController, dataSetFormModel) {
 
 	this._allowedToEdit = function() {
 		var dataSet = this._dataSetFormModel.v3_dataset;
-		return dataSet.frozen == false;
+		var rights = this._dataSetFormModel.rights;
+		var updateAllowed = rights && rights.rights.indexOf("UPDATE") >= 0;
+		return updateAllowed && dataSet.frozen == false;
 	}
 
 	this._allowedToMove = function() {
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormController.js
index ad10d568e22e48c33d98bc4bdbd56310f5856f5a..8109e976e41de632e59fe3e2e4c24e0b317a4caa 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormController.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormController.js
@@ -28,9 +28,12 @@ function ExperimentFormController(mainController, mode, experiment) {
 				var fetchOptions = new ExperimentFetchOptions();
 				fetchOptions.withProject().withSpace();
 				mainController.openbisV3.getExperiments([ id ], fetchOptions).done(function(map) {
-	                _this._experimentFormModel.v3_experiment = map[id];
-	                _this._experimentFormView.repaint(views);
-	            });		
+					_this._experimentFormModel.v3_experiment = map[id];
+					mainController.openbisV3.getRights([ id ], null).done(function(rightsByIds) {
+						_this._experimentFormModel.rights = rightsByIds[id];
+						_this._experimentFormView.repaint(views);
+					});
+				});
 		});
 	}
 	
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 c53b3a01e185d1838f9912a43e27e1f8ab8b04d4..99c98af8f23650988552da95068804f915140e36 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
@@ -575,7 +575,8 @@ function ExperimentFormView(experimentFormController, experimentFormModel) {
 	
 	this._allowedToEdit = function() {
 		var experiment = this._experimentFormModel.v3_experiment;
-		return experiment.frozen == false;
+		var updateAllowed = this._experimentFormModel.rights.rights.indexOf("UPDATE") >= 0;
+		return updateAllowed && experiment.frozen == false;
 	}
 
 	this._allowedToMove = function() {
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js
index df2a9b1fce2acd81bf794a0f7702ad527bde11a2..9dfddc01d5e6f63341579f63301c54db181a180c 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js
@@ -22,7 +22,7 @@ function ResearchCollectionExportController(parentController) {
         researchCollectionExportView.repaint(views);
     };
 
-    this.initialiseSubmissionTypesDropdown = function() {
+    this.initialiseSubmissionTypesDropdown = function(callback) {
         Util.blockUI();
         mainController.serverFacade.listSubmissionTypes(function(error, result) {
             Util.unblockUI();
@@ -44,9 +44,12 @@ function ResearchCollectionExportController(parentController) {
         var _this = this;
         var selectedNodes = $(researchCollectionExportModel.tree).fancytree('getTree').getSelectedNodes();
 
-        var selectedOption = researchCollectionExportView.$submissionTypeDropdown.find(":selected");
-        var submissionUrl = selectedOption.val();
-        var submissionType = selectedOption.text();
+        var selectedStOption = researchCollectionExportView.$submissionTypeDropdown.find(":selected");
+        var submissionUrl = selectedStOption.val();
+        var submissionType = selectedStOption.text();
+
+        var selectedRpOption = researchCollectionExportView.$retentionPeriodDropdown.find(":selected");
+        var retentionPeriod = selectedRpOption.val();
 
         var toExport = [];
         for (var eIdx = 0; eIdx < selectedNodes.length; eIdx++) {
@@ -59,11 +62,13 @@ function ResearchCollectionExportController(parentController) {
         } else if (!this.isValid(toExport)) {
             Util.showInfo('Not only spaces and the root should be selected. It will result in an empty export file.');
         } else if (!submissionUrl) {
-            Util.showInfo('First select submission type.');
+            Util.showInfo('Select submission type.');
+        } else if (!retentionPeriod) {
+            Util.showInfo('Select retention period.');
         } else {
             Util.blockUI();
             this.getUserInformation(function(userInformation) {
-                mainController.serverFacade.exportRc(toExport, true, false, submissionUrl, submissionType, userInformation,
+                mainController.serverFacade.exportRc(toExport, true, false, submissionUrl, submissionType, retentionPeriod, userInformation,
                         function(operationExecutionPermId) {
                             _this.waitForOpExecutionResponse(operationExecutionPermId, function(error, result) {
                                 Util.unblockUI();
@@ -72,11 +77,7 @@ function ResearchCollectionExportController(parentController) {
                                     win.focus();
                                     mainController.refreshView();
                                 } else {
-                                    if (error) {
-                                        Util.showError(error);
-                                    } else {
-                                        Util.showError('Returned result format is not correct.');
-                                    }
+                                    Util.showError(error ? error : 'Returned result format is not correct.');
                                 }
                             });
                         });
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js
index 7099e83d87552bbebcdff0b2788f00a34d18d6e1..b68c871b9c1af0b70fc6ba3d60d8e16c6aba138d 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js
@@ -15,9 +15,34 @@
  */
 
 function ResearchCollectionExportModel() {
-    this.submissionTypes = [];
-
-    this.addSubmissionType = function(submissionType) {
-        submissionTypes.push(submissionType);
-    };
+    this.submissionTypes = [
+        {
+            label: "Data Collection",
+            value: "/swordv2/collection/20.500.11850/30"
+        },
+        {
+            label: "Dataset",
+            value: "/swordv2/collection/20.500.11850/31"
+        },
+        {
+            label: "Image",
+            value: "/swordv2/collection/20.500.11850/32"
+        },
+        {
+            label: "Model",
+            value: "/swordv2/collection/20.500.11850/33"
+        },
+        {
+            label: "Sound",
+            value: "/swordv2/collection/20.500.11850/35"
+        },
+        {
+            label: "Video",
+            value: "/swordv2/collection/20.500.11850/36"
+        },
+        {
+            label: "Other Research Data",
+            value: "/swordv2/collection/20.500.11850/37"
+        }
+    ];
 }
\ 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/ResearchCollectionExport/ResearchCollectionExportView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportView.js
index fa97054bbcc0ef9b17d3cc9d3cdb4c2e1ff6beae..b3ea2436cc3fb85ec879017cfab2e151d6ba0c2a 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportView.js
@@ -16,8 +16,6 @@
 
 function ResearchCollectionExportView(researchCollectionExportController, researchCollectionExportModel) {
     this.repaint = function(views) {
-        researchCollectionExportController.initialiseSubmissionTypesDropdown();
-
         var $header = views.header;
         var $container = views.content;
 
@@ -44,6 +42,7 @@ function ResearchCollectionExportView(researchCollectionExportController, resear
         $container.append($form);
 
         this.paintSubmissionTypeDropdown($container);
+        this.paintRetentionPeriodDropdown($container);
 
         researchCollectionExportModel.tree = TreeUtil.getCompleteTree($tree);
 
@@ -57,17 +56,38 @@ function ResearchCollectionExportView(researchCollectionExportController, resear
 
     this.paintSubmissionTypeDropdown = function($container) {
         this.$submissionTypeDropdown = this.getSubmissionTypeDropdown();
-        var entityTypeDropdownFormGroup = FormUtil.getFieldForComponentWithLabel(this.$submissionTypeDropdown, 'Submission Type', null, true);
-        entityTypeDropdownFormGroup.css('width', '50%');
-        $container.append(entityTypeDropdownFormGroup);
+        var submissionTypeDropdownFormGroup = FormUtil.getFieldForComponentWithLabel(this.$submissionTypeDropdown, 'Submission Type', null, true);
+        submissionTypeDropdownFormGroup.css('width', '50%');
+        $container.append(submissionTypeDropdownFormGroup);
+    };
+
+    this.paintRetentionPeriodDropdown = function($container) {
+        this.$retentionPeriodDropdown = this.getRetentionPeriodDropdown();
+        var retentionPeriodDropdownFormGroup = FormUtil.getFieldForComponentWithLabel(this.$retentionPeriodDropdown, 'Retention Period', null, true);
+        retentionPeriodDropdownFormGroup.css('width', '50%');
+        $container.append(retentionPeriodDropdownFormGroup);
     };
 
     this.getSubmissionTypeDropdown = function() {
         return FormUtil.getDropdown(researchCollectionExportModel.submissionTypes, 'Select a submission type');
     };
 
-    this.refreshSubmissionTypeDropdown = function() {
-        FormUtil.setValuesToComponent(this.$submissionTypeDropdown, researchCollectionExportModel.submissionTypes);
-    }
+    this.getRetentionPeriodDropdown = function() {
+        var values = [
+            {
+                value: '10 years',
+                label: '10 years'
+            },
+            {
+                value: '15 years',
+                label: '15 years'
+            },
+            {
+                value: 'indefinite',
+                label: 'indefinite'
+            }
+        ];
+        return FormUtil.getDropdown(values, 'Select a retention period');
+    };
 
 }
\ 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/SampleForm/SampleFormController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormController.js
index a328c84c9d9bcc087d77d206998f7cda4f25a8ea..f1b2fb066f66a1e19441ee73c40ed9f1533c396d 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
@@ -37,18 +37,21 @@ function SampleFormController(mainController, mode, sample, paginationInfo) {
 					fetchOptions.withParents();
 					fetchOptions.withChildren();
 					mainController.openbisV3.getSamples([ id ], fetchOptions).done(function(map) {
-		                _this._sampleFormModel.v3_sample = map[id];
-		                //
-		                mainController.serverFacade.listDataSetsForSample(_this._sampleFormModel.sample, true, function(datasets) {
-		    				if(!datasets.error) {
-		    					_this._sampleFormModel.datasets = datasets.result;
-		    				}
-		    				
-		    				//Load view
-		    				_this._sampleFormView.repaint(views);
-		    				Util.unblockUI();
-		    			});
-		            });		
+						_this._sampleFormModel.v3_sample = map[id];
+						//
+						mainController.openbisV3.getRights([ id ], null).done(function(rightsByIds) {
+							_this._sampleFormModel.rights = rightsByIds[id];
+							mainController.serverFacade.listDataSetsForSample(_this._sampleFormModel.sample, true, function(datasets) {
+								if(!datasets.error) {
+									_this._sampleFormModel.datasets = datasets.result;
+								}
+								
+								//Load view
+								_this._sampleFormView.repaint(views);
+								Util.unblockUI();
+							});
+						});
+					});
 			});
 		} else {
 //			if(sample.sampleTypeCode === "ORDER") {
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormView.js
index 101c44681eecd3861d7839a7831254c2ff973844..e688f94ca77796037530d7f1655d9e46cc8dc402 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormView.js
@@ -442,6 +442,10 @@ function SampleFormView(sampleFormController, sampleFormModel) {
 		var $header = views.header;
 		
 		$header.append($formTitle);
+		var sampleTypeDefinitionsExtension = profile.sampleTypeDefinitionsExtension[_this._sampleFormModel.sample.sampleTypeCode];
+		if(sampleTypeDefinitionsExtension && sampleTypeDefinitionsExtension.extraToolbar) {
+		    toolbarModel = toolbarModel.concat(sampleTypeDefinitionsExtension.extraToolbar(_this._sampleFormModel.mode, _this._sampleFormModel.sample));
+		}
 		$header.append(FormUtil.getToolbar(toolbarModel));
 		
 		//
@@ -806,7 +810,7 @@ function SampleFormView(sampleFormController, sampleFormModel) {
 					            if(propertyType.dataType === "MULTILINE_VARCHAR") {
 					                $component = FormUtil.activateRichTextProperties($component, changeEvent(propertyType), propertyType);
 					            } else {
-					                alert("Word Processor only works with MULTILINE_VARCHAR data type.");
+					                alert("Word Processor only works with MULTILINE_VARCHAR data type, " + propertyType.code + " is " + propertyType.dataType + ".");
 					            }
 					            break;
 					        case 'Spreadsheet':
@@ -815,7 +819,7 @@ function SampleFormView(sampleFormController, sampleFormModel) {
                                     JExcelEditorManager.createField($jexcelContainer, this._sampleFormModel.mode, propertyType.code, this._sampleFormModel.sample);
                                     $component = $jexcelContainer;
 					            } else {
-					                alert("Spreadsheet only works with XML data type.");
+					                alert("Spreadsheet only works with XML data type, " + propertyType.code + " is " + propertyType.dataType + ".");
 					            }
 					            break;
 					    }
@@ -1269,7 +1273,8 @@ function SampleFormView(sampleFormController, sampleFormModel) {
 	
 	this._allowedToEdit = function() {
 		var sample = this._sampleFormModel.v3_sample;
-		return sample.frozen == false;
+		var updateAllowed = this._sampleFormModel.rights.rights.indexOf("UPDATE") >= 0;
+		return updateAllowed && sample.frozen == false;
 	}
 	
 	this._allowedToMove = function() {
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SettingsForm/SettingsFormController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SettingsForm/SettingsFormController.js
index 787b3cf4ee32ec7eb542cd5b7f0afa72acde69e0..feb9a5b5f2c5dcf1396e57b1c9b7f90fe9d30353 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SettingsForm/SettingsFormController.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SettingsForm/SettingsFormController.js
@@ -46,13 +46,13 @@ function SettingsFormController(mainController, settingsSample, mode) {
                 switch(widget.Widget) {
                     case "Word Processor":
                         if(property.dataType !== "MULTILINE_VARCHAR") {
-                            Util.showUserError("Word Processor only works with MULTILINE_VARCHAR data type.", function() {}, true);
+                            Util.showUserError("Word Processor only works with MULTILINE_VARCHAR data type, " + property.code + " is " + property.dataType + ".", function() {}, true);
                             return;
                         }
                         break;
                     case "Spreadsheet":
                         if(property.dataType !== "XML") {
-                            Util.showUserError("Spreadsheet only works with XML data type.", function() {}, true);
+                            Util.showUserError("Spreadsheet only works with XML data type, " + property.code + " is " + property.dataType + ".", function() {}, true);
                             return;
                         }
                         break;
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 7112bc215d9ce606cb256e6f8b837cfeeb8ad394..65e1b63b9c5c8f5cc94274ca2969f7f743f51492 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
@@ -594,7 +594,9 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
                         for(var sIdx = 0; sIdx < samples.length; sIdx++) {
                             var sample = samples[sIdx];
                             var sampleIsExperiment = sample.type.code.indexOf("EXPERIMENT") > -1;
-                            var sampleTypeOnNav = profile.sampleTypeDefinitionsExtension[sample.type.code] && profile.sampleTypeDefinitionsExtension[sample.type.code]["SHOW_ON_NAV"];
+                            var sampleTypeOnNav = profile.sampleTypeDefinitionsExtension[sample.type.code] &&
+                                                  profile.sampleTypeDefinitionsExtension[sample.type.code]["SHOW_ON_NAV"] &&
+                                                  !profile.sampleTypeDefinitionsExtension[sample.type.code]["SHOW_ON_NAV_FOR_PARENT_TYPES"];
                             if(sampleIsExperiment || sampleTypeOnNav) {
                                 var parentIsExperiment = false;
                                 if(sample.parents) {
@@ -733,24 +735,40 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
                                     }
                                     if(sample.type.code.indexOf("EXPERIMENT") > -1 ||
                                     (profile.sampleTypeDefinitionsExtension[sample.type.code] && profile.sampleTypeDefinitionsExtension[sample.type.code]["SHOW_ON_NAV"])) {
-                                        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(), null);
-                                        var sampleNode = {
-                                            displayName: sampleDisplayName,
-                                            title : sampleLink,
-                                            entityType: "SAMPLE",
-                                            key : sample.getPermId().getPermId(),
-                                            folder : true,
-                                            lazy : true,
-                                            view : "showViewSamplePageFromPermId",
-                                            viewData: sample.getPermId().getPermId(),
-                                            icon : sampleIcon,
-                                            registrationDate: sample.registrationDate
-                                        };
-                                        results.push(sampleNode);
+                                        var parentTypeCode = samples[0].type.code;
+                                        var showOnNavForParentTypes = profile.sampleTypeDefinitionsExtension[sample.type.code]["SHOW_ON_NAV_FOR_PARENT_TYPES"];
+                                        var showSampleOnNav = false;
+                                        if(!showOnNavForParentTypes) {
+                                            showSampleOnNav = true;
+                                        } else {
+                                            for(var ptIdx = 0; ptIdx < showOnNavForParentTypes.length; ptIdx++) {
+                                                if(parentTypeCode === showOnNavForParentTypes[ptIdx]) {
+                                                    showSampleOnNav = true;
+                                                    break;
+                                                }
+                                            }
+                                        }
+
+                                        if(showSampleOnNav) {
+                                            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(), null);
+                                            var sampleNode = {
+                                                displayName: sampleDisplayName,
+                                                title : sampleLink,
+                                                entityType: "SAMPLE",
+                                                key : sample.getPermId().getPermId(),
+                                                folder : true,
+                                                lazy : true,
+                                                view : "showViewSamplePageFromPermId",
+                                                viewData: sample.getPermId().getPermId(),
+                                                icon : sampleIcon,
+                                                registrationDate: sample.registrationDate
+                                            };
+                                            results.push(sampleNode);
+                                        }
                                     }
                                 }
                             }
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/FlowCytometryTechnology.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/flow/plugin.js
similarity index 99%
rename from openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/FlowCytometryTechnology.js
rename to openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/flow/plugin.js
index 2ddf91069a99b6a87aa4067afd49d79b11e7fd72..bc298d1d51318362cf313cdcf5d3025b67ff2ddd 100755
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/FlowCytometryTechnology.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/flow/plugin.js
@@ -751,3 +751,5 @@ $.extend(FlowCytometryTechnology.prototype, ELNLIMSPlugin.prototype, {
     }
 
 });
+
+profile.plugins.push( new FlowCytometryTechnology());
\ No newline at end of file
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/plugins/generic/plugin.js
similarity index 100%
rename from openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/GenericTechnology.js
rename to openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/generic/plugin.js
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/LifeSciencesTechnology.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/life-sciences/plugin.js
similarity index 98%
rename from openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/LifeSciencesTechnology.js
rename to openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/life-sciences/plugin.js
index d064fe15a729ff430e894d1b338292136caaa7e7..845f27618cc9e70760b64ce0a0f61abfd2d0cc28 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/LifeSciencesTechnology.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/life-sciences/plugin.js
@@ -16,6 +16,8 @@ $.extend(LifeSciencesTechnology.prototype, ELNLIMSPlugin.prototype, {
 				"CHEMICAL" : {
 					"SHOW" : false,
 					"ENABLE_STORAGE" : true,
+					"SHOW_ON_NAV": true,
+				    "SHOW_ON_NAV_FOR_PARENT_TYPES": ["EXPERIMENTAL_STEP"]
 				},
 				"ENZYME" : {
 					"SHOW" : false,
@@ -231,4 +233,6 @@ $.extend(LifeSciencesTechnology.prototype, ELNLIMSPlugin.prototype, {
 	dataSetFormBottom : function($container, model) {
 		
 	}
-});
\ No newline at end of file
+});
+
+profile.plugins.push( new LifeSciencesTechnology());
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/MicroscopyTechnology.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/microscopy/plugin.js
similarity index 99%
rename from openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/MicroscopyTechnology.js
rename to openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/microscopy/plugin.js
index 6c5dfe97af5360ab100cd3f55b4ac2af1f306459..eecf50e95286acba71bbb9fded2c6b4ed3bffac9 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/MicroscopyTechnology.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/microscopy/plugin.js
@@ -373,3 +373,5 @@ $.extend(MicroscopyTechnology.prototype, ELNLIMSPlugin.prototype, {
             });
     }
 });
+
+profile.plugins.push( new MicroscopyTechnology());
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py
index 7af095aa70a2bb27e997ae05a1d669464da3fd5c..3dbe4b7b1b820b66dcc97015e4c21ad3aaaa1cd7 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py
@@ -13,11 +13,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+from collections import deque
+
 import jarray
 # To obtain the openBIS URL
 from ch.systemsx.cisd.openbis.dss.generic.server import DataStoreServer;
 from ch.systemsx.cisd.openbis.generic.client.web.client.exception import UserFailureException
-from collections import deque
 # Zip Format
 from java.io import File;
 from java.io import FileInputStream;
@@ -246,7 +247,7 @@ def findEntitiesToExport(params):
             operationLog.info("Found: " + str(results.getTotalCount()) + " files");
             for file in results.getObjects():
                 entityFound = {"type": "FILE", "permId": permId, "path": file.getPath(), "isDirectory": file.isDirectory(),
-                               "length": file.getFileLength()};
+                               "length": file.getFileLength(), "registrationDate": dataset.getRegistrationDate()};
                 addToExportWithoutRepeating(entitiesToExport, entityFound);
     return entitiesToExport
 
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py
index f44630466dd4df1741ea821304dfa691d8dc1b08..5c10f7faf58e32208c74ce88c52612f83bd60510 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py
@@ -17,6 +17,7 @@ import json
 import os
 import traceback
 import xml.etree.ElementTree as ET
+from urlparse import urlsplit
 
 import datetime
 import time
@@ -35,8 +36,8 @@ from org.eclipse.jetty.client.util import BasicAuthentication
 from org.eclipse.jetty.http import HttpMethod
 from org.eclipse.jetty.util.ssl import SslContextFactory
 
-from exportsApi import displayResult, findEntitiesToExport, validateDataSize, getConfigurationProperty, generateFilesInZip, addToZipFile, cleanUp, \
-    generateZipFile, checkResponseStatus
+from exportsApi import displayResult, findEntitiesToExport, validateDataSize, getConfigurationProperty, addToZipFile, generateZipFile, \
+    checkResponseStatus, cleanUp
 
 operationLog = Logger.getLogger(str(LogCategory.OPERATION) + '.rcExports.py')
 
@@ -50,25 +51,11 @@ def process(tr, params, tableBuilder):
     if method == 'exportAll':
         resultUrl = expandAndExport(tr, params)
         displayResult(resultUrl is not None, tableBuilder, '{"url": "' + resultUrl + '"}' if resultUrl is not None else None)
-    elif method == 'getSubmissionTypes':
-        collectionUrls = getSubmissionTypes(tr)
-        displayResult(collectionUrls is not None, tableBuilder, collectionUrls)
 
 
-def getSubmissionTypes(tr):
-    url = getConfigurationProperty(tr, 'service-document-url')
-
-    httpClient = None
-    try:
-        httpClient = authenticateUserJava(url, tr)
-        httpClient.setFollowRedirects(True)
-        httpClient.start()
-
-        collections = fetchServiceDocument(url, httpClient)
-    finally:
-        if httpClient is not None:
-            httpClient.stop()
-    return collections
+def getBaseUrl(url):
+    splitUrl = urlsplit(url)
+    return splitUrl.scheme + '://' + splitUrl.netloc
 
 
 def expandAndExport(tr, params):
@@ -118,7 +105,8 @@ def export(entities, tr, params, userInformation):
 
 
 def sendToDSpace(params, tr, tempZipFileName, tempZipFilePath):
-    depositUrl = str(params.get('submissionUrl'))
+    serviceDocumentUrl = getConfigurationProperty(tr, 'service-document-url')
+    depositUrl = getBaseUrl(serviceDocumentUrl) + str(params.get('submissionUrl'))
 
     headers = {
         'In-Progress': 'true',
@@ -193,9 +181,6 @@ def fetchServiceDocument(url, httpClient):
 def generateExternalZipFile(params, exportDirPath, contentZipFilePath, contentZipFileName, exportZipFileName, userInformation, entities):
     # Generates ZIP file which will go to the research collection server
 
-    originUrl=params.get('originUrl')
-    submissionType = str(params.get('submissionType'))
-
     fileMetadata = [
         {
             'fileName': contentZipFileName,
@@ -211,8 +196,8 @@ def generateExternalZipFile(params, exportDirPath, contentZipFilePath, contentZi
 
         addToZipFile(' ' + contentZipFileName, File(contentZipFilePath), zos)
 
-        generateXML(zipOutputStream=zos, fileMetadata=fileMetadata, exportDirPath=exportDirPath, submissionType=submissionType,
-                    userInformation=userInformation, entities=entities, originUrl=originUrl)
+        generateXML(zipOutputStream=zos, fileMetadata=fileMetadata, exportDirPath=exportDirPath,
+                    userInformation=userInformation, entities=entities, params=params)
     except Exception as e:
         operationLog.error('Exception at: ' + traceback.format_exc())
         operationLog.error('Exception: ' + str(e))
@@ -224,7 +209,10 @@ def generateExternalZipFile(params, exportDirPath, contentZipFilePath, contentZi
             fos.close()
 
 
-def generateXML(zipOutputStream, fileMetadata, exportDirPath, submissionType, userInformation, entities, originUrl):
+def generateXML(zipOutputStream, fileMetadata, exportDirPath, userInformation, entities, params):
+    originUrl=params.get('originUrl')
+    submissionType = str(params.get('submissionType'))
+
     ns = {
         'mets': 'http://www.loc.gov/METS/',
         'xlink': 'http://www.w3.org/1999/xlink',
@@ -296,6 +284,12 @@ def generateXML(zipOutputStream, fileMetadata, exportDirPath, submissionType, us
     publicationDateField.set('qualifier', 'issued')
     publicationDateField.text = datetime.date.today().strftime('%Y-%m-%d')
 
+    openBisApiUrlField = ET.SubElement(dim, ET.QName(dimNS, 'field'))
+    openBisApiUrlField.set('mdschema', 'ethz')
+    openBisApiUrlField.set('element', 'identifier')
+    openBisApiUrlField.set('qualifier', 'openBisApiUrl')
+    openBisApiUrlField.text = originUrl + '/openbis-test/'
+
     elnLimsURLPattern = '/openbis-test/webapp/eln-lims/?menuUniqueId=null&viewName='
 
     for entity in entities:
@@ -337,7 +331,7 @@ def generateXML(zipOutputStream, fileMetadata, exportDirPath, submissionType, us
         fLocat = ET.SubElement(file, ET.QName(metsNS, 'FLocat'))
         fLocat.set('LOCTYPE', 'URL')
         fLocat.set('MIMETYPE', fileMetadatum.get('mimeType'))
-        fLocat.set('RETENTIONPERIOD', '10 years')
+        fLocat.set('RETENTIONPERIOD', params.get('retentionPeriod'))
         fLocat.set(ET.QName(xlinkNS, 'href'), fileMetadatum.get('fileName'))
 
     structMap = ET.SubElement(root, ET.QName(metsNS, 'structMap'))
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/entrypoint.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/entrypoint.py
index ce30b77747f2bcb546070e67ff0759db68dcc6eb..5c17cd7132d3bc10ed4fef8f644d542ba0bad896 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/entrypoint.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/entrypoint.py
@@ -9,7 +9,8 @@ from processors import OpenbisDuplicatesHandler, PropertiesLabelHandler, Duplica
     unify_properties_representation_of, validate_creations
 from search_engines import SearchEngine
 from utils import FileHandler
-from utils.openbis_utils import get_version_name_for, get_metadata_name_for
+from utils.openbis_utils import get_version_name_for, get_metadata_name_for, get_metadata_name_for_existing_element
+from parsers.definition_to_creation.creation_types import CreationTypes, VocabularyTermDefinitionToCreationType
 
 
 def validate_data(xls_byte_arrays, csv_strings, update_mode, xls_name):
@@ -47,7 +48,7 @@ def save_versioning_information(versioning_information, xls_version_filepath):
 def create_versioning_information(all_versioning_information, creations, creations_metadata, update_mode,
                                   xls_version_name):
     if xls_version_name in all_versioning_information:
-        versioning_information = all_versioning_information[xls_version_name]
+        versioning_information = all_versioning_information[xls_version_name].copy()
         for creation_type, creation_collection in creations.items():
             if creation_type in versionable_types:
                 for creation in creation_collection:
@@ -69,6 +70,30 @@ def create_versioning_information(all_versioning_information, creations, creatio
     return versioning_information
 
 
+def checkDataConsistency(existing_elements, all_versioning_information, xls_version_name, creations):
+    #check that data from json exist in DB
+
+    if xls_version_name not in all_versioning_information:
+        return
+
+    versioning_information = all_versioning_information[xls_version_name]
+
+    existing_elements_dict = set()
+
+    for existing_type, elements in existing_elements.items():
+        for element in elements:
+            existing_elements_dict.add(get_metadata_name_for_existing_element(existing_type, element))
+
+    for creation_type, creation_collection in creations.items():
+        if creation_type in versionable_types:
+            for creation in creation_collection:
+                code = get_metadata_name_for(creation_type, creation)
+
+                if code in versioning_information and code not in existing_elements_dict:
+                    raise Exception("xls-import-version-info.json contains creation = '" + code + "' with creation_type = '" + \
+                                    creation_type + "' that is not in the database. Please edit the file or delete it.")
+
+
 def process(context, parameters):
     """
         Excel import AS service.
@@ -109,6 +134,7 @@ def process(context, parameters):
     versioning_information = create_versioning_information(all_versioning_information, creations, creations_metadata,
                                                            update_mode, xls_version_name)
     existing_elements = search_engine.find_all_existing_elements(creations)
+    checkDataConsistency(existing_elements, all_versioning_information, xls_version_name, creations)
     entity_kinds = search_engine.find_existing_entity_kind_definitions_for(creations)
     existing_vocabularies = search_engine.find_all_existing_vocabularies()
     existing_unified_kinds = unify_properties_representation_of(creations, entity_kinds, existing_vocabularies,
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation/creation_parsers.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation/creation_parsers.py
index 49299827ae757d863dfc48a7c3c7dc6ff4c70f2a..35d2998b3614f265de85917a3e66b9f2a82eaad9 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation/creation_parsers.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation/creation_parsers.py
@@ -18,7 +18,7 @@ from ch.ethz.sis.openbis.generic.asapi.v3.dto.project.id import ProjectIdentifie
 from ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id import ExperimentIdentifier
 from java.lang import UnsupportedOperationException
 from ch.systemsx.cisd.common.exceptions import UserFailureException
-from utils.openbis_utils import is_internal_namespace, get_script_name_for
+from utils.openbis_utils import is_internal_namespace, get_script_name_for, upper_case_code
 from .creation_types import PropertyTypeDefinitionToCreationType, VocabularyDefinitionToCreationType, \
     VocabularyTermDefinitionToCreationType, \
     PropertyAssignmentDefinitionToCreationType, SampleTypeDefinitionToCreationType, \
@@ -73,13 +73,12 @@ class PropertyTypeDefinitionToCreationParser(object):
 
         for prop in definition.properties:
             property_type_creation = PropertyTypeCreation()
-            code = prop.get(u'code')
-            code = code.upper() if code is not None else None
+            code = upper_case_code(prop.get(u'code'))
             property_type_creation.code = code
             property_type_creation.label = prop.get(u'property label')
             property_type_creation.description = prop.get(u'description')
             property_type_creation.dataType = DataType.valueOf(prop.get(u'data type'))
-            property_type_creation.internalNameSpace = is_internal_namespace(prop.get(u'code'))
+            property_type_creation.internalNameSpace = is_internal_namespace(upper_case_code(prop.get(u'code')))
             property_type_creation.vocabularyId = VocabularyPermId(prop.get(u'vocabulary code')) if prop.get(
                 u'vocabulary code') is not None else None
             metadata = json.loads(prop.get(u'metadata')) if prop.get(u'metadata') is not None else None
@@ -95,8 +94,7 @@ class PropertyTypeDefinitionToCreationParser(object):
 class VocabularyDefinitionToCreationParser(object):
 
     def parse(self, definition):
-        code = definition.attributes.get(u'code')
-        code = code.upper() if code is not None else None
+        code = upper_case_code(definition.attributes.get(u'code'))
         vocabulary_creation = VocabularyCreation()
         vocabulary_creation.code = code
         vocabulary_creation.internalNameSpace = is_internal_namespace(code)
@@ -111,11 +109,11 @@ class VocabularyDefinitionToCreationParser(object):
 class VocabularyTermDefinitionToCreationParser(object):
 
     def parse(self, definition):
-        vocabulary_code = VocabularyPermId(definition.attributes.get(u'code'))
+        vocabulary_code = VocabularyPermId(upper_case_code(definition.attributes.get(u'code')))
         vocabulary_creations_terms = []
         for prop in definition.properties:
             vocabulary_creation_term = VocabularyTermCreation()
-            vocabulary_creation_term.code = prop.get(u'code')
+            vocabulary_creation_term.code = upper_case_code(prop.get(u'code'))
             vocabulary_creation_term.label = prop.get(u'label')
             vocabulary_creation_term.description = prop.get(u'description')
             vocabulary_creation_term.vocabularyId = vocabulary_code
@@ -130,7 +128,7 @@ class VocabularyTermDefinitionToCreationParser(object):
 class PropertyAssignmentDefinitionToCreationParser(object):
 
     def parse(self, prop):
-        code = prop.get(u'code')
+        code = upper_case_code(prop.get(u'code'))
         property_assignment_creation = PropertyAssignmentCreation()
         is_mandatory = get_boolean_from_string(prop.get(u'mandatory'))
         property_assignment_creation.mandatory = is_mandatory
@@ -151,7 +149,7 @@ class PropertyAssignmentDefinitionToCreationParser(object):
 class SampleTypeDefinitionToCreationParser(object):
 
     def parse(self, definition):
-        code = definition.attributes.get(u'code')
+        code = upper_case_code(definition.attributes.get(u'code'))
         sample_creation = SampleTypeCreation()
         sample_creation.code = code
         should_auto_generate_codes = get_boolean_from_string(definition.attributes.get(u'auto generate codes'))
@@ -181,7 +179,7 @@ class SampleTypeDefinitionToCreationParser(object):
 class ExperimentTypeDefinitionToCreationParser(object):
 
     def parse(self, definition):
-        code = definition.attributes.get(u'code')
+        code = upper_case_code(definition.attributes.get(u'code'))
         experiment_type_creation = ExperimentTypeCreation()
         experiment_type_creation.code = code
         experiment_type_creation.description = definition.attributes.get(u'description')
@@ -208,7 +206,7 @@ class DatasetTypeDefinitionToCreationParser(object):
 
     def parse(self, definition):
         dataset_type_creation = DataSetTypeCreation()
-        code = definition.attributes.get(u'code')
+        code = upper_case_code(definition.attributes.get(u'code'))
         dataset_type_creation.code = code
         dataset_type_creation.description = definition.attributes.get(u'description')
         if u'validation script' in definition.attributes and definition.attributes.get(
@@ -235,9 +233,9 @@ class SpaceDefinitionToCreationParser(object):
         space_creations = []
         for prop in definition.properties:
             space_creation = SpaceCreation()
-            space_creation.code = prop.get(u'code')
+            space_creation.code = upper_case_code(prop.get(u'code'))
             space_creation.description = prop.get(u'description')
-            creation_id = prop.get(u'code')
+            creation_id = upper_case_code(prop.get(u'code'))
             space_creation.creationId = CreationId(creation_id)
             space_creations.append(space_creation)
         return space_creations
@@ -252,10 +250,10 @@ class ProjectDefinitionToCreationParser(object):
         project_creations = []
         for prop in definition.properties:
             project_creation = ProjectCreation()
-            project_creation.code = prop.get(u'code')
+            project_creation.code = upper_case_code(prop.get(u'code'))
             project_creation.description = prop.get(u'description')
             project_creation.spaceId = CreationId(prop.get(u'space'))
-            creation_id = prop.get(u'code')
+            creation_id = upper_case_code(prop.get(u'code'))
             project_creation.creationId = CreationId(creation_id)
             project_creations.append(project_creation)
         return project_creations
@@ -272,9 +270,9 @@ class ExperimentDefinitionToCreationParser(object):
         for experiment_properties in definition.properties:
             experiment_creation = ExperimentCreation()
             experiment_creation.typeId = EntityTypePermId(definition.attributes.get(u'experiment type'))
-            experiment_creation.code = experiment_properties.get(u'code')
+            experiment_creation.code = upper_case_code(experiment_properties.get(u'code'))
             experiment_creation.projectId = ProjectIdentifier(experiment_properties.get(u'project'))
-            creation_id = experiment_properties.get(u'code')
+            creation_id = upper_case_code(experiment_properties.get(u'code'))
             experiment_creation.creationId = CreationId(creation_id)
             for prop, value in experiment_properties.items():
                 if prop not in project_attributes:
@@ -296,8 +294,8 @@ class SampleDefinitionToCreationParser(object):
             sample_creation = SampleCreation()
             sample_creation.typeId = EntityTypePermId(definition.attributes.get(u'sample type'))
             if u'code' in sample_properties and sample_properties.get(u'code') is not None:
-                sample_creation.code = sample_properties.get(u'code')
-                creation_id = sample_properties.get(u'code')
+                sample_creation.code = upper_case_code(sample_properties.get(u'code'))
+                creation_id = upper_case_code(sample_properties.get(u'code'))
                 sample_creation.creationId = CreationId(creation_id)
             if u'$' in sample_properties and sample_properties.get(u'$') is not None:
                 # may overwrite creationId from code, which is intended
@@ -347,7 +345,7 @@ class ScriptDefinitionToCreationParser(object):
         if u'validation script' in definition.attributes:
             validation_script_path = definition.attributes.get(u'validation script')
             if validation_script_path is not None:
-                code = definition.attributes.get(u'code')
+                code = upper_case_code(definition.attributes.get(u'code'))
                 validation_script_creation = PluginCreation()
                 script = self.context.get_script(validation_script_path)
                 validation_script_creation.name = get_script_name_for(code, validation_script_path)
@@ -358,7 +356,7 @@ class ScriptDefinitionToCreationParser(object):
         for prop in definition.properties:
             if u'dynamic script' in prop:
                 dynamic_prop_script_path = prop.get(u'dynamic script')
-                code = prop.get(u'code')
+                code = upper_case_code(prop.get(u'code'))
                 if dynamic_prop_script_path is not None:
                     validation_script_creation = PluginCreation()
                     script = self.context.get_script(dynamic_prop_script_path)
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation_metadata/creation_metadata_parsers.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation_metadata/creation_metadata_parsers.py
index aed19d6a9ad271b8da129da544d1aa9d631ff183..cbb2d3b2e7f7094fb6a1ccfd9878899fedd14877 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation_metadata/creation_metadata_parsers.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation_metadata/creation_metadata_parsers.py
@@ -3,7 +3,7 @@ from ..definition_to_creation import PropertyTypeDefinitionToCreationType, Vocab
     SampleTypeDefinitionToCreationType, ExperimentTypeDefinitionToCreationType, DatasetTypeDefinitionToCreationType, \
     SpaceDefinitionToCreationType, ProjectDefinitionToCreationType, ExperimentDefinitionToCreationType, \
     SampleDefinitionToCreationType, ScriptDefinitionToCreationType
-from utils.openbis_utils import get_metadata_name_for
+from utils.openbis_utils import get_metadata_name_for, upper_case_code
 from ch.systemsx.cisd.common.exceptions import UserFailureException
 from utils.dotdict import dotdict
 
@@ -56,8 +56,7 @@ class PropertyTypeDefinitionToCreationMetadataParser(object):
 
         for prop in definition.properties:
             property_creation_metadata = dotdict()
-            code = prop.get(u'code')
-            code = code.upper() if code is not None else None
+            code = upper_case_code(prop.get(u'code'))
             property_creation_metadata.code = code
             property_creation_metadata.version = get_version(prop.get(u'version', 1))
             creation_metadata[code] = property_creation_metadata
@@ -72,12 +71,12 @@ class VocabularyDefinitionToCreationMetadataParser(object):
     def parse(self, definition):
         creation_metadata = dotdict()
         vocabulary_creation_metadata = dotdict()
-        code = definition.attributes.get(u'code')
+        code = upper_case_code(definition.attributes.get(u'code'))
         vocabulary_creation_metadata.code = code
         vocabulary_creation_metadata.version = get_version(definition.attributes.get(u'version', 1))
         vocabulary_creation_metadata.terms = dotdict()
         for prop in definition.properties:
-            term_code = prop.get(u'code')
+            term_code = upper_case_code(prop.get(u'code'))
             creation_term_metadata = dotdict()
             creation_term_metadata.code = term_code
             creation_term_metadata.version = get_version(prop.get(u'version', 1))
@@ -95,7 +94,7 @@ class SampleTypeDefinitionToCreationMetadataParser(object):
     def parse(self, definition):
         creation_metadata = dotdict()
         sample_type_creation_metadata = dotdict()
-        code = definition.attributes.get(u'code')
+        code = upper_case_code(definition.attributes.get(u'code'))
         sample_type_creation_metadata.code = code
         sample_type_creation_metadata.version = get_version(definition.attributes.get(u'version', 1))
         creation_metadata[code] = sample_type_creation_metadata
@@ -111,7 +110,7 @@ class ExperimentTypeDefinitionToCreationMetadataParser(object):
     def parse(self, definition):
         creation_metadata = dotdict()
         experiment_type_creation_metadata = dotdict()
-        code = definition.attributes.get(u'code')
+        code = upper_case_code(definition.attributes.get(u'code'))
         experiment_type_creation_metadata.code = code
         experiment_type_creation_metadata.version = get_version(definition.attributes.get(u'version', 1))
         creation_metadata[code] = experiment_type_creation_metadata
@@ -127,7 +126,7 @@ class DatasetTypeDefinitionToCreationMetadataParser(object):
     def parse(self, definition):
         creation_metadata = dotdict()
         dataset_type_creation_metadata = dotdict()
-        code = definition.attributes.get(u'code')
+        code = upper_case_code(definition.attributes.get(u'code'))
         dataset_type_creation_metadata.code = code
         dataset_type_creation_metadata.version = get_version(definition.attributes.get(u'version', 1))
         creation_metadata[code] = dataset_type_creation_metadata
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/utils/openbis_utils.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/utils/openbis_utils.py
index e347dc16b43b4241ec13ab3c87fbfc52f0b2965a..bc632d631da35d371f16f0de0cd4bff4ba32c31d 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/utils/openbis_utils.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/utils/openbis_utils.py
@@ -48,3 +48,20 @@ def get_metadata_name_for(creation_type, creation):
         code = "{}-{}".format(creation_type, creation.code)
     code = code.upper()
     return code
+
+
+def get_metadata_name_for_existing_element(existing_type, existing_element):
+    if existing_type == VocabularyTermDefinitionToCreationType:
+        code = str(existing_element.permId).split("(")
+        code = code[1].split(")")
+        code = "{}-{}".format(code[0], existing_element.code)
+    else:
+        code = existing_element.code
+
+    code = "{}-{}".format(existing_type, code)
+    code = code.upper()
+    return code
+
+
+def upper_case_code(code):
+    return code.upper() if code is not None else None
\ No newline at end of file
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java
index 193174e4d5c77a34c4cc2f2c2f21abe1f62f9ecd..82698022b4f2736bbc75317c657ee309f520291d 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java
@@ -15,7 +15,9 @@ import ch.systemsx.cisd.openbis.generic.shared.coreplugin.CorePluginsUtils;
 
 public class AbstractImportTest extends AbstractTransactionalTestNGSpringContextTests {
 
-    private String XLS_VERSIONING_DIR = "xls-import.version-data-file";
+    private static final String VERSIONING_JSON = "./versioning.json";
+
+    private static final String XLS_VERSIONING_DIR = "xls-import.version-data-file";
 
     private static final String TEST_USER = "test";
 
@@ -30,7 +32,7 @@ public class AbstractImportTest extends AbstractTransactionalTestNGSpringContext
 
     @BeforeSuite
     public void setupSuite() {
-        System.setProperty(XLS_VERSIONING_DIR, "./versioning.bin");
+        System.setProperty(XLS_VERSIONING_DIR, VERSIONING_JSON);
         System.setProperty(CorePluginsUtils.CORE_PLUGINS_FOLDER_KEY, "dist/core-plugins");
         System.setProperty(Constants.ENABLED_MODULES_KEY, "xls-import");
         TestInitializer.initEmptyDbNoIndex();
@@ -39,13 +41,11 @@ public class AbstractImportTest extends AbstractTransactionalTestNGSpringContext
     @BeforeMethod
     public void beforeTest() {
         sessionToken = v3api.login(TEST_USER, PASSWORD);
-        System.out.println("AHAHHAHAHAHHA");
-        System.out.println(sessionToken);
     }
 
     @AfterMethod
     public void afterTest() {
-        File f = new File("./versioning.bin");
+        File f = new File(VERSIONING_JSON);
         f.delete();
         v3api.logout(sessionToken);
     }
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportPropertyTypesTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportPropertyTypesTest.java
index 7db73a88f35d6717cc265e4b5ac14f73c75a1f3f..b2bb2ec0ee40133a2fa1adda67ddd4726f14c587 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportPropertyTypesTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportPropertyTypesTest.java
@@ -7,16 +7,16 @@ import static org.testng.Assert.assertTrue;
 
 import java.io.IOException;
 import java.nio.file.Paths;
+import java.util.Arrays;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.delete.PropertyTypeDeletionOptions;
 import org.apache.commons.io.FilenameUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.transaction.annotation.Transactional;
-import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.DataType;
@@ -154,4 +154,18 @@ public class ImportPropertyTypesTest extends AbstractImportTest {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_NON_VOCAB_TYPE_VOCABULARY_CODE)));
     }
 
+    @Test(expectedExceptions = Exception.class)
+    @DirtiesContext
+    public void deleteProjectFromDBButNotFromJSON() throws IOException {
+        // GIVEN
+        TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS)));
+        // WHEN
+        PropertyType type = TestUtils.getPropertyType(v3api, sessionToken, "$INTERNAL_PROP");
+        PropertyTypeDeletionOptions deletionOptions = new PropertyTypeDeletionOptions();
+        deletionOptions.setReason("test");
+        v3api.deletePropertyTypes(sessionToken, Arrays.asList(type.getPermId()), deletionOptions);
+        // THEN
+        TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS)));
+    }
+
 }
diff --git a/pybis/src/python/CHANGELOG.md b/pybis/src/python/CHANGELOG.md
index be9a687de2bbb1af16ee2086db232bcb1f38c897..5ed3891a4524ff8a4327926456d5bb6ca2bcd2f4 100644
--- a/pybis/src/python/CHANGELOG.md
+++ b/pybis/src/python/CHANGELOG.md
@@ -1,3 +1,8 @@
+## Changes with pybis-1.10.1
+
+* fixed a nasty threading bug: open threads are now closed when downloading or uploading datasets
+* this bugfix avoids this RuntimeError: can't start new thread
+
 ## Changes with pybis-1.10.0
 
 * dataSet upload now supports zipfiles
diff --git a/pybis/src/python/pybis/__init__.py b/pybis/src/python/pybis/__init__.py
index 054013efef119a95e9c774b1a7e04f59df677c73..df207bd89b7c849af3505368c59aa3749fe7c077 100644
--- a/pybis/src/python/pybis/__init__.py
+++ b/pybis/src/python/pybis/__init__.py
@@ -1,7 +1,7 @@
 name = 'pybis'
 __author__ = 'Swen Vermeul'
 __email__ = 'swen@ethz.ch'
-__version__ = '1.10.0'
+__version__ = '1.10.1'
 
 from . import pybis
 from .pybis import Openbis
diff --git a/pybis/src/python/pybis/dataset.py b/pybis/src/python/pybis/dataset.py
index 982977b0e67590603e3c0f327d072bd3b5feecda..6e893eb7d11100fe840e16e97cc7ea4b6a51f0b7 100644
--- a/pybis/src/python/pybis/dataset.py
+++ b/pybis/src/python/pybis/dataset.py
@@ -1,7 +1,7 @@
 import os
 from threading import Thread
-from tabulate import tabulate
 from queue import Queue
+from tabulate import tabulate
 from .openbis_object import OpenBisObject 
 from .definitions import openbis_definitions
 from .utils import VERBOSE
@@ -241,23 +241,21 @@ class DataSet(
         """
 
         base_url = self.data['dataStore']['downloadUrl'] + '/datastore_server/' + self.permId + '/'
+        with DataSetDownloadQueue(workers=workers) as queue:
+            # get file list and start download
+            for filename in files:
+                file_info = self.get_file_list(start_folder=filename)
+                file_size = file_info[0]['fileSize']
+                download_url = base_url + filename + '?sessionID=' + self.openbis.token
+                filename_dest = os.path.join(destination, self.permId, filename)
+                queue.put([download_url, filename, filename_dest, file_size, self.openbis.verify_certificates, 'wb'])
 
-        queue = DataSetDownloadQueue(workers=workers)
-
-        # get file list and start download
-        for filename in files:
-            file_info = self.get_file_list(start_folder=filename)
-            file_size = file_info[0]['fileSize']
-            download_url = base_url + filename + '?sessionID=' + self.openbis.token
-            filename_dest = os.path.join(destination, self.permId, filename)
-            queue.put([download_url, filename, filename_dest, file_size, self.openbis.verify_certificates, 'wb'])
+            # wait until all files have downloaded
+            if wait_until_finished:
+                queue.join()
 
-        # wait until all files have downloaded
-        if wait_until_finished:
-            queue.join()
-
-        if VERBOSE: print("Files downloaded to: %s" % os.path.join(destination, self.permId))
-        return destination
+            if VERBOSE: print("Files downloaded to: %s" % os.path.join(destination, self.permId))
+            return destination
 
 
     def _download_link(self, files, destination, wait_until_finished, workers, linked_dataset_fileservice_url, content_copy_index):
@@ -265,42 +263,42 @@ class DataSet(
         Requires the microservice server to be running at the given linked_dataset_fileservice_url.
         """
 
-        queue = DataSetDownloadQueue(workers=workers, collect_files_with_wrong_length=True)
+        with DataSetDownloadQueue(workers=workers, collect_files_with_wrong_length=True) as queue:
 
-        if content_copy_index >= len(self.data["linkedData"]["contentCopies"]):
-            raise ValueError("Content Copy index out of range.")
-        content_copy = self.data["linkedData"]["contentCopies"][content_copy_index]
+            if content_copy_index >= len(self.data["linkedData"]["contentCopies"]):
+                raise ValueError("Content Copy index out of range.")
+            content_copy = self.data["linkedData"]["contentCopies"][content_copy_index]
 
-        for filename in files:
-            file_info = self.get_file_list(start_folder=filename)
-            file_size = file_info[0]['fileSize']
+            for filename in files:
+                file_info = self.get_file_list(start_folder=filename)
+                file_size = file_info[0]['fileSize']
 
-            download_url = linked_dataset_fileservice_url
-            download_url += "?sessionToken=" + self.openbis.token
-            download_url += "&datasetPermId=" + self.data["permId"]["permId"]
-            download_url += "&externalDMSCode=" + content_copy["externalDms"]["code"]
-            download_url += "&contentCopyPath=" + content_copy["path"].replace("/", "%2F")
-            download_url += "&datasetPathToFile=" + urllib.parse.quote(filename)
+                download_url = linked_dataset_fileservice_url
+                download_url += "?sessionToken=" + self.openbis.token
+                download_url += "&datasetPermId=" + self.data["permId"]["permId"]
+                download_url += "&externalDMSCode=" + content_copy["externalDms"]["code"]
+                download_url += "&contentCopyPath=" + content_copy["path"].replace("/", "%2F")
+                download_url += "&datasetPathToFile=" + urllib.parse.quote(filename)
 
-            filename_dest = os.path.join(destination, self.permId, filename)
+                filename_dest = os.path.join(destination, self.permId, filename)
 
-            # continue download if file is not complete - do nothing if it is
-            write_mode = 'wb'
-            if os.path.exists(filename_dest):
-                actual_size = os.path.getsize(filename_dest)
-                if actual_size == int(file_size):
-                    continue
-                elif actual_size < int(file_size):
-                    write_mode = 'ab'
-                    download_url += "&offset=" + str(actual_size)
+                # continue download if file is not complete - do nothing if it is
+                write_mode = 'wb'
+                if os.path.exists(filename_dest):
+                    actual_size = os.path.getsize(filename_dest)
+                    if actual_size == int(file_size):
+                        continue
+                    elif actual_size < int(file_size):
+                        write_mode = 'ab'
+                        download_url += "&offset=" + str(actual_size)
 
-            queue.put([download_url, filename, filename_dest, file_size, self.openbis.verify_certificates, write_mode])
+                queue.put([download_url, filename, filename_dest, file_size, self.openbis.verify_certificates, write_mode])
 
-        if wait_until_finished:
-            queue.join()
+            if wait_until_finished:
+                queue.join()
 
-        if VERBOSE: print("Files downloaded to: %s" % os.path.join(destination, self.permId))
-        return destination, queue.files_with_wrong_length
+            if VERBOSE: print("Files downloaded to: %s" % os.path.join(destination, self.permId))
+            return destination, queue.files_with_wrong_length
 
 
     @property
@@ -584,50 +582,60 @@ class DataSet(
 
 
         # define a queue to handle the upload threads
-        queue = DataSetUploadQueue()
+        with DataSetUploadQueue() as queue:
 
-        real_files = []
-        for filename in files:
-            if os.path.isdir(filename):
-                real_files.extend(
-                    [os.path.join(dp, f) for dp, dn, fn in os.walk(os.path.expanduser(filename)) for f in fn])
-            else:
-                real_files.append(os.path.join(filename))
-
-        # compose the upload-URL and put URL and filename in the upload queue 
-        for filename in real_files:
-            file_in_wsp = os.path.join(folder, os.path.basename(filename))
-            url_filename = os.path.join(folder, urllib.parse.quote(os.path.basename(filename)))
-            self.files_in_wsp.append(file_in_wsp)
-
-            upload_url = (
-                datastore_url + '/datastore_server/session_workspace_file_upload'
-                + '?filename=' + url_filename
-                + '&id=1'
-                + '&startByte=0&endByte=0'
-                + '&sessionID=' + self.openbis.token
-            )
-            queue.put([upload_url, filename, self.openbis.verify_certificates])
+            real_files = []
+            for filename in files:
+                if os.path.isdir(filename):
+                    real_files.extend(
+                        [os.path.join(dp, f) for dp, dn, fn in os.walk(os.path.expanduser(filename)) for f in fn])
+                else:
+                    real_files.append(os.path.join(filename))
+
+            # compose the upload-URL and put URL and filename in the upload queue 
+            for filename in real_files:
+                file_in_wsp = os.path.join(folder, os.path.basename(filename))
+                url_filename = os.path.join(folder, urllib.parse.quote(os.path.basename(filename)))
+                self.files_in_wsp.append(file_in_wsp)
+
+                upload_url = (
+                    datastore_url + '/datastore_server/session_workspace_file_upload'
+                    + '?filename=' + url_filename
+                    + '&id=1'
+                    + '&startByte=0&endByte=0'
+                    + '&sessionID=' + self.openbis.token
+                )
+                queue.put([upload_url, filename, self.openbis.verify_certificates])
 
-        # wait until all files have uploaded
-        if wait_until_finished:
-            queue.join()
+            # wait until all files have uploaded
+            if wait_until_finished:
+                queue.join()
 
-        # return files with full path in session workspace
-        return self.files_in_wsp
+            # return files with full path in session workspace
+            return self.files_in_wsp
 
 
 class DataSetUploadQueue():
     def __init__(self, workers=20):
         # maximum files to be uploaded at once
         self.upload_queue = Queue()
+        self.workers = workers
 
         # define number of threads and start them
         for t in range(workers):
             t = Thread(target=self.upload_file)
-            t.daemon = True
             t.start()
 
+    def __enter__(self, *args, **kwargs):
+        return self
+
+    def __exit__(self, *args, **kwargs):
+        """This method is called at the end of a with statement.
+        """
+        # stop the workers
+        for i in self.workers:
+            self.upload_queue.put(None)
+
     def put(self, things):
         """ expects a list [url, filename] which is put into the upload queue
         """
@@ -636,12 +644,17 @@ class DataSetUploadQueue():
     def join(self):
         """ needs to be called if you want to wait for all uploads to be finished
         """
+        # block until all tasks are done
         self.upload_queue.join()
 
     def upload_file(self):
         while True:
             # get the next item in the queue
-            upload_url, filename, verify_certificates = self.upload_queue.get()
+            queue_item = self.upload_queue.get()
+            if queue_item is None:
+                # when we call the .join() method of the DataSetUploadQueue and empty the queue
+                break
+            upload_url, filename, verify_certificates = queue_item
 
             filesize = os.path.getsize(filename)
 
@@ -713,14 +726,24 @@ class DataSetDownloadQueue():
     def __init__(self, workers=20, collect_files_with_wrong_length=False):
         self.collect_files_with_wrong_length = collect_files_with_wrong_length
         # maximum files to be downloaded at once
+        self.workers = workers
         self.download_queue = Queue()
         self.files_with_wrong_length = []
 
         # define number of threads
-        for t in range(workers):
-            t = Thread(target=self.download_file)
-            t.daemon = True
-            t.start()
+        for i in range(workers):
+            thread = Thread(target=self.download_file)
+            thread.start()
+
+    def __enter__(self, *args, **kwargs):
+        return self
+
+    def __exit__(self, *args, **kwargs):
+        """This method is called at the end of a with statement.
+        """
+        # stop all workers
+        for i in range(self.workers):
+            self.download_queue.put(None)
 
     def put(self, things):
         """ expects a list [url, filename] which is put into the download queue
@@ -735,7 +758,11 @@ class DataSetDownloadQueue():
     def download_file(self):
         while True:
             try:
-                url, filename, filename_dest, file_size, verify_certificates, write_mode = self.download_queue.get()
+                queue_item = self.download_queue.get()
+                if queue_item is None:
+                    # when we call the .join() method of the DataSetDownloadQueue and empty the queue
+                    break
+                url, filename, filename_dest, file_size, verify_certificates, write_mode = queue_item
                 # create the necessary directory structure if they don't exist yet
                 os.makedirs(os.path.dirname(filename_dest), exist_ok=True)
 
diff --git a/pybis/src/python/setup.py b/pybis/src/python/setup.py
index 85bef8b0fb2bf46a2adbfd898e98cbbbfa6764e9..9f870a55dee228d5fab7046c55a908f94880b4e9 100644
--- a/pybis/src/python/setup.py
+++ b/pybis/src/python/setup.py
@@ -11,7 +11,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
 
 setup(
     name='PyBIS',
-    version= '1.10.0',
+    version= '1.10.1',
     author='Swen Vermeul • ID SIS • ETH Zürich',
     author_email='swen@ethz.ch',
     description='openBIS connection and interaction, optimized for using with Jupyter',