diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/ServiceProvider.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/ServiceProvider.java
index 10c3304f8c6632ef60fba8d73cae168e06d42c68..ce8fd97fbac6ae13180a5026ea4403ee32cf33a6 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/ServiceProvider.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/ServiceProvider.java
@@ -138,6 +138,11 @@ public class ServiceProvider
         return ((IApplicationServerApi) getApplicationContext().getBean(V3_APPLICATION_SERVICE_BEAN));
     }
 
+    public static IDataStoreServerApi getV3DataStoreService()
+    {
+        return ((IDataStoreServerApi) getApplicationContext().getBean(DataStoreServerApi.INTERNAL_SERVICE_NAME));
+    }
+
     public static IGeneralInformationService getGeneralInformationService()
     {
         return ((IGeneralInformationService) getApplicationContext().getBean(
diff --git a/integration-tests/TEST_PYTHON_LIBRARIES_LOCALLY b/integration-tests/TEST_PYTHON_LIBRARIES_LOCALLY
new file mode 100644
index 0000000000000000000000000000000000000000..025024e43c90b61c001bfd13aa4bcc7a55f0fd69
--- /dev/null
+++ b/integration-tests/TEST_PYTHON_LIBRARIES_LOCALLY
@@ -0,0 +1,17 @@
+# add the source folder to the PYTHONPATH:
+
+export PYTHONPATH=/Users/vermeul/openbis/integration-tests/source:/Users/vermeul/openbis/integration-tests/sourceTest:$PYTHONPATH
+
+# make sure you test the develop-version of pybis or obis
+
+cd ~/openbis/pybis/src/python
+pip install -e .
+
+# ... and prevent this test-suite to install pybis from some source
+vim openbis/integration-tests/source/test_pybis.py
+
+# on line 34, make sure that
+
+    #self.installPybis()
+
+# is commented out, otherwise your installation is being overwritten
diff --git a/integration-tests/source/systemtest/testcase.py b/integration-tests/source/systemtest/testcase.py
index 1bb35a7792e50c1cec256efb97a6aa25e22ae235..63d1fa64f3c76dd8ce76c490bd36325bdcd7d04f 100644
--- a/integration-tests/source/systemtest/testcase.py
+++ b/integration-tests/source/systemtest/testcase.py
@@ -280,17 +280,20 @@ class TestCase(object):
         return ScreeningTestClient(self, installPath)
 
     def installPybis(self):
-        zipFile = self.artifactRepository.getPathToArtifact(OPENBIS_STANDARD_TECHNOLOGIES_PROJECT, 'pybis-')
-        installPath = "%s/pybis" % self.playgroundFolder
-        util.unzip(zipFile, installPath)
-        util.executeCommand(['pip', 'install', installPath + '/src/python'], "Installation of pybis failed.")
+        #zipFile = self.artifactRepository.getPathToArtifact(OPENBIS_STANDARD_TECHNOLOGIES_PROJECT, 'pybis-')
+        #installPath = "%s/pybis" % self.playgroundFolder
+        #util.unzip(zipFile, installPath)
+        #util.executeCommand(['pip', 'install', installPath + '/src/python'], "Installation of pybis failed.")
+        # install the local pybis in editable-mode (-e)
+        util.executeCommand(['pip', 'install', '-e', '../pybis/src/python'], "Installation of pybis failed.")
 
     def installObis(self):
-        zipFile = self.artifactRepository.getPathToArtifact(OPENBIS_STANDARD_TECHNOLOGIES_PROJECT, 'obis-')
-        installPath = "%s/obis" % self.playgroundFolder
-        util.unzip(zipFile, installPath)
-        print('pip install ' + installPath + '/src/python')
-        util.executeCommand(['pip', 'install', installPath + '/src/python'], "Installation of obis failed.")
+        #zipFile = self.artifactRepository.getPathToArtifact(OPENBIS_STANDARD_TECHNOLOGIES_PROJECT, 'obis-')
+        #installPath = "%s/obis" % self.playgroundFolder
+        #util.unzip(zipFile, installPath)
+        #print('pip install ' + installPath + '/src/python')
+        #util.executeCommand(['pip', 'install', installPath + '/src/python'], "Installation of obis failed.")
+        util.executeCommand(['pip', 'install', '-e', '../obis/src/python'], "Installation of pybis failed.")
 
 
     def getTemplatesFolder(self):
@@ -815,4 +818,4 @@ class OpenbisController(_Controller):
             for line in lines:
                 f.write("%s\n" % line)
 
-        
\ No newline at end of file
+        
diff --git a/integration-tests/test_pybis.py b/integration-tests/test_pybis.py
index 34a6e06daff835b51b97e13083965219f756fcf5..12e38db131349e0ae71299617c2e096ee3b114d2 100755
--- a/integration-tests/test_pybis.py
+++ b/integration-tests/test_pybis.py
@@ -31,7 +31,7 @@ class TestCase(systemtest.testcase.TestCase):
         self.FILE = 'TEST_FILE_' + str(randrange(100000))
 
         self.installOpenbis()
-        #self.installPybis()
+        self.installPybis()
         self.openbisController = self.createOpenbisController()
         self.openbisController.createTestDatabase("openbis")
         self.openbisController.allUp()
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/SingleSignOnServlet.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/SingleSignOnServlet.java
index f370f8c7d41928d8d4bfcb43303f671e0bec0faf..dd45e86850433efcc9ddaddbbc062192444d829e 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/SingleSignOnServlet.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/SingleSignOnServlet.java
@@ -17,6 +17,8 @@
 package ch.ethz.sis.openbis.generic.server;
 
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -108,6 +110,18 @@ public class SingleSignOnServlet extends AbstractServlet
     {
         String sessionId = getHeader(request, SESSION_ID_KEY, DEFAULT_SESSION_ID_KEY);
         String sessionToken = sessionTokenBySessionId.get(sessionId);
+        String returnURL = request.getParameter("return");
+        if (returnURL != null)
+        {
+            handleLogOut(request, response, sessionId, sessionToken, returnURL);
+        } else
+        {
+            handleLogIn(request, response, sessionId, sessionToken);
+        }
+    }
+
+    private void handleLogIn(HttpServletRequest request, HttpServletResponse response, String sessionId, String sessionToken) throws IOException
+    {
         if (sessionToken != null)
         {
             Session session = sessionManager.tryGetSession(sessionToken);
@@ -138,13 +152,40 @@ public class SingleSignOnServlet extends AbstractServlet
         redirectToApp(request, response, sessionToken);
     }
 
-    protected void redirectToApp(HttpServletRequest request, HttpServletResponse response, String sessionToken) throws IOException
+    private void handleLogOut(HttpServletRequest request, HttpServletResponse response, String sessionId, String sessionToken, String returnURL)
+            throws IOException
+    {
+        operationLog.info("log out session id: " + sessionId);
+        if (sessionToken != null)
+        {
+            Session session = sessionManager.tryGetSession(sessionToken);
+            if (session != null)
+            {
+                sessionManager.closeSession(sessionToken);
+                operationLog.info("Session " + sessionToken + " closed.");
+            }
+        }
+        operationLog.info("Redirect to " + returnURL);
+        removeOpenbisCookies(request, response);
+        response.sendRedirect(returnURL);
+    }
+
+    private void redirectToApp(HttpServletRequest request, HttpServletResponse response, String sessionToken) throws IOException
     {
         String host = request.getHeader("X-Forwarded-Host");
         Template template = this.template.createFreshCopy();
         template.bind("host", host);
         String redirectUrl = configurer.getResolvedProps().getProperty(REDIRECT_URL_KEY, template.createText());
         operationLog.info("redirect to " + redirectUrl);
+        removeOpenbisCookies(request, response);
+        Cookie cookie = new Cookie("openbis", sessionToken);
+        cookie.setPath("/");
+        response.addCookie(cookie);
+        response.sendRedirect(redirectUrl);
+    }
+
+    private void removeOpenbisCookies(HttpServletRequest request, HttpServletResponse response)
+    {
         Cookie[] cookies = request.getCookies();
         for (Cookie cookie : cookies)
         {
@@ -156,10 +197,6 @@ public class SingleSignOnServlet extends AbstractServlet
                 response.addCookie(cookie);
             }
         }
-        Cookie cookie = new Cookie("openbis", sessionToken);
-        cookie.setPath("/");
-        response.addCookie(cookie);
-        response.sendRedirect(redirectUrl);
     }
 
     private String getHeader(HttpServletRequest request, String keyProperty, String defaultKey)
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/search/DefaultFullTextIndexer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/search/DefaultFullTextIndexer.java
index 0d1b6ac00d43e9d33828be7ec3f63b20680905df..3060eb00acdbc0899db194ff5b5d9627951c500f 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/search/DefaultFullTextIndexer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/search/DefaultFullTextIndexer.java
@@ -148,6 +148,9 @@ final class DefaultFullTextIndexer implements IFullTextIndexer
                 List<Long> subList = ids.subList(index, nextIndex);
                 final List<T> results =
                         listEntitiesWithRestrictedId(fullTextSession, clazz, subList);
+                if (subList.size() != results.size()) {
+                    operationLog.error(String.format("The system tried to index %d but only found %d on the database.", subList.size(), results.size()));
+                }
                 indexEntities(fullTextSession, results);
                 index = nextIndex;
                 operationLog.info(String.format("%d/%d %ss have been reindexed...", index,
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/BasicConstant.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/BasicConstant.java
index 3416d16c13051a66faa0542a7bdfd38f2f0a08c1..978e14e2ef7b11b458fb26acc9a3fc595ff29e59 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/BasicConstant.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/BasicConstant.java
@@ -93,7 +93,7 @@ public class BasicConstant
     /**
      * Canonical date format pattern used to render dates in GUI in a more readable way.
      */
-    public static final String RENDERED_CANONICAL_DATE_FORMAT_PATTERN = "yyyy-MM-dd HH:mm:ss ZZZ";
+    public static final String RENDERED_CANONICAL_DATE_FORMAT_PATTERN = "yyyy-MM-dd HH:mm:ss Z";
 
     /**
      * Date format which does not include time zone.
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims-life-sciences/1/as/initialize-master-data.py b/openbis_standard_technologies/dist/core-plugins/eln-lims-life-sciences/1/as/initialize-master-data.py
index f682ae31ed44532ac9bccc4a6c47c2d71b803c2c..d76ed8acd11035a28bd2b1b8c72d737f68e93147 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims-life-sciences/1/as/initialize-master-data.py
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims-life-sciences/1/as/initialize-master-data.py
@@ -16,7 +16,6 @@
 # MasterDataRegistrationTransaction Class
 from ch.ethz.sis.openbis.generic.server.asapi.v3 import ApplicationServerApi
 from ch.systemsx.cisd.openbis.generic.server import CommonServiceProvider
-from ch.systemsx.cisd.openbis.generic.server import ComponentNames
 from ch.ethz.sis.openbis.generic.asapi.v3.dto.service.id import CustomASServiceCode
 from ch.ethz.sis.openbis.generic.asapi.v3.dto.service import CustomASServiceExecutionOptions
 from ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl import MasterDataRegistrationHelper
@@ -25,19 +24,10 @@ import sys
 helper = MasterDataRegistrationHelper(sys.path)
 api = CommonServiceProvider.getApplicationContext().getBean(ApplicationServerApi.INTERNAL_SERVICE_NAME)
 sessionToken = api.loginAsSystem()
-props = CustomASServiceExecutionOptions().withParameter('xls', helper.listXlsByteArrays())\
-    .withParameter('xls_name', 'ELN-LIMS-LIFE-SCIENCES').withParameter('update_mode', 'IGNORE_EXISTING')\
+props = CustomASServiceExecutionOptions().withParameter('xls', helper.listXlsByteArrays()) \
+    .withParameter('xls_name', 'ELN-LIMS-LIFE-SCIENCES').withParameter('update_mode', 'IGNORE_EXISTING') \
     .withParameter('scripts', helper.getAllScripts())
-result = api.executeCustomASService(sessionToken, CustomASServiceCode("xls-import-api"), props);
-# Updating MULTILINE_VARCHAR to use "Word Processor" for all Properties
-daoFactory = CommonServiceProvider.getApplicationContext().getBean(ComponentNames.DAO_FACTORY);
-currentSession = daoFactory.getSessionFactory().getCurrentSession();
-SQL = "UPDATE property_types SET meta_data = cast(:meta_data AS jsonb) WHERE id IN (SELECT id FROM property_types WHERE daty_id = (SELECT id FROM data_types WHERE code = 'MULTILINE_VARCHAR'))";
-meta_data = "{ \"custom_widget\" : \"Word Processor\" }";
-sqlQuery = currentSession.createSQLQuery(SQL);
-sqlQuery.setParameter("meta_data", meta_data);
-sqlQuery.executeUpdate();
-#
+result = api.executeCustomASService(sessionToken, CustomASServiceCode("xls-import-api"), props)
 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-life-sciences/1/as/master-data/data-model.xls b/openbis_standard_technologies/dist/core-plugins/eln-lims-life-sciences/1/as/master-data/data-model.xls
index b6725cb9c04a7e5c2b7e09d1e0be48893d3fa778..e2755043112873eaa41408f7a01ab4e152444d93 100644
Binary files a/openbis_standard_technologies/dist/core-plugins/eln-lims-life-sciences/1/as/master-data/data-model.xls and b/openbis_standard_technologies/dist/core-plugins/eln-lims-life-sciences/1/as/master-data/data-model.xls differ
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 4d69110b10b17aa7e664af2f3bc8fc636427f53f..c061f53d70d87bbbb9cf15446655855d2d1bf0ce 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/services/publication-api/publication-api.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/publication-api.py
index b898b2b7b3fb242a979ca33933c46a083d92c1cf..f4e5c0d716dbd1b4bf84461a4a7a6646b2db7184 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/publication-api.py
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/publication-api.py
@@ -89,7 +89,7 @@ def getDefaultDataStoreCode(v3, sessionToken):
 def createPublicationSample(parameters, sessionToken, v3):
     publicationOrganization = parameters.get('publicationOrganization')
     if publicationOrganization is None:
-        raise ValueError('publicationOrganization parameter is None.')
+        publicationOrganization = ''
 
     name = parameters.get('name')
     if name is None:
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 2ed7d10ad30493b50d6f2ef725993e9b5a61454e..aa154b6a24013efa7ce3540fbf74cd1cec3f32bc 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
@@ -258,6 +258,10 @@
 	<script type="text/javascript" src="./js/views/ResearchCollectionExport/ResearchCollectionExportModel.js"></script>
 	<script type="text/javascript" src="./js/views/ResearchCollectionExport/ResearchCollectionExportView.js"></script>
 
+	<script type="text/javascript" src="./js/views/ZenodoExport/ZenodoExportController.js"></script>
+	<script type="text/javascript" src="./js/views/ZenodoExport/ZenodoExportModel.js"></script>
+	<script type="text/javascript" src="./js/views/ZenodoExport/ZenodoExportView.js"></script>
+
 	<script type="text/javascript" src="./js/views/DrawingBoards/DrawingBoardsController.js"></script>
 	<script type="text/javascript" src="./js/views/DrawingBoards/DrawingBoardsModel.js"></script>
 	<script type="text/javascript" src="./js/views/DrawingBoards/DrawingBoardsView.js"></script>
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 b713b303923d04fa508d679c6db1851f4d2ce138..8b1a0d7ebb7f7f357ea1016d155394606e58f0fd 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
@@ -135,6 +135,7 @@ $.extend(DefaultProfile.prototype, {
 //				"ADMIN-BS-MBPR28.D.ETHZ.CH-E96954A7" : "http://localhost:8080/download"
 		}
 		this.singleSignOnUrlTemplate = null;
+		this.singleSignOnUrlLogoutTemplate = null;
 		this.singleSignOnLinkLabel = 'Single Sign On Login';
 		
         this.customWidgetSettings = {};
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js
index 142198f6bab65d4da0dce1cf78171022e97001b4..1294aec2ba78fe8eec0fbf4992a436edea3db223 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js
@@ -389,6 +389,13 @@ function MainController(profile) {
 					newResearchCollectionExportView.init(researchCollectionExportViews);
 					this.currentView = newResearchCollectionExportView;
 					break;
+				case "showZenodoExportPage":
+					document.title = "Zenodo Export Builder";
+					var newZenodoExportView = new ZenodoExportController(this);
+					var zenodoExportViews = this._getNewViewModel(true, true, false);
+					newZenodoExportView.init(zenodoExportViews);
+					this.currentView = newZenodoExportView;
+					break;
 				case "showLabNotebookPage":
 					document.title = "Lab Notebook";
 					var newView = new LabNotebookController(this);
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 10486e4a5bbe79d136de70edc36f315712adf67a..96e277604e4a4ba2387881404142543ed6e52463 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
@@ -396,6 +396,51 @@ function ServerFacade(openbisServer) {
 			});
 	};
 
+    this.exportZenodo = function(entities, includeRoot, metadataOnly, userInformation, callbackFunction) {
+        this.asyncExportZenodo({
+            "method": "exportAll",
+            "includeRoot": includeRoot,
+            "entities": entities,
+            "metadataOnly": metadataOnly,
+            "userInformation": userInformation,
+            "originUrl": window.location.origin,
+            "sessionToken": this.openbisServer.getSession(),
+        }, callbackFunction, "zenodo-exports-api");
+    };
+
+	this.asyncExportZenodo = function(parameters, callbackFunction, serviceId) {
+		require(["as/dto/service/execute/ExecuteAggregationServiceOperation",
+				"as/dto/operation/AsynchronousOperationExecutionOptions", "as/dto/service/id/DssServicePermId",
+				"as/dto/datastore/id/DataStorePermId", "as/dto/service/execute/AggregationServiceExecutionOptions"],
+			function(ExecuteAggregationServiceOperation, AsynchronousOperationExecutionOptions, DssServicePermId, DataStorePermId,
+					 AggregationServiceExecutionOptions) {
+				var dataStoreCode = profile.getDefaultDataStoreCode();
+				var dataStoreId = new DataStorePermId(dataStoreCode);
+				var dssServicePermId = new DssServicePermId(serviceId, dataStoreId);
+				var options = new AggregationServiceExecutionOptions();
+
+				options.withParameter("sessionToken", parameters["sessionToken"]);
+
+				options.withParameter("entities", parameters["entities"]);
+				options.withParameter("includeRoot", parameters["includeRoot"]);
+				options.withParameter("metadataOnly", parameters["metadataOnly"]);
+				options.withParameter("method", parameters["method"]);
+				options.withParameter("originUrl", parameters["originUrl"]);
+				options.withParameter("submissionType", parameters["submissionType"]);
+				options.withParameter("submissionUrl", parameters["submissionUrl"]);
+				options.withParameter("entities", parameters["entities"]);
+				options.withParameter("userId", parameters["userInformation"]["id"]);
+				options.withParameter("userEmail", parameters["userInformation"]["email"]);
+				options.withParameter("userFirstName", parameters["userInformation"]["firstName"]);
+				options.withParameter("userLastName", parameters["userInformation"]["lastName"]);
+
+				var operation = new ExecuteAggregationServiceOperation(dssServicePermId, options);
+				mainController.openbisV3.executeOperations([operation], new AsynchronousOperationExecutionOptions()).done(function(results) {
+					callbackFunction(results.executionId.permId);
+				});
+			});
+	};
+
 	//
 	// Gets submission types
 	//
@@ -1476,33 +1521,70 @@ function ServerFacade(openbisServer) {
 		});
 	}
 
+    this.getResultsWithBrokenEqualsFix = function(hackFixForBrokenEquals, results, operator) {
+        if (!operator) {
+            operator = "AND";
+        }
+
+        if(hackFixForBrokenEquals.length > 0 && results) {
+            var filteredResults = [];
+            var resultsValid = new Array(results.length);
+            for (var vIdx = 0; vIdx < resultsValid.length; vIdx++) {
+                switch(operator) {
+                    case "AND":
+                        resultsValid[vIdx] = true;
+                        break;
+                    case "OR":
+                        resultsValid[vIdx] = false;
+                        break;
+                }
+            }
+
+            for(var rIdx = 0; rIdx < results.length; rIdx++) {
+        	    var result = results[rIdx];
+        	    for(var fIdx = 0; fIdx < hackFixForBrokenEquals.length; fIdx++) {
+        		    if(	result &&
+        		        result.properties &&
+        			    result.properties[hackFixForBrokenEquals[fIdx].propertyCode] === hackFixForBrokenEquals[fIdx].value) {
+        			    switch(operator) {
+                            case "AND":
+                                resultsValid[rIdx] = resultsValid[rIdx] && true;
+                                break;
+                            case "OR":
+                                resultsValid[rIdx] = resultsValid[rIdx] || true;
+                                break;
+                        }
+        		    } else {
+                        switch(operator) {
+                            case "AND":
+                                resultsValid[rIdx] = resultsValid[rIdx] && false;
+                                break;
+                            case "OR":
+                                resultsValid[rIdx] = resultsValid[rIdx] || false;
+                                break;
+                        }
+        	        }
+                }
+            }
+
+            for(var rIdx = 0; rIdx < results.length; rIdx++) {
+        	    if(resultsValid[rIdx]) {
+        	        filteredResults.push(results[rIdx]);
+        	    }
+            }
+
+            results = filteredResults;
+        }
+
+        return results;
+    }
+
 	this.searchForEntityAdvanced = function(advancedSearchCriteria, advancedFetchOptions, callback, criteriaClass, fetchOptionsClass, searchMethodName) {
+		var _this = this;
 		var searchFunction = function(searchCriteria, fetchOptions, hackFixForBrokenEquals) {
 			mainController.openbisV3[searchMethodName](searchCriteria, fetchOptions)
 			.done(function(apiResults) {
-				//
-				// Fix For broken equals PART 2
-				//
-				var results = apiResults.objects;
-				var filteredResults = [];
-				if(hackFixForBrokenEquals.length > 0 && results) {
-					for(var rIdx = 0; rIdx < results.length; rIdx++) {
-						var result = results[rIdx];
-						for(var fIdx = 0; fIdx < hackFixForBrokenEquals.length; fIdx++) {
-							if(	result && 
-								result.properties && 
-								result.properties[hackFixForBrokenEquals[fIdx].propertyCode] === hackFixForBrokenEquals[fIdx].value) {
-								filteredResults.push(result);
-							}
-						}
-					}
-				} else {
-					filteredResults = results;
-				}
-				apiResults.objects = filteredResults;
-				//
-				// Fix For broken equals PART 2 - END
-				//
+				apiResults.objects = _this.getResultsWithBrokenEqualsFix(hackFixForBrokenEquals, apiResults.objects, advancedSearchCriteria.logicalOperator);
 				callback(apiResults);
 			})
 			.fail(function(result) {
@@ -1825,14 +1907,14 @@ function ServerFacade(openbisServer) {
 		var localReference = this;
 		
 		//
-		// Fix For broken equals PART 1
+		// Collection Rules for broken equals Fix
 		// Currently the back-end matches whole words instead doing a standard EQUALS
 		// This fixes some most used cases for the storage system, but other use cases that use subcriterias can fail
 		//
 		var hackFixForBrokenEquals = [];
 		if(sampleCriteria.matchClauses) {
 			for(var cIdx = 0; cIdx < sampleCriteria.matchClauses.length; cIdx++) {
-				if(sampleCriteria.matchClauses[cIdx]["@type"] === "PropertyMatchClause" && 
+				if(sampleCriteria.matchClauses[cIdx]["@type"] === "PropertyMatchClause" &&
 						sampleCriteria.matchClauses[cIdx]["compareMode"] === "EQUALS") {
 					hackFixForBrokenEquals.push({
 						propertyCode : sampleCriteria.matchClauses[cIdx].propertyCode,
@@ -1844,32 +1926,11 @@ function ServerFacade(openbisServer) {
 		//
 		// Fix For broken equals PART 1 - END
 		//
-		
+		var _this = this;
 		this.openbisServer.searchForSamplesWithFetchOptions(sampleCriteria, options, function(data) {
 			var results = localReference.getInitializedSamples(data.result);
-			//
-			// Fix For broken equals PART 2
-			//
-			var filteredResults = [];
-			if(hackFixForBrokenEquals.length > 0 && results) {
-				for(var rIdx = 0; rIdx < results.length; rIdx++) {
-					var result = results[rIdx];
-					for(var fIdx = 0; fIdx < hackFixForBrokenEquals.length; fIdx++) {
-						if(	result && 
-							result.properties && 
-							result.properties[hackFixForBrokenEquals[fIdx].propertyCode] === hackFixForBrokenEquals[fIdx].value) {
-							filteredResults.push(result);
-						}
-					}
-				}
-			} else {
-				filteredResults = results;
-			}
-			//
-			// Fix For broken equals PART 2 - END
-			//
-			
-			callbackFunction(filteredResults);
+			results = _this.getResultsWithBrokenEqualsFix(hackFixForBrokenEquals, results, "AND");
+			callbackFunction(results);
 		});
 	}
 	
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 9658d70d42f076133974dd2075ce5654b55951de..19a34eb8422cac3228815ef88103b4e019eafa83 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
@@ -1176,7 +1176,17 @@ var FormUtil = new function() {
 	}
 
 	this._getDropboxFolderName = function(nameElements, dataSetTypeCode, name) {
-		var folderName = nameElements.join("+");
+		var folderName = "";
+
+		for(var nIdx = 0; nIdx < nameElements.length; nIdx++) {
+		    if(nameElements[nIdx]) {
+		        if(nIdx !== 0) {
+		            folderName += "+";
+		        }
+		        folderName += nameElements[nIdx];
+		    }
+		}
+
 		for (var optionalPart of [dataSetTypeCode, name]) {
 			if (optionalPart) {
 				folderName += "+" + optionalPart;				
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 514b6a15d2dcc03336b6c499ac7ad13528fbe454..df2a9b1fce2acd81bf794a0f7702ad527bde11a2 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(callback) {
+    this.initialiseSubmissionTypesDropdown = function() {
         Util.blockUI();
         mainController.serverFacade.listSubmissionTypes(function(error, result) {
             Util.unblockUI();
@@ -56,6 +56,8 @@ function ResearchCollectionExportController(parentController) {
 
         if (toExport.length === 0) {
             Util.showInfo('First select something to export.');
+        } 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.');
         } else {
@@ -82,6 +84,16 @@ function ResearchCollectionExportController(parentController) {
         }
     };
 
+    this.isValid = function(toExport) {
+        for (var i = 0; i < toExport.length; i++) {
+            var value = toExport[i];
+            if (value.type !== 'ROOT' && value.type !== 'SPACE' || value.expand) {
+                return true;
+            }
+        }
+        return false;
+    };
+
     this.waitForOpExecutionResponse = function(operationExecutionPermIdString, callbackFunction) {
         var _this = this;
         require(["as/dto/operation/id/OperationExecutionPermId",
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 7945f200cb3b4bdf5591f784dbeff1a6335ce1f0..fa97054bbcc0ef9b17d3cc9d3cdb4c2e1ff6beae 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
@@ -15,8 +15,6 @@
  */
 
 function ResearchCollectionExportView(researchCollectionExportController, researchCollectionExportModel) {
-    var exportTreeView = new ExportTreeView(researchCollectionExportController, researchCollectionExportModel);
-
     this.repaint = function(views) {
         researchCollectionExportController.initialiseSubmissionTypesDropdown();
 
@@ -52,7 +50,7 @@ function ResearchCollectionExportView(researchCollectionExportController, resear
         var $formTitle = $('<h2>').append('Research Collection Export Builder');
         $header.append($formTitle);
 
-        var $exportButton = $('<input>', { 'type': 'submit', 'class': 'btn btn-primary', 'value': 'Export Selected',
+        var $exportButton = $('<input>', {'type': 'submit', 'class': 'btn btn-primary', 'value': 'Export Selected',
                 'onClick': '$("form[name=\'rcExportForm\']").submit()'});
         $header.append($exportButton);
     };
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 5f1a59eb228352e90f692b6a428337bb6204f7a8..a4b303cd98b51654a4aa5817221b0ef10342324e 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
@@ -361,8 +361,6 @@ function SampleFormView(sampleFormController, sampleFormModel) {
                                     sample = results.objects[rIdx];
                                 }
                             }
-                            _this._sampleFormModel.sample.properties = sample.properties;
-                            delete _this._sampleFormModel.sample.properties[profile.propertyReplacingCode];
 
                             if(_this._sampleFormModel.views.header) {
                                 _this._sampleFormModel.views.header.empty();
@@ -1245,7 +1243,7 @@ function SampleFormView(sampleFormController, sampleFormModel) {
 	
 	this._allowedToCreateChild = function() {
 		var sample = this._sampleFormModel.v3_sample;
-		return sample.frozenForChildren == false && sample.experiment.frozenForSamples == false;
+		return sample.frozenForChildren == false && (!sample.experiment || sample.experiment.frozenForSamples == false);
 	}
 	
 	this._allowedToEdit = function() {
@@ -1255,21 +1253,21 @@ function SampleFormView(sampleFormController, sampleFormModel) {
 	
 	this._allowedToMove = function() {
 		var sample = this._sampleFormModel.v3_sample;
-		return sample.experiment.frozenForSamples == false;
+		return !sample.experiment || sample.experiment.frozenForSamples == false;
 	}
 	
 	this._allowedToDelete = function() {
 		var sample = this._sampleFormModel.v3_sample;
-		return sample.frozen == false && sample.experiment.frozenForSamples == false;
+		return sample.frozen == false && (!sample.experiment || sample.experiment.frozenForSamples == false);
 	}
 	
 	this._allowedToCopy = function() {
 		var sample = this._sampleFormModel.v3_sample;
-		return sample.experiment.frozenForSamples == false;
+		return !sample.experiment || sample.experiment.frozenForSamples == false;
 	}
 	
 	this._allowedToRegisterDataSet = function() {
 		var sample = this._sampleFormModel.v3_sample;
-		return sample.frozenForDataSets == false && sample.experiment.frozenForDataSets == false;
+		return sample.frozenForDataSets == false && (!sample.experiment || sample.experiment.frozenForDataSets == false);
 	}
 }
\ 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/SettingsForm/SettingsFormController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SettingsForm/SettingsFormController.js
index 78ddfd0bd3f5f1b5219a904641976de2414d1ba3..787b3cf4ee32ec7eb542cd5b7f0afa72acde69e0 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
@@ -39,6 +39,27 @@ function SettingsFormController(mainController, settingsSample, mode) {
 	}
 
 	this.save = function(settings, widgetSettings) {
+	    if(widgetSettings) { // Validate Widget Settings
+            for(var idx = 0; idx < widgetSettings.length; idx++) {
+                var widget = widgetSettings[idx];
+                var property = profile.getPropertyType(widget["Property Type"]);
+                switch(widget.Widget) {
+                    case "Word Processor":
+                        if(property.dataType !== "MULTILINE_VARCHAR") {
+                            Util.showUserError("Word Processor only works with MULTILINE_VARCHAR data type.", function() {}, true);
+                            return;
+                        }
+                        break;
+                    case "Spreadsheet":
+                        if(property.dataType !== "XML") {
+                            Util.showUserError("Spreadsheet only works with XML data type.", function() {}, true);
+                            return;
+                        }
+                        break;
+                }
+            }
+	    }
+
 	    var _this = this;
 	    var onSave = function() {
 	        _this._settingsManager.validateAndsave(_this._settingsFormModel.settingsSample, settings, (function() {
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 03d1f8e2c9581b2f078153158d33a037359c596b..c7f77e5c4cd57151ba75599ade67fbfcd3b60f52 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
@@ -106,9 +106,14 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
         searchElement.css({"margin-right" : "2px"});
         
         var logoutButton = FormUtil.getButtonWithIcon("glyphicon-off", function() {
-            $('body').addClass('bodyLogin');
-            mainController.serverFacade.logout();
-        });
+            $('body').addClass('bodyLogin');
+            var logoutTemplate = mainController.profile.singleSignOnUrlLogoutTemplate;
+            if (logoutTemplate) {
+                window.location = logoutTemplate.replace("${host}", window.location.hostname);
+            } else {
+                mainController.serverFacade.logout();
+            }
+         });
         
         var $searchForm = $("<form>", { "onsubmit": "return false;" })
                             .append(logoutButton)
@@ -203,7 +208,7 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
                 sortField = "displayName";
             }
             // descending order for registrationDate
-            if (sortField == "registrationDate") {
+            if (sortField === "registrationDate") {
                 return naturalSort(resultB[sortField], resultA[sortField]);
             }
             return naturalSort(resultA[sortField], resultB[sortField]);
@@ -226,8 +231,8 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
         }
         
         if(profile.mainMenu.showStock) {
-            var inventoryLink = _this.getLinkForNode("Stock", "STOCK", "showStockPage", null);
-            treeModel.push({ displayName: "Stock", title : inventoryLink, entityType: "STOCK", key : "STOCK", folder : true, lazy : true, view : "showStockPage", icon: "fa fa-shopping-cart" });
+            var stockLink = _this.getLinkForNode("Stock", "STOCK", "showStockPage", null);
+            treeModel.push({ displayName: "Stock", title : stockLink, entityType: "STOCK", key : "STOCK", folder : true, lazy : true, view : "showStockPage", icon: "fa fa-shopping-cart" });
         }
         
         var treeModelUtils = [];
@@ -241,8 +246,8 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
         }
         
         if(profile.mainMenu.showUserProfile && profile.isFileAuthenticationService && profile.isFileAuthenticationUser) {
-            var settingsLink = _this.getLinkForNode("User Profile", "USER_PROFILE", "showUserProfilePage", null);
-            treeModelUtils.push({ title : settingsLink, entityType: "USER_PROFILE", key : "USER_PROFILE", folder : false, lazy : false, view : "showUserProfilePage", icon : "glyphicon glyphicon-user" });
+            var userProfileLink = _this.getLinkForNode("User Profile", "USER_PROFILE", "showUserProfilePage", null);
+            treeModelUtils.push({ title : userProfileLink, entityType: "USER_PROFILE", key : "USER_PROFILE", folder : false, lazy : false, view : "showUserProfilePage", icon : "glyphicon glyphicon-user" });
         }
 
         if(profile.mainMenu.showDrawingBoard) {
@@ -264,19 +269,38 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
             var advancedSearchLink = _this.getLinkForNode("Advanced Search", "ADVANCED_SEARCH", "showAdvancedSearchPage", null);
             treeModelUtils.push({ displayName: "Advanced Search", title : advancedSearchLink, entityType: "ADVANCED_SEARCH", key : "ADVANCED_SEARCH", folder : false, lazy : false, view : "showAdvancedSearchPage", icon : "glyphicon glyphicon-search" });
         }
-        
-        if(profile.mainMenu.showExports) {
-            var exportBuilderLink = _this.getLinkForNode("Export Builder", "EXPORT_BUILDER", "showExportTreePage", null);
-            treeModelUtils.push({ displayName: "Export Builder", title : exportBuilderLink, entityType: "EXPORT_BUILDER", key : "EXPORT_BUILDER",
-                    folder : false, lazy : false, view : "showExportTreePage", icon : "glyphicon glyphicon-export" });
-        }
 
-        if (profile.mainMenu.showResearchCollectionExportBuilder) {
-            var researchCollectionExportBuilderLink = _this.getLinkForNode("Research Collection Export Builder", "RESEARCH_COLLECTION_EXPORT_BUILDER",
-                "showResearchCollectionExportPage", null);
-            treeModelUtils.push({ displayName: "Research Collection Export Builder", title: researchCollectionExportBuilderLink,
-                entityType: "RESEARCH_COLLECTION_EXPORT_BUILDER", key: "RESEARCH_COLLECTION_EXPORT_BUILDER", folder: false, lazy: false,
-                view: "showResearchCollectionExportPage" });
+        if (profile.mainMenu.showExports || profile.mainMenu.showResearchCollectionExportBuilder || profile.mainMenu.showZenodoExportBuilder) {
+            var treeModelExports = [];
+
+            if (profile.mainMenu.showExports) {
+                var exportBuilderLink = _this.getLinkForNode("Export to ZIP", "EXPORT_TO_ZIP", "showExportTreePage", null);
+                treeModelExports.push({
+                    displayName: "Export to ZIP", title: exportBuilderLink, entityType: "EXPORT_TO_ZIP", key: "EXPORT_TO_ZIP",
+                    folder: false, lazy: false, view: "showExportTreePage", icon: "glyphicon glyphicon-export"
+                });
+            }
+
+            if (profile.mainMenu.showResearchCollectionExportBuilder) {
+                var researchCollectionExportBuilderLink = _this.getLinkForNode("Export to Research Collection",
+                        "EXPORT_TO_RESEARCH_COLLECTION", "showResearchCollectionExportPage", null);
+                treeModelExports.push({
+                    displayName: "Export to Research Collection", title: researchCollectionExportBuilderLink,
+                    entityType: "EXPORT_TO_RESEARCH_COLLECTION", key: "EXPORT_TO_RESEARCH_COLLECTION", folder: false, lazy: false,
+                    view: "showResearchCollectionExportPage", icon: "./img/research-collection-icon.png"
+                });
+            }
+
+            if (profile.mainMenu.showZenodoExportBuilder) {
+                var zenodoExportBuilderLink = _this.getLinkForNode("Export to Zenodo", "EXPORT_TO_ZENODO", "showZenodoExportPage", null);
+                treeModelExports.push({
+                    displayName: "Export to Zenodo", title: zenodoExportBuilderLink, entityType: "EXPORT_TO_ZENODO",
+                    key: "EXPORT_TO_ZENODO", folder: false, lazy: false, view: "showZenodoExportPage", icon: "glyphicon glyphicon-export"
+                });
+            }
+
+            treeModelUtils.push({ displayName: "Exports", title: "Exports", entityType: "EXPORTS", key: "EXPORTS", folder: true, lazy: false,
+                    expanded: false, children: treeModelExports, view: "showBlancPage", icon: "glyphicon glyphicon-export" });
         }
         
         if(profile.mainMenu.showStorageManager) {
@@ -299,26 +323,26 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
             treeModelUtils.push({ displayName: "Settings", title : settingsLink, entityType: "SETTINGS", key : "SETTINGS", folder : false, lazy : false, view : "showSettingsPage", icon : "glyphicon glyphicon-cog" });
         }
 
-        treeModel.push({ displayName: "Utilities", title : "Utilities", entityType: "UTILITIES", key : "UTILITIES", folder : true, lazy : false, expanded : true, children : treeModelUtils, icon : "glyphicon glyphicon-wrench" });
+        treeModel.push({ displayName: "Utilities", title : "Utilities", entityType: "UTILITIES", key : "UTILITIES", folder : true, lazy : false, expanded : true, children : treeModelUtils, view: "showBlancPage", icon : "glyphicon glyphicon-wrench" });
         treeModel.push({ displayName: "About", title : "About", entityType: "ABOUT", key : "ABOUT", folder : false, lazy : false, view : "showAbout", icon : "glyphicon glyphicon-info-sign" });
         
         var glyph_opts = {
-                map: {
-                  doc: "glyphicon glyphicon-file",
-                  docOpen: "glyphicon glyphicon-file",
-                  checkbox: "glyphicon glyphicon-unchecked",
-                  checkboxSelected: "glyphicon glyphicon-check",
-                  checkboxUnknown: "glyphicon glyphicon-share",
-                  dragHelper: "glyphicon glyphicon-play",
-                  dropMarker: "glyphicon glyphicon-arrow-right",
-                  error: "glyphicon glyphicon-warning-sign",
-                  expanderClosed: "glyphicon glyphicon-plus-sign",
-                  expanderLazy: "glyphicon glyphicon-plus-sign",  // glyphicon-expand
-                  expanderOpen: "glyphicon glyphicon-minus-sign",  // glyphicon-collapse-down
-                  folder: "glyphicon glyphicon-folder-close",
-                  folderOpen: "glyphicon glyphicon-folder-open",
-                  loading: "glyphicon glyphicon-refresh"
-                }
+            map: {
+                doc: "glyphicon glyphicon-file",
+                docOpen: "glyphicon glyphicon-file",
+                checkbox: "glyphicon glyphicon-unchecked",
+                checkboxSelected: "glyphicon glyphicon-check",
+                checkboxUnknown: "glyphicon glyphicon-share",
+                dragHelper: "glyphicon glyphicon-play",
+                dropMarker: "glyphicon glyphicon-arrow-right",
+                error: "glyphicon glyphicon-warning-sign",
+                expanderClosed: "glyphicon glyphicon-plus-sign",
+                expanderLazy: "glyphicon glyphicon-plus-sign",  // glyphicon-expand
+                expanderOpen: "glyphicon glyphicon-minus-sign",  // glyphicon-collapse-down
+                folder: "glyphicon glyphicon-folder-close",
+                folderOpen: "glyphicon glyphicon-folder-open",
+                loading: "glyphicon glyphicon-refresh"
+            }
         };
         
         var onLazyLoad = function(event, data) {
@@ -381,7 +405,7 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
                         });
                         });
                     });
-            }
+            };
             
             switch(type) {
                 case "LAB_NOTEBOOK":
@@ -450,7 +474,7 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
                                     registrationDate: space.registrationDate,
                                 };
                                 if(!space.getCode().endsWith("STOCK_CATALOG") && !space.getCode().endsWith("STOCK_ORDERS")) {
-                                        results.push(spaceNode);
+                                    results.push(spaceNode);
                                 }
                             }
                         }
@@ -661,24 +685,24 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
                                 });
                                 
                             }
-                        }
+                        };
                         
                         var getCancelResultsFunction = function(dfd) {
-                                return function() {
-                                    dfd.resolve([]);
-                                }
-                        }
+                            return function() {
+                                dfd.resolve([]);
+                            }
+                        };
                         
                         if(samplesToShow.length > 50) {
-                                var toExecute = function() {
-                                    Util.blockUIConfirm("Do you want to show " + samplesWithoutELNParents.length + " " + ELNDictionary.Samples + " on the tree?", 
-                                            getOkResultsFunction(dfd, samplesToShow),
-                                            getCancelResultsFunction(dfd));
-                            }
+                            var toExecute = function() {
+                                Util.blockUIConfirm("Do you want to show " + samplesWithoutELNParents.length + " " + ELNDictionary.Samples + " on the tree?",
+                                        getOkResultsFunction(dfd, samplesToShow),
+                                        getCancelResultsFunction(dfd));
+                            };
                             
-                                setTimeout(toExecute, 1000);
+                            setTimeout(toExecute, 1000);
                         } else {
-                                getOkResultsFunction(dfd, samplesToShow)();
+                            getOkResultsFunction(dfd, samplesToShow)();
                         }
                     });
                     break;
@@ -820,10 +844,9 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
             stock.setExpanded(true);
         }
         
-        setCustomIcon($tree, "RESEARCH_COLLECTION_EXPORT_BUILDER", "./img/research-collection-icon.png");
         setCustomIcon($tree, "JUPYTER_WORKSPACE", "./img/jupyter-icon.png");
         setCustomIcon($tree, "NEW_JUPYTER_NOTEBOOK", "./img/jupyter-icon.png");
-    }
+    };
     
     function setCustomIcon($tree, nodeKey, iconImage) {
         var node = $tree.fancytree("getTree").getNodeByKey(nodeKey);
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportController.js
new file mode 100644
index 0000000000000000000000000000000000000000..34f489234de0f0119b948bb8139ea5a7e76855ff
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportController.js
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+function ZenodoExportController(parentController) {
+    var exportModel = new ZenodoExportModel();
+    var exportView = new ZenodoExportView(this, exportModel);
+
+    this.init = function(views) {
+        exportView.repaint(views);
+    };
+
+    this.exportSelected = function() {
+        var _this = this;
+        var selectedNodes = $(exportModel.tree).fancytree('getTree').getSelectedNodes();
+
+        var toExport = [];
+        for (var eIdx = 0; eIdx < selectedNodes.length; eIdx++) {
+            var node = selectedNodes[eIdx];
+            toExport.push({type: node.data.entityType, permId: node.key, expand: !node.expanded});
+        }
+
+        if (toExport.length === 0) {
+            Util.showInfo('First select something to export.');
+        } 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 {
+            Util.blockUI();
+            this.getUserInformation(function(userInformation) {
+                mainController.serverFacade.exportZenodo(toExport, true, false, userInformation,
+                        function(operationExecutionPermId) {
+                            _this.waitForOpExecutionResponse(operationExecutionPermId, function(error, result) {
+                                Util.unblockUI();
+                                if (result && result.data && result.data.url) {
+                                    var win = window.open(result.data.url, '_blank');
+                                    win.focus();
+                                    mainController.refreshView();
+                                } else {
+                                    if (error) {
+                                        Util.showError(error);
+                                    } else {
+                                        Util.showError('Returned result format is not correct.');
+                                    }
+                                }
+                            });
+                        });
+            });
+        }
+    };
+
+    this.isValid = function(toExport) {
+        for (var i = 0; i < toExport.length; i++) {
+            var value = toExport[i];
+            if (value.type !== 'ROOT' && value.type !== 'SPACE' || value.expand) {
+                return true;
+            }
+        }
+        return false;
+    };
+
+    this.waitForOpExecutionResponse = function(operationExecutionPermIdString, callbackFunction) {
+        var _this = this;
+        require(['as/dto/operation/id/OperationExecutionPermId',
+                'as/dto/operation/fetchoptions/OperationExecutionFetchOptions'],
+            function(OperationExecutionPermId, OperationExecutionFetchOptions) {
+                var operationExecutionPermId = new OperationExecutionPermId(operationExecutionPermIdString);
+                var fetchOptions = new OperationExecutionFetchOptions();
+                var fetchOptionsDetails = fetchOptions.withDetails();
+                fetchOptionsDetails.withResults();
+                fetchOptionsDetails.withError();
+                mainController.openbisV3.getOperationExecutions([operationExecutionPermId], fetchOptions).done(function(results) {
+                    var result = results[operationExecutionPermIdString];
+                    var v2Result = null;
+                    if (result && result.details && result.details.results) {
+                        v2Result = result.details.results[0];
+                    }
+
+                    if (result && result.state === 'FINISHED') {
+                        mainController.serverFacade.customELNApiCallbackHandler(v2Result, callbackFunction);
+                    } else if (!result || result.state === 'FAILED') {
+                        mainController.serverFacade.customELNApiCallbackHandler(v2Result, callbackFunction);
+                    } else {
+                        setTimeout(function() {
+                            _this.waitForOpExecutionResponse(operationExecutionPermIdString, callbackFunction);
+                        }, 3000);
+                    }
+                });
+            });
+    };
+
+    this.getUserInformation = function(callback) {
+        var userId = mainController.serverFacade.getUserId();
+        mainController.serverFacade.getSessionInformation(function(sessionInfo) {
+            var userInformation = {
+                id: userId,
+            };
+            callback(userInformation);
+        });
+    };
+}
\ 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/ZenodoExport/ZenodoExportModel.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportModel.js
new file mode 100644
index 0000000000000000000000000000000000000000..f555844b57e82e8c640965767f2077b1b66f213e
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportModel.js
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+function ZenodoExportModel() {
+}
\ 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/ZenodoExport/ZenodoExportView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportView.js
new file mode 100644
index 0000000000000000000000000000000000000000..775a8893be6bbaed639dd4409ab6e73e14e56b43
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportView.js
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+function ZenodoExportView(exportController, exportModel) {
+    this.repaint = function(views) {
+        var $header = views.header;
+        var $container = views.content;
+
+        var $form = $("<div>");
+        var $formColumn = $("<form>", {
+            'name': 'rcExportForm',
+            'role': 'form',
+            'action': 'javascript:void(0);',
+            'onsubmit': 'mainController.currentView.exportSelected();'
+        });
+        $form.append($formColumn);
+
+        var $infoBox1 = FormUtil.getInfoBox('You can select any parts of the accessible openBIS structure to export:', [
+            'If you select a tree node and do not expand it, everything below this node will be exported by default.',
+            'To export selectively only parts of a tree, open the nodes and select what to export.'
+        ]);
+        $infoBox1.css('border', 'none');
+        $container.append($infoBox1);
+
+        var $infoBox2 = FormUtil.getInfoBox('Publication time constraint', [
+            'After the resource has been exported it should be published in Zenodo UI within 2 hours.',
+            'Otherwise, the publication metadata will not be registered in openBIS.'
+        ]);
+        $infoBox2.css('border', 'none');
+        $container.append($infoBox2);
+
+        var $tree = $('<div>', { 'id' : 'exportsTree' });
+        $formColumn.append($('<br>'));
+        $formColumn.append(FormUtil.getBox().append($tree));
+
+        $container.append($form);
+
+        exportModel.tree = TreeUtil.getCompleteTree($tree);
+
+        var $formTitle = $('<h2>').append('Zenodo Export Builder');
+        $header.append($formTitle);
+
+        var $exportButton = $('<input>', { 'type': 'submit', 'class': 'btn btn-primary', 'value': 'Export Selected',
+            'onClick': '$("form[name=\'rcExportForm\']").submit()'});
+        $header.append($exportButton);
+    };
+}
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/drop-boxes/eln-lims-dropbox/eln-lims-dropbox.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/drop-boxes/eln-lims-dropbox/eln-lims-dropbox.py
index 50c8297589fcd2df04625c1510e88306723ac95c..71b0971ba7ed85a576b6b53fc307f89dbb4474f5 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/drop-boxes/eln-lims-dropbox/eln-lims-dropbox.py
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/drop-boxes/eln-lims-dropbox/eln-lims-dropbox.py
@@ -32,22 +32,15 @@ def process(transaction):
 		
 		# Parse entity Kind Format
 		if entityKind == "O":
-			
-			if len(datasetInfo) >= 4:
+			OPENBISURL = DataStoreServer.getConfigParameters().getServerURL() + "/openbis/openbis"
+			v3 = HttpInvokerUtils.createServiceStub(IApplicationServerApi, OPENBISURL + IApplicationServerApi.SERVICE_URL, 30 * 1000);
+			projectSamplesEnabled = v3.getServerInformation(transaction.getOpenBisServiceSessionToken())['project-samples-enabled'] == 'true'
+
+			if len(datasetInfo) >= 4 and projectSamplesEnabled:
 				sampleSpace = datasetInfo[1];
 				projectCode = datasetInfo[2];
 				sampleCode = datasetInfo[3];
-				
-				OPENBISURL = DataStoreServer.getConfigParameters().getServerURL() + "/openbis/openbis"
-				v3 = HttpInvokerUtils.createServiceStub(IApplicationServerApi, OPENBISURL + IApplicationServerApi.SERVICE_URL, 30 * 1000);
-				projectSamplesEnabled = v3.getServerInformation(transaction.getOpenBisServiceSessionToken())['project-samples-enabled'] == 'true'
-				
-				sample = None
-				if projectSamplesEnabled:
-					sample = transaction.getSample("/" +sampleSpace + "/" + projectCode + "/" + sampleCode);
-				else:
-					sample = transaction.getSample("/" +sampleSpace + "/" + sampleCode);
-				
+				sample = transaction.getSample("/" +sampleSpace + "/" + projectCode + "/" + sampleCode);
 				if sample is None:
 					raise UserFailureException(INVALID_FORMAT_ERROR_MESSAGE + ":" + SAMPLE_MISSING_ERROR_MESSAGE);
 				if len(datasetInfo) >= 5:
@@ -56,9 +49,20 @@ def process(transaction):
 					name = datasetInfo[5];
 				if len(datasetInfo) > 6:
 					raise UserFailureException(INVALID_FORMAT_ERROR_MESSAGE + ":" + FAILED_TO_PARSE_SAMPLE_ERROR_MESSAGE);
+			elif len(datasetInfo) >= 3 and not projectSamplesEnabled:
+				sampleSpace = datasetInfo[1];
+				sampleCode = datasetInfo[2];
+				sample = transaction.getSample("/" +sampleSpace + "/" + sampleCode);
+				if sample is None:
+					raise UserFailureException(INVALID_FORMAT_ERROR_MESSAGE + ":" + SAMPLE_MISSING_ERROR_MESSAGE);
+				if len(datasetInfo) >= 4:
+					datasetType = datasetInfo[3];
+				if len(datasetInfo) >= 5:
+					name = datasetInfo[4];
+				if len(datasetInfo) > 5:
+					raise UserFailureException(INVALID_FORMAT_ERROR_MESSAGE + ":" + FAILED_TO_PARSE_SAMPLE_ERROR_MESSAGE);
 			else:
 				raise UserFailureException(INVALID_FORMAT_ERROR_MESSAGE + ":" + FAILED_TO_PARSE_SAMPLE_ERROR_MESSAGE);
-		
 		if entityKind == "E":
 			if len(datasetInfo) >= 4:
 				experimentSpace = datasetInfo[1];
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 932c0fa302302c30bd0dc8174b5fd680c62a0707..7af095aa70a2bb27e997ae05a1d669464da3fd5c 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
@@ -645,4 +645,33 @@ def getConfigurationProperty(transaction, propertyName):
     try:
         return threadProperties.getProperty(propertyName);
     except:
-        return None
\ No newline at end of file
+        return None
+
+
+def generateZipFile(entities, params, tempDirPath, tempZipFilePath):
+    # Generates ZIP file with selected item for export
+
+    sessionToken = params.get('sessionToken')
+    includeRoot = params.get('includeRoot')
+
+    fos = None
+    zos = None
+    try:
+        fos = FileOutputStream(tempZipFilePath)
+        zos = ZipOutputStream(fos)
+
+        fileMetadata = generateFilesInZip(zos, entities, includeRoot, sessionToken, tempDirPath)
+    finally:
+        if zos is not None:
+            zos.close()
+        if fos is not None:
+            fos.close()
+
+    return fileMetadata
+
+
+def checkResponseStatus(response):
+    status = response.getStatus()
+    if status >= 300:
+        reason = response.getReason()
+        raise ValueError('Unsuccessful response from the server: %s %s' % (status, reason))
\ No newline at end of file
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 99f41ac9578a20a317c6af933a733e5e6b407d00..66e91990f86074101ad93186f4f6c7aac001b285 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
@@ -35,7 +35,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
+from exportsApi import displayResult, findEntitiesToExport, validateDataSize, getConfigurationProperty, generateFilesInZip, addToZipFile, cleanUp, \
+    generateZipFile, checkResponseStatus
 
 operationLog = Logger.getLogger(str(LogCategory.OPERATION) + '.rcExports.py')
 
@@ -106,7 +107,7 @@ def export(entities, tr, params, userInformation):
     exportZipFilePath = exportDirPath + '.zip'
     exportZipFileName = exportDirName + '.zip'
 
-    generateInternalZipFile(entities, params, contentDirPath, contentZipFilePath)
+    generateZipFile(entities, params, contentDirPath, contentZipFilePath)
     FileUtils.forceDelete(File(contentDirPath))
 
     generateExternalZipFile(params=params, exportDirPath=exportDirPath, contentZipFilePath=contentZipFilePath, contentZipFileName=contentZipFileName,
@@ -189,35 +190,6 @@ def fetchServiceDocument(url, httpClient):
     return json.dumps(map(collectionToDictionaryMapper, collections))
 
 
-def checkResponseStatus(response):
-    status = response.getStatus()
-    if status >= 300:
-        reason = response.getReason()
-        raise ValueError('Unsuccessful response from the server: %s %s' % (status, reason))
-
-
-def generateInternalZipFile(entities, params, tempDirPath, tempZipFilePath):
-    # Generates ZIP file with selected item for export
-
-    sessionToken = params.get('sessionToken')
-    includeRoot = params.get('includeRoot')
-
-    fos = None
-    zos = None
-    try:
-        fos = FileOutputStream(tempZipFilePath)
-        zos = ZipOutputStream(fos)
-
-        fileMetadata = generateFilesInZip(zos, entities, includeRoot, sessionToken, tempDirPath)
-    finally:
-        if zos is not None:
-            zos.close()
-        if fos is not None:
-            fos.close()
-
-    return fileMetadata
-
-
 def generateExternalZipFile(params, exportDirPath, contentZipFilePath, contentZipFileName, exportZipFileName, userInformation, entities):
     # Generates ZIP file which will go to the research collection server
 
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/exportsApi.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/exportsApi.py
new file mode 120000
index 0000000000000000000000000000000000000000..ccb8a49feb33c9712b493e377365bd0b7dad5f8d
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/exportsApi.py
@@ -0,0 +1 @@
+../exports-api/exportsApi.py
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/job-scheduler-source/JobScheduler.java b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/job-scheduler-source/JobScheduler.java
new file mode 100644
index 0000000000000000000000000000000000000000..23558319831415c5d4b1a912d720553ce45efc7d
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/job-scheduler-source/JobScheduler.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.ethz.sis;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class JobScheduler
+{
+
+    public static void scheduleRepeatedRequest(final long period, final int repCount, final BooleanCallable callable)
+    {
+        final Timer timer = new Timer(false);
+
+        final AtomicInteger remainingRepCount = new AtomicInteger(repCount);
+        timer.scheduleAtFixedRate(new TimerTask()
+        {
+            @Override
+            public void run()
+            {
+                final boolean returnedValue = callable.call();
+                if (returnedValue || remainingRepCount.decrementAndGet() <= 0)
+                {
+                    timer.cancel();
+                }
+            }
+
+        }, period, period);
+    }
+
+    public interface BooleanCallable
+    {
+        boolean call();
+    }
+
+}
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/lib/job-scheduler.jar b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/lib/job-scheduler.jar
new file mode 100644
index 0000000000000000000000000000000000000000..3ab019d90a01dc5cb77d1c80b8f37dab09761137
Binary files /dev/null and b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/lib/job-scheduler.jar differ
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/plugin.properties b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/plugin.properties
new file mode 100644
index 0000000000000000000000000000000000000000..46637e2ba22178234ec544460bef0591959e8a9a
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/plugin.properties
@@ -0,0 +1,7 @@
+label = Zenodo Exports API
+class = ch.systemsx.cisd.openbis.dss.generic.server.plugins.jython.JythonIngestionService
+script-path = zenodoExports.py
+service-document-url=https://test.research-collection.ethz.ch/swordv2/servicedocument
+limit-data-size-megabytes = 50000
+zenodoUrl=https://localhost
+accessToken=
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/zenodoExports.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/zenodoExports.py
new file mode 100644
index 0000000000000000000000000000000000000000..4934cac3586e661ffe31a3142a24b52af2876ba7
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/zenodoExports.py
@@ -0,0 +1,260 @@
+#
+# Copyright 2016 ETH Zuerich, Scientific IT Services
+#
+# 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.
+#
+
+from __future__ import print_function
+
+import json
+import traceback
+
+import time
+from ch.systemsx.cisd.common.logging import LogCategory
+from ch.systemsx.cisd.openbis.dss.generic.shared import ServiceProvider
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.service.id import CustomASServiceCode
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.service import CustomASServiceExecutionOptions
+from java.io import File
+from java.nio.file import Paths
+from org.apache.commons.io import FileUtils
+from org.apache.log4j import Logger
+from org.eclipse.jetty.client import HttpClient
+from org.eclipse.jetty.client.util import MultiPartContentProvider
+from org.eclipse.jetty.client.util import PathContentProvider
+from org.eclipse.jetty.client.util import StringContentProvider
+from org.eclipse.jetty.http import HttpMethod
+from org.eclipse.jetty.util.ssl import SslContextFactory
+from org.json import JSONObject
+
+from ch.ethz.sis import JobScheduler
+from exportsApi import findEntitiesToExport, validateDataSize, getConfigurationProperty, generateZipFile, checkResponseStatus, displayResult, cleanUp
+
+operationLog = Logger.getLogger(str(LogCategory.OPERATION) + '.zenodoExports.py')
+
+
+def process(tr, params, tableBuilder):
+    method = params.get('method')
+
+    # Set user using the service
+    tr.setUserId(userId)
+
+    if method == 'exportAll':
+        resultUrl = expandAndExport(tr, params)
+        displayResult(resultUrl is not None, tableBuilder, '{"url": "' + resultUrl + '"}' if resultUrl is not None else None)
+
+
+def expandAndExport(tr, params):
+    entitiesToExport = findEntitiesToExport(params)
+    validateDataSize(entitiesToExport, tr)
+
+    operationLog.info('Found ' + str(len(entitiesToExport)) + ' entities to export')
+    exportUrl = export(entities=entitiesToExport, tr=tr, params=params)
+
+    return exportUrl
+
+
+def export(entities, tr, params):
+    #Create temporal folder
+    timeNow = time.time()
+
+    exportDirName = 'export_' + str(timeNow)
+    exportDir = File.createTempFile(exportDirName, None)
+    exportDirPath = exportDir.getCanonicalPath()
+    exportDir.delete()
+    exportDir.mkdir()
+
+    contentZipFileName = 'content.zip'
+    contentDirName = 'content_' + str(timeNow)
+    contentDir = File.createTempFile(contentDirName, None, exportDir)
+    contentDirPath = contentDir.getCanonicalPath()
+    contentDir.delete()
+    contentDir.mkdir()
+
+    contentZipFilePath = exportDirPath + '/' + contentZipFileName
+
+    generateZipFile(entities, params, contentDirPath, contentZipFilePath)
+    FileUtils.forceDelete(File(contentDirPath))
+
+    resultUrl = sendToZenodo(tr=tr, params=params, tempZipFilePath=contentZipFilePath, entities=entities)
+    FileUtils.forceDelete(File(exportDirPath))
+    return resultUrl
+
+
+def sendToZenodo(tr, params, tempZipFilePath, entities):
+    depositRootUrl = str(getConfigurationProperty(tr, 'zenodoUrl')) + '/api/deposit/depositions'
+
+    accessToken = str(getConfigurationProperty(tr, 'accessToken'))
+    operationLog.info('accessToken: %s' % accessToken)
+
+    httpClient = None
+    try:
+        httpClient = createHttpClient()
+
+        httpClient.setFollowRedirects(False)
+        httpClient.start()
+
+        depositionData = createDepositionResource(httpClient.newRequest(depositRootUrl), accessToken)
+
+        depositionLinks = depositionData.get('links')
+        depositUrl = depositionLinks.get('files')
+        selfUrl = depositionLinks.get('self')
+
+        submitFile(httpClient.newRequest(depositUrl), accessToken, tempZipFilePath)
+        addMetadata(httpClient.newRequest(selfUrl), accessToken)
+
+        entityPermIds = map(lambda entity: entity['permId'], entities)
+        zenodoCallable = ZenodoCallable(params, accessToken, selfUrl,
+                                        reduce(lambda str, permId: str + ',' + permId, entityPermIds))
+        zenodoCallable.scheduleMetadataCheck()
+
+        result = depositionLinks.get('html')
+        return result
+    except Exception as e:
+        operationLog.error('Exception at: ' + traceback.format_exc())
+        operationLog.error('Exception: ' + str(e))
+        raise e
+    finally:
+        if httpClient is not None:
+            httpClient.stop()
+
+
+def submitFile(request, accessToken, tempZipFilePath):
+    multiPart = MultiPartContentProvider()
+    multiPart.addFilePart('file', 'content.zip', PathContentProvider(Paths.get(tempZipFilePath)), None)
+    multiPart.close()
+    addAuthenticationHeader(accessToken, request)
+    response = request.method(HttpMethod.POST).content(multiPart).send()
+    checkResponseStatus(response)
+    contentStr = response.getContentAsString()
+
+    return JSONObject(contentStr)
+
+
+def addMetadata(request, accessToken):
+    data = {
+        'metadata': {
+            'title': str(time.time()),
+            'license': 'cc-zero',
+            'upload_type': 'dataset',
+            'description': 'Add some description.',
+            'creators': [{'name': userId}]
+        }
+    }
+
+    addAuthenticationHeader(accessToken, request)
+    jsonString = json.dumps(data)
+    response = request.method(HttpMethod.PUT).content(StringContentProvider(jsonString), 'application/json').send()
+
+    checkResponseStatus(response)
+
+
+def retrieve(request, accessToken):
+    addAuthenticationHeader(accessToken, request)
+    response = request.method(HttpMethod.GET).send()
+    contentStr = response.getContentAsString()
+
+    # If the resource has been deleted instead of published return None.
+    if response.getStatus() == 410:
+        return None
+
+    checkResponseStatus(response)
+
+    return JSONObject(contentStr)
+
+
+def createDepositionResource(request, accessToken):
+    addAuthenticationHeader(accessToken, request)
+    response = request.method(HttpMethod.POST).content(StringContentProvider('{}'), 'application/json').send()
+    checkResponseStatus(response)
+
+    contentStr = response.getContentAsString()
+    return JSONObject(contentStr)
+
+
+def addAuthenticationHeader(accessToken, request):
+    request.header('Authorization', 'Bearer ' + accessToken)
+
+
+def createHttpClient():
+    sslContextFactory = SslContextFactory()
+    sslContextFactory.setTrustAll(True)
+    return HttpClient(sslContextFactory)
+
+
+class ZenodoCallable(object):
+    params = None
+    accessToken = None
+    selfUrl = None
+    permIdsStr = None
+
+    def __init__(self, params, accessToken, selfUrl, permIdsStr):
+        self.params = params
+        self.accessToken = accessToken
+        self.selfUrl = selfUrl
+        self.permIdsStr = permIdsStr
+
+    def scheduleMetadataCheck(self):
+        JobScheduler.scheduleRepeatedRequest(120000, 60, self.call)
+
+    def call(self):
+        httpClient = None
+
+        # Whether this method returned a completion result and it should not be called repeatedly.
+        actionCompleted = False
+
+        try:
+            httpClient = createHttpClient()
+
+            httpClient.setFollowRedirects(False)
+            httpClient.start()
+
+            try:
+                publicationJson = retrieve(httpClient.newRequest(self.selfUrl), self.accessToken)
+                if publicationJson is None:
+                    operationLog.info('Publication at the URL has been deleted.' % self.selfUrl)
+                    actionCompleted = True
+                elif publicationJson.get('submitted'):
+                    operationLog.info('Publication #%d submitted. Registering metadata.' % publicationJson.get('id'))
+                    self.registerPublicationInOpenbis(publicationJson.get('metadata'))
+                    actionCompleted = True
+                else:
+                    operationLog.info('Publication #%d not submitted yet.' % publicationJson.get('id'))
+            except Exception as e:
+                operationLog.error('Exception at: ' + traceback.format_exc())
+                operationLog.error('Exception: ' + str(e))
+                actionCompleted = False
+        except Exception as e:
+            operationLog.error('Exception at: ' + traceback.format_exc())
+            operationLog.error('Exception: ' + str(e))
+            raise e
+        finally:
+            if httpClient is not None:
+                httpClient.stop()
+
+        return actionCompleted
+
+
+    def registerPublicationInOpenbis(self, publicationMetadataJson):
+        sessionToken = self.params.get('sessionToken')
+        v3 = ServiceProvider.getV3ApplicationService()
+        id = CustomASServiceCode('publication-api')
+        options = CustomASServiceExecutionOptions() \
+            .withParameter('method', 'insertPublication') \
+            .withParameter('publicationURL', self.selfUrl) \
+            .withParameter('openBISRelatedIdentifiers', self.permIdsStr) \
+            .withParameter('name', publicationMetadataJson.get('title')) \
+            .withParameter('publicationDescription', publicationMetadataJson.get('description')) \
+            .withParameter('publicationType', publicationMetadataJson.get('upload_type')) \
+            .withParameter('publicationIdentifier', publicationMetadataJson.get('doi'))
+        result = v3.executeCustomASService(sessionToken, id, options)
+        return result
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 adf93431d3e19e72826f8cff495710a536a4e5d5..9806b27718bb3855c9f0eff450b44c2edb637b3b 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
@@ -26,9 +26,9 @@ def validate_data(xls_byte_arrays, update_mode, xls_name):
 
 
 def get_property(key, defaultValue):
-    propertyConfigurer = CommonServiceProvider.getApplicationContext().getBean("propertyConfigurer");
-    properties = propertyConfigurer.getResolvedProps();
-    return properties.getProperty(key, defaultValue);
+    propertyConfigurer = CommonServiceProvider.getApplicationContext().getBean("propertyConfigurer")
+    properties = propertyConfigurer.getResolvedProps()
+    return properties.getProperty(key, defaultValue)
 
 
 def read_versioning_information(xls_version_filepath):
@@ -93,7 +93,8 @@ def process(context, parameters):
                     if code in versioning_information:
                         version = versioning_information[code]
                     else:
-                        version = creations_metadata.get_metadata_for(creation_type, creation).version if update_mode != "UPDATE_IF_EXISTS" else 0
+                        version = creations_metadata.get_metadata_for(creation_type,
+                                                                      creation).version if update_mode != "UPDATE_IF_EXISTS" else 0
                     versioning_information[code] = int(version)
     else:
         versioning_information = {}
@@ -101,12 +102,14 @@ def process(context, parameters):
             if creation_type in versionable_types:
                 for creation in creation_collection:
                     code = get_metadata_name_for(creation_type, creation)
-                    versioning_information[code] = creations_metadata.get_metadata_for(creation_type, creation).version if update_mode != "UPDATE_IF_EXISTS" else 0
+                    versioning_information[code] = creations_metadata.get_metadata_for(creation_type,
+                                                                                       creation).version if update_mode != "UPDATE_IF_EXISTS" else 0
 
     existing_elements = search_engine.find_all_existing_elements(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, existing_elements)
+    existing_unified_kinds = unify_properties_representation_of(creations, entity_kinds, existing_vocabularies,
+                                                                existing_elements)
     creations = PropertiesLabelHandler.rewrite_property_labels_to_codes(creations, existing_unified_kinds)
     server_duplicates_handler = OpenbisDuplicatesHandler(creations, creations_metadata, existing_elements,
                                                          versioning_information, update_mode)
@@ -117,4 +120,3 @@ def process(context, parameters):
     all_versioning_information[xls_version_name] = versioning_information
     save_versioning_information(all_versioning_information, xls_version_filepath)
     return res
-
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/creation_to_update/update_parsers.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/creation_to_update/update_parsers.py
index a9be5e9003d550f3a6ebf87aaeaa15f251cd60b6..93daca6f0a0b5e766fb24526a148da944271c4e9 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/creation_to_update/update_parsers.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/creation_to_update/update_parsers.py
@@ -8,14 +8,14 @@ from ch.ethz.sis.openbis.generic.asapi.v3.dto.space.update import SpaceUpdate
 from ch.ethz.sis.openbis.generic.asapi.v3.dto.project.update import ProjectUpdate
 from ch.ethz.sis.openbis.generic.asapi.v3.dto.plugin.update import PluginUpdate
 from java.lang import UnsupportedOperationException
-from ..definition_to_creation import PropertyTypeDefinitionToCreationType, VocabularyDefinitionToCreationType, VocabularyTermDefinitionToCreationType, \
-                    PropertyAssignmentDefinitionToCreationType, SampleTypeDefinitionToCreationType, ExperimentTypeDefinitionToCreationType, \
-                    DatasetTypeDefinitionToCreationType, SpaceDefinitionToCreationType, ProjectDefinitionToCreationType, ExperimentDefinitionToCreationType, \
-                    SampleDefinitionToCreationType, ScriptDefinitionToCreationType
-from .update_types import PropertyTypeCreationToUpdateType, VocabularyCreationToUpdateType, PropertyAssignmentCreationToUpdateType, \
-                        SampleTypeCreationToUpdateType, ExperimentTypeCreationToUpdateType, DatasetTypeCreationToUpdateType, \
-                        SpaceCreationToUpdateType, ProjectCreationToUpdateType, ExperimentCreationToUpdateType, \
-                        SampleCreationToUpdateType, ScriptCreationToUpdateType, VocabularyTermCreationToUpdateType
+from ..definition_to_creation import PropertyTypeDefinitionToCreationType, VocabularyDefinitionToCreationType, \
+    VocabularyTermDefinitionToCreationType, SampleTypeDefinitionToCreationType, ExperimentTypeDefinitionToCreationType, \
+    DatasetTypeDefinitionToCreationType, SpaceDefinitionToCreationType, ProjectDefinitionToCreationType, \
+    ExperimentDefinitionToCreationType, SampleDefinitionToCreationType, ScriptDefinitionToCreationType
+from .update_types import PropertyTypeCreationToUpdateType, VocabularyCreationToUpdateType, \
+    SampleTypeCreationToUpdateType, ExperimentTypeCreationToUpdateType, DatasetTypeCreationToUpdateType, \
+    SpaceCreationToUpdateType, ProjectCreationToUpdateType, ExperimentCreationToUpdateType, SampleCreationToUpdateType, \
+    ScriptCreationToUpdateType, VocabularyTermCreationToUpdateType
 
 
 class CreationToUpdateParserFactory(object):
@@ -81,6 +81,13 @@ class PropertyTypeCreationToUpdateParser(object):
         property_type_update.typeId = existing_property_type.permId
         property_type_update.setLabel(creation.label)
         property_type_update.setDescription(creation.description)
+        metadata_update = property_type_update.getMetaData()
+        if existing_property_type.metaData:
+            for metaDataEntry in existing_property_type.metaData:
+                if creation.metaData and metaDataEntry not in creation.metaData:
+                    metadata_update.remove(metaDataEntry)
+        if creation.metaData:
+            metadata_update.add(creation.metaData)
         return property_type_update
 
     def get_type(self):
@@ -91,8 +98,10 @@ class EntityTypeCreationToUpdateParser(object):
 
     def parseAssignments(self, creation, existing_entity_type):
         assignments_update = ListUpdateValue()
-        creationPropertyAssignmentCodes = [str(property_assignment.propertyTypeId) for property_assignment in creation.propertyAssignments]
-        existingPropertyAssignmentCodes = [str(property_assignment.propertyType.code) for property_assignment in existing_entity_type.propertyAssignments]
+        creationPropertyAssignmentCodes = [str(property_assignment.propertyTypeId) for property_assignment in
+                                           creation.propertyAssignments]
+        existingPropertyAssignmentCodes = [str(property_assignment.propertyType.code) for property_assignment in
+                                           existing_entity_type.propertyAssignments]
         for property_assignment in existing_entity_type.propertyAssignments:
             if str(property_assignment.propertyType.code) in creationPropertyAssignmentCodes:
                 continue
@@ -205,9 +214,11 @@ class SampleCreationToUpdateParser(object):
         for child in existing_sample.children:
             existing_children_identifiers.extend([str(child.permId), str(child.identifier)])
 
-        parentsToRemove = [parent.permId for parent in existing_sample.parents if parent.permId not in creation.parentIds and parent.identifier not in creation.parentIds]
+        parentsToRemove = [parent.permId for parent in existing_sample.parents if
+                           parent.permId not in creation.parentIds and parent.identifier not in creation.parentIds]
         parentsToAdd = [parent for parent in creation.parentIds if str(parent) not in existing_parent_identifiers]
-        childrenToRemove = [child.permId for child in existing_sample.children if child.permId not in creation.childIds and child.identifier not in creation.parentIds]
+        childrenToRemove = [child.permId for child in existing_sample.children if
+                            child.permId not in creation.childIds and child.identifier not in creation.parentIds]
         childrenToAdd = [child for child in creation.childIds if str(child) not in existing_children_identifiers]
         sample_update.childIds.remove([parent.permId for parent in existing_sample.children])
         sample_update.parentIds.remove([child.permId for child in existing_sample.parents])
@@ -230,4 +241,3 @@ class ScriptCreationToUpdateParser(object):
 
     def get_type(self):
         return ScriptCreationToUpdateType
-
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 9a9f1918639be04793de2877c4264970cfae4852..d1681a5a469899b943903533b91f04474c3a82de 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
@@ -17,15 +17,20 @@ from ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id import VocabularyPer
 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 .creation_types import PropertyTypeDefinitionToCreationType, VocabularyDefinitionToCreationType, VocabularyTermDefinitionToCreationType, \
-                    PropertyAssignmentDefinitionToCreationType, SampleTypeDefinitionToCreationType, ExperimentTypeDefinitionToCreationType, \
-                    DatasetTypeDefinitionToCreationType, SpaceDefinitionToCreationType, ProjectDefinitionToCreationType, ExperimentDefinitionToCreationType, \
-                    SampleDefinitionToCreationType, ScriptDefinitionToCreationType
+from .creation_types import PropertyTypeDefinitionToCreationType, VocabularyDefinitionToCreationType, \
+    VocabularyTermDefinitionToCreationType, \
+    PropertyAssignmentDefinitionToCreationType, SampleTypeDefinitionToCreationType, \
+    ExperimentTypeDefinitionToCreationType, \
+    DatasetTypeDefinitionToCreationType, SpaceDefinitionToCreationType, ProjectDefinitionToCreationType, \
+    ExperimentDefinitionToCreationType, \
+    SampleDefinitionToCreationType, ScriptDefinitionToCreationType
+import json
 
 
 def get_boolean_from_string(text):
     if text.lower() not in [u'true', u'false']:
-        raise UserFailureException("Boolean field should either be 'true' or 'false' (case insensitive) but was " + text)
+        raise UserFailureException(
+            "Boolean field should either be 'true' or 'false' (case insensitive) but was " + text)
     return True if text and text.lower() == u'true' else False
 
 
@@ -73,7 +78,10 @@ class PropertyTypeDefinitionToCreationParser(object):
             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.vocabularyId = VocabularyPermId(prop.get(u'vocabulary code')) if prop.get(u'vocabulary code') is not None else None
+            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
+            property_type_creation.metaData = metadata
             property_creations.append(property_type_creation)
 
         return property_creations
@@ -150,7 +158,8 @@ class SampleTypeDefinitionToCreationParser(object):
         generatedCodePrefix = definition.attributes.get(u'generated code prefix')
         if generatedCodePrefix is not None:
             sample_creation.generatedCodePrefix = generatedCodePrefix
-        if u'validation script' in definition.attributes and definition.attributes.get(u'validation script') is not None:
+        if u'validation script' in definition.attributes and definition.attributes.get(
+                u'validation script') is not None:
             validation_script_path = definition.attributes.get(u'validation script')
             sample_creation.validationPluginId = PluginPermId(get_script_name_for(code, validation_script_path))
 
@@ -174,9 +183,11 @@ class ExperimentTypeDefinitionToCreationParser(object):
         experiment_type_creation = ExperimentTypeCreation()
         experiment_type_creation.code = code
         experiment_type_creation.description = definition.attributes.get(u'description')
-        if u'validation script' in definition.attributes and definition.attributes.get(u'validation script') is not None:
+        if u'validation script' in definition.attributes and definition.attributes.get(
+                u'validation script') is not None:
             validation_script_path = definition.attributes.get(u'validation script')
-            experiment_type_creation.validationPluginId = PluginPermId(get_script_name_for(code, validation_script_path))
+            experiment_type_creation.validationPluginId = PluginPermId(
+                get_script_name_for(code, validation_script_path))
 
         property_assignment_creations = []
         property_assignment_parser = PropertyAssignmentDefinitionToCreationParser()
@@ -198,7 +209,8 @@ class DatasetTypeDefinitionToCreationParser(object):
         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(u'validation script') is not None:
+        if u'validation script' in definition.attributes and definition.attributes.get(
+                u'validation script') is not None:
             validation_script_path = definition.attributes.get(u'validation script')
             dataset_type_creation.validationPluginId = PluginPermId(get_script_name_for(code, validation_script_path))
 
@@ -277,7 +289,7 @@ class SampleDefinitionToCreationParser(object):
     def parse(self, definition):
         samples = []
         sample_attributes = [u'$', u'code', u'space', u'project', u'experiment', u'auto generate code', u'parents',
-                                u'children']
+                             u'children']
         for sample_properties in definition.properties:
             sample_creation = SampleCreation()
             sample_creation.typeId = EntityTypePermId(definition.attributes.get(u'sample type'))
@@ -288,9 +300,11 @@ class SampleDefinitionToCreationParser(object):
             if u'$' in sample_properties and sample_properties.get(u'$') is not None:
                 # may overwrite creationId from code, which is intended
                 sample_creation.creationId = CreationId(sample_properties.get(u'$'))
-            if u'auto generate code' in sample_properties and sample_properties.get(u'auto generate code') is not None and \
+            if u'auto generate code' in sample_properties and sample_properties.get(
+                    u'auto generate code') is not None and \
                     sample_properties.get(u'auto generate code') != '':
-                sample_creation.autoGeneratedCode = get_boolean_from_string(sample_properties.get(u'auto generate code'))
+                sample_creation.autoGeneratedCode = get_boolean_from_string(
+                    sample_properties.get(u'auto generate code'))
             if u'space' in sample_properties and sample_properties.get(u'space') is not None:
                 sample_creation.spaceId = CreationId(sample_properties.get(u'space'))
             if u'project' in sample_properties and sample_properties.get(u'project') is not None:
diff --git a/pybis/src/python/CHANGELOG.md b/pybis/src/python/CHANGELOG.md
index 4ba563b5f32628fd97bd1378de9904040d4d3d28..1c68708da418d638fa3158a6d438d9e08b63e341 100644
--- a/pybis/src/python/CHANGELOG.md
+++ b/pybis/src/python/CHANGELOG.md
@@ -1,3 +1,8 @@
+## Changes with pybis-1.9.1
+
+* bugfix: controlled vocabulary
+
+
 ## Changes with pybis-1.9.0
 
 * new: search, create, update and delete Property Types
@@ -6,6 +11,7 @@
 * freeze entities to prevent changes
 * added more tests
 
+
 ## Changes with pybis-1.8.5
 
 * changed to v3 API when fetching datastores
diff --git a/pybis/src/python/pybis/__init__.py b/pybis/src/python/pybis/__init__.py
index 2ca20089ea36d860b51223bba7c30326c7957f96..4aa2b995675dc04f0874952732c359ada99bdd0f 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.9.0'
+__version__ = '1.9.1'
 
 from . import pybis
 from .pybis import Openbis
diff --git a/pybis/src/python/pybis/property.py b/pybis/src/python/pybis/property.py
index 9dd9bf6c31f89a0868f743a03e04f97868aa485e..9d2a6431abf9304559c0fb9ddae52bf6b2e89f2f 100644
--- a/pybis/src/python/pybis/property.py
+++ b/pybis/src/python/pybis/property.py
@@ -60,7 +60,7 @@ class PropertyHolder():
             if name in self._property_names:
                 property_type = self._property_names[name]
                 if property_type['dataType'] == 'CONTROLLEDVOCABULARY':
-                    return self._get_terms(property_type['vocabulary']['code'])
+                    return self._get_terms(property_type['code'])
                 else:
                     syntax = { property_type["label"] : property_type["dataType"]}
                     if property_type["dataType"] == "TIMESTAMP":
@@ -82,7 +82,7 @@ class PropertyHolder():
         property_type = self._property_names[name]
         data_type = property_type['dataType']
         if data_type == 'CONTROLLEDVOCABULARY':
-            voc = self._get_terms(property_type['vocabulary']['code'])
+            voc = self._get_terms(property_type['code'])
             value = str(value).upper()
             if value not in voc.df['code'].values:
                 raise ValueError("Value for attribute {} must be one of these terms: {}".format(
diff --git a/pybis/src/python/setup.py b/pybis/src/python/setup.py
index ba3e2a12cd1d6c1af822210f2223dee6324fe419..723fcda825c708e6a385739579d327aa8d44c80e 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.9.0',
+    version= '1.9.1',
     author='Swen Vermeul • ID SIS • ETH Zürich',
     author_email='swen@ethz.ch',
     description='openBIS connection and interaction, optimized for using with Jupyter',