From cedde58569b56456ef508917b77e7a151e375aa9 Mon Sep 17 00:00:00 2001
From: Cezary Czernecki <ccezary@ethz.ch>
Date: Wed, 4 Sep 2019 14:42:25 +0200
Subject: [PATCH] SSDM-8701 excel parser csv support

---
 .../v1/impl/MasterDataRegistrationHelper.java |  67 ++++---
 .../as/services/xls-import-api/entrypoint.py  |  18 +-
 .../xls-import-api/parsers/__init__.py        |   5 +-
 .../xls-import-api/parsers/parsers_facade.py  |  18 +-
 .../parsers/poi_to_definition/__init__.py     |   2 -
 .../parsers/to_definition/__init__.py         |   3 +
 .../csv_to_definition/__init__.py             |   0
 .../csv_to_definition/csv_to_definition.py    |  28 +++
 .../definition.py                             |   0
 .../poi_to_definition/__init__.py             |   0
 .../poi_to_definition/definition_parsers.py   |  49 ++---
 .../poi_to_definition/poi_cleaner.py          |   8 +-
 .../poi_to_definition/poi_to_definition.py    |   0
 .../xls-import-api/utils/file_handling.py     |   3 +
 .../excelimport/AbstractImportTest.java       |   9 +-
 .../plugin/excelimport/ImportFromCsvTest.java |  59 ++++++
 .../ImportVocabularyTypesTest.java            |   1 -
 .../plugin/excelimport/TestUtils.java         | 171 +++++++-----------
 .../excelimport/test_files/csv/types.csv      |   7 +
 19 files changed, 262 insertions(+), 186 deletions(-)
 delete mode 100644 openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/__init__.py
 create mode 100644 openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/__init__.py
 create mode 100644 openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/csv_to_definition/__init__.py
 create mode 100644 openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/csv_to_definition/csv_to_definition.py
 rename openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/{poi_to_definition => to_definition}/definition.py (100%)
 create mode 100644 openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/__init__.py
 rename openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/{ => to_definition}/poi_to_definition/definition_parsers.py (64%)
 rename openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/{ => to_definition}/poi_to_definition/poi_cleaner.py (90%)
 rename openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/{ => to_definition}/poi_to_definition/poi_to_definition.py (100%)
 create mode 100644 openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportFromCsvTest.java
 create mode 100644 openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/csv/types.csv

diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/jython/api/v1/impl/MasterDataRegistrationHelper.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/jython/api/v1/impl/MasterDataRegistrationHelper.java
index 8d5e02c3dee..dcddcc9c160 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/jython/api/v1/impl/MasterDataRegistrationHelper.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/jython/api/v1/impl/MasterDataRegistrationHelper.java
@@ -17,6 +17,8 @@
 package ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl;
 
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -31,31 +33,24 @@ import ch.systemsx.cisd.common.logging.LogFactory;
 
 /**
  * Helper class to be used in initialize-master-data.py.
- * 
+ *
  * @author Franz-Josef Elmer
  */
-public class MasterDataRegistrationHelper
-{
+public class MasterDataRegistrationHelper {
     private static Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, MasterDataRegistrationHelper.class);
-    
+
     private File masterDataFolder;
 
-    public MasterDataRegistrationHelper(Collection<?> systemPaths)
-    {
-        for (Object systemPath : systemPaths)
-        {
-            if (systemPath != null)
-            {
+    public MasterDataRegistrationHelper(Collection<?> systemPaths) {
+        for (Object systemPath : systemPaths) {
+            if (systemPath != null) {
                 String systemPathString = String.valueOf(systemPath);
-                if (systemPathString.contains("core-plugins"))
-                {
+                if (systemPathString.contains("core-plugins")) {
                     masterDataFolder = new File(new File(systemPathString), "master-data");
-                    if (masterDataFolder.exists() == false)
-                    {
+                    if (masterDataFolder.exists()) {
                         throw new IllegalArgumentException("Folder does not exist: " + masterDataFolder.getAbsolutePath());
                     }
-                    if (masterDataFolder.isFile())
-                    {
+                    if (masterDataFolder.isFile()) {
                         throw new IllegalArgumentException("Is not a folder but a file: " + masterDataFolder.getAbsolutePath());
                     }
                     operationLog.info("Master data folder: " + masterDataFolder.getAbsolutePath());
@@ -66,14 +61,11 @@ public class MasterDataRegistrationHelper
         throw new IllegalArgumentException("Does not contain path to the core plugin: " + systemPaths);
     }
 
-    public List<byte[]> listXlsByteArrays()
-    {
+    public List<byte[]> listXlsByteArrays() {
         List<byte[]> result = new ArrayList<>();
-        for (File file : masterDataFolder.listFiles())
-        {
+        for (File file : masterDataFolder.listFiles()) {
             String name = file.getName();
-            if (name.endsWith(".xls") || name.endsWith(".xlsx"))
-            {
+            if (name.endsWith(".xls") || name.endsWith(".xlsx")) {
                 operationLog.info("load master data " + file.getName());
                 result.add(FileUtilities.loadToByteArray(file));
             }
@@ -81,34 +73,39 @@ public class MasterDataRegistrationHelper
         return result;
     }
 
-    public Map<String, String> getAllScripts()
-    {
+    public List<byte[]> listCsvByteArrays() throws IOException {
+        List<byte[]> result = new ArrayList<>();
+        for (File file : masterDataFolder.listFiles()) {
+            String name = file.getName();
+            if (name.endsWith(".csv")) {
+                operationLog.info("load master data " + file.getName());
+                result.add(Files.readAllBytes(file.toPath()));
+            }
+        }
+        return result;
+    }
+
+    public Map<String, String> getAllScripts() {
         Map<String, String> result = new TreeMap<>();
         File scriptsFolder = new File(masterDataFolder, "scripts");
-        if (scriptsFolder.isDirectory())
-        {
+        if (scriptsFolder.isDirectory()) {
             gatherScripts(result, scriptsFolder, scriptsFolder);
         }
         return result;
     }
 
-    private void gatherScripts(Map<String, String> scripts, File rootFolder, File file)
-    {
-        if (file.isFile())
-        {
+    private void gatherScripts(Map<String, String> scripts, File rootFolder, File file) {
+        if (file.isFile()) {
             String scriptPath = FileUtilities.getRelativeFilePath(rootFolder, file);
             scripts.put(scriptPath, FileUtilities.loadToString(file));
             operationLog.info("Script " + scriptPath + " loaded");
         }
-        if (file.isDirectory())
-        {
+        if (file.isDirectory()) {
             File[] files = file.listFiles();
-            for (File child : files)
-            {
+            for (File child : files) {
                 gatherScripts(scripts, rootFolder, child);
             }
         }
     }
 
-    
 }
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 9806b27718b..ff5e9b18ff8 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
@@ -3,7 +3,7 @@ import os
 from ch.ethz.sis.openbis.generic.asapi.v3.dto.operation import SynchronousOperationExecutionOptions
 from ch.systemsx.cisd.common.exceptions import UserFailureException
 from ch.systemsx.cisd.openbis.generic.server import CommonServiceProvider
-from parsers import get_creations_from, get_definitions_from, get_creation_metadata_from, \
+from parsers import get_creations_from, get_definitions_from_xls, get_definitions_from_csv, get_creation_metadata_from, \
     CreationOrUpdateToOperationParser, versionable_types
 from processors import OpenbisDuplicatesHandler, PropertiesLabelHandler, DuplicatesHandler, \
     unify_properties_representation_of
@@ -14,9 +14,9 @@ from utils.openbis_utils import get_version_name_for, get_metadata_name_for
 REMOVE_VERSIONS = False
 
 
-def validate_data(xls_byte_arrays, update_mode, xls_name):
-    if xls_byte_arrays is None:
-        raise UserFailureException('Excel sheet has not been provided. "xls" parameter is None')
+def validate_data(xls_byte_arrays, csv_strings, update_mode, xls_name):
+    if xls_byte_arrays is None and csv_strings is None:
+        raise UserFailureException('Nor Excel sheet nor csv has not been provided. "xls" and "csv" parameters are None')
     if update_mode not in ['IGNORE_EXISTING', 'FAIL_IF_EXISTS', 'UPDATE_IF_EXISTS']:
         raise UserFailureException(
             'Update mode has to be one of following: IGNORE_EXISTING FAIL_IF_EXISTS UPDATE_IF_EXISTS but was ' + (
@@ -69,11 +69,17 @@ def process(context, parameters):
     search_engine = SearchEngine(api, session_token)
 
     xls_byte_arrays = parameters.get('xls', None)
+    csv_strings = parameters.get('csv', None)
     xls_name = parameters.get('xls_name', None)
     scripts = parameters.get('scripts', {})
     update_mode = parameters.get('update_mode', None)
-    validate_data(xls_byte_arrays, update_mode, xls_name)
-    definitions = get_definitions_from(xls_byte_arrays)
+    validate_data(xls_byte_arrays, csv_strings, update_mode, xls_name)
+    definitions = get_definitions_from_xls(xls_byte_arrays)
+    print(xls_byte_arrays)
+    print(csv_strings)
+    print ("EOEOOEOE")
+    print(definitions)
+    definitions.extend(get_definitions_from_csv(csv_strings))
     creations = get_creations_from(definitions, FileHandler(scripts))
     creations_metadata = get_creation_metadata_from(definitions)
     creations = DuplicatesHandler.get_distinct_creations(creations)
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/__init__.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/__init__.py
index 28a31b2287a..4bd91747064 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/__init__.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/__init__.py
@@ -7,8 +7,9 @@ from .definition_to_creation import PropertyTypeDefinitionToCreationType, Vocabu
     SampleDefinitionToCreationType, ScriptDefinitionToCreationType
 from .definition_to_creation_metadata import DefinitionToCreationMetadataParser
 from .excel_to_poi import ExcelToPoiParser
-from .parsers_facade import get_creations_from, get_definitions_from, get_creation_metadata_from
-from .poi_to_definition import PoiToDefinitionParser, Definition
+from .parsers_facade import get_creations_from, get_definitions_from_xls, get_definitions_from_csv, \
+    get_creation_metadata_from
+from .to_definition import PoiToDefinitionParser, CsvReaderToDefinitionParser, Definition
 from .creation_to_update import CreationToUpdateParser, UpdateTypes, PropertyTypeCreationToUpdateType, \
     VocabularyCreationToUpdateType, VocabularyTermCreationToUpdateType, PropertyAssignmentCreationToUpdateType, \
     SampleTypeCreationToUpdateType, ExperimentTypeCreationToUpdateType, DatasetTypeCreationToUpdateType, \
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/parsers_facade.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/parsers_facade.py
index 51624c520c9..a2884641bee 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/parsers_facade.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/parsers_facade.py
@@ -1,21 +1,29 @@
 from .definition_to_creation import DefinitionToCreationParser
 from .excel_to_poi import ExcelToPoiParser
-from .poi_to_definition import PoiToDefinitionParser
-
+from .to_definition import PoiToDefinitionParser, CsvReaderToDefinitionParser
 from .definition_to_creation_metadata import DefinitionToCreationMetadataParser
 
 
-def get_definitions_from(xls_byte_arrays):
+def get_definitions_from_xls(xls_byte_arrays):
     definitions = []
-    for excel_byte_array in xls_byte_arrays:
+    for excel_byte_array in xls_byte_arrays or []:
         poi_definitions = ExcelToPoiParser.parse(excel_byte_array)
         partial_definitions = PoiToDefinitionParser.parse(poi_definitions)
         definitions.extend(partial_definitions)
     return definitions
 
 
+def get_definitions_from_csv(csv_strings):
+    definitions = []
+    for csv_string in csv_strings or []:
+        csv_definitions = CsvReaderToDefinitionParser.parse(csv_string)
+        partial_definitions = PoiToDefinitionParser.parse(csv_definitions)
+        definitions.extend(partial_definitions)
+
+    return definitions
+
+
 def get_creations_from(definitions, context):
-    pass
     return DefinitionToCreationParser.parse(definitions, context)
 
 
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/__init__.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/__init__.py
deleted file mode 100644
index 3d310af376a..00000000000
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from .poi_to_definition import PoiToDefinitionParser
-from .definition import Definition
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/__init__.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/__init__.py
new file mode 100644
index 00000000000..0c359051546
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/__init__.py
@@ -0,0 +1,3 @@
+from .poi_to_definition.poi_to_definition import PoiToDefinitionParser
+from .csv_to_definition.csv_to_definition import CsvReaderToDefinitionParser
+from .definition import Definition
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/csv_to_definition/__init__.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/csv_to_definition/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/csv_to_definition/csv_to_definition.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/csv_to_definition/csv_to_definition.py
new file mode 100644
index 00000000000..bb3fcffad07
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/csv_to_definition/csv_to_definition.py
@@ -0,0 +1,28 @@
+from StringIO import StringIO
+import csv
+
+
+class CsvReaderToDefinitionParser():
+
+    @staticmethod
+    def parse(csv_string):
+        def is_row_empty(row):
+            return not ''.join(row).strip()
+
+        f = StringIO("".join(map(chr, csv_string)))
+        reader = csv.reader(f, delimiter=',')
+        definitions = []
+        definition_rows = []
+        previous_row_empty = False
+        for row in reader:
+            if is_row_empty(row) and previous_row_empty:
+                break
+            if is_row_empty(row):
+                definitions.append(definition_rows)
+                definition_rows = []
+                previous_row_empty = True
+                continue
+            previous_row_empty = False
+            definition_rows.append({k: v for k, v in enumerate(row) if v != ''})
+
+        return definitions
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/definition.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/definition.py
similarity index 100%
rename from openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/definition.py
rename to openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/definition.py
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/__init__.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/definition_parsers.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/definition_parsers.py
similarity index 64%
rename from openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/definition_parsers.py
rename to openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/definition_parsers.py
index d17255c0f85..c5ccd24ee88 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/definition_parsers.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/definition_parsers.py
@@ -1,18 +1,20 @@
 from java.lang import UnsupportedOperationException
-from .definition import Definition
+from ..definition import Definition
 from .poi_cleaner import PoiCleaner
 
 
 class DefinitionParserFactory(object):
 
-        @staticmethod
-        def get_parser(definition_type):
-            if definition_type in ['VOCABULARY_TYPE', 'SAMPLE_TYPE', 'EXPERIMENT_TYPE', 'DATASET_TYPE', 'EXPERIMENT', 'SAMPLE']:
-                return GeneralDefinitionParser
-            elif definition_type in ['PROPERTY_TYPE', 'SPACE', 'PROJECT']:
-                return PropertiesOnlyDefinitionParser
-            else:
-                raise UnsupportedOperationException("Definition of " + str(definition_type) + " is not supported (probably yet).")
+    @staticmethod
+    def get_parser(definition_type):
+        if definition_type in ['VOCABULARY_TYPE', 'SAMPLE_TYPE', 'EXPERIMENT_TYPE', 'DATASET_TYPE', 'EXPERIMENT',
+                               'SAMPLE']:
+            return GeneralDefinitionParser
+        elif definition_type in ['PROPERTY_TYPE', 'SPACE', 'PROJECT']:
+            return PropertiesOnlyDefinitionParser
+        else:
+            raise UnsupportedOperationException(
+                "Definition of " + str(definition_type) + " is not supported (probably yet).")
 
 
 class PropertiesOnlyDefinitionParser(object):
@@ -25,15 +27,20 @@ class PropertiesOnlyDefinitionParser(object):
         PROPERTIES_VALUES_ROW_START = 2
 
         row_numbers = {
-            'DEFINITION_TYPE_ROW' : DEFINITION_TYPE_ROW,
-            'DEFINITION_TYPE_CELL' : DEFINITION_TYPE_CELL,
-            'ATTRIBUTES_HEADER_ROW' : None,
-            'ATTRIBUTES_VALUES_ROW' : None,
-            'PROPERTIES_HEADER_ROW' : PROPERTIES_HEADER_ROW,
-            'PROPERTIES_VALUES_ROW_START' : PROPERTIES_VALUES_ROW_START
+            'DEFINITION_TYPE_ROW': DEFINITION_TYPE_ROW,
+            'DEFINITION_TYPE_CELL': DEFINITION_TYPE_CELL,
+            'ATTRIBUTES_HEADER_ROW': None,
+            'ATTRIBUTES_VALUES_ROW': None,
+            'PROPERTIES_HEADER_ROW': PROPERTIES_HEADER_ROW,
+            'PROPERTIES_VALUES_ROW_START': PROPERTIES_VALUES_ROW_START
         }
 
+        print("PREPREPRPPRE")
+        print(poi_definition)
         poi_definition = PoiCleaner.clean_data(poi_definition, row_numbers)
+        print("POSOPOPSPOPSPOSPOOPS")
+        print(poi_definition)
+
         definition = Definition()
         definition.type = poi_definition[DEFINITION_TYPE_ROW][DEFINITION_TYPE_CELL]
         if PropertiesOnlyDefinitionParser.hasProperties(poi_definition):
@@ -65,12 +72,12 @@ class GeneralDefinitionParser(object):
         PROPERTIES_VALUES_ROW_START = 4
 
         row_numbers = {
-            'DEFINITION_TYPE_ROW' : DEFINITION_TYPE_ROW,
-            'DEFINITION_TYPE_CELL' : DEFINITION_TYPE_CELL,
-            'ATTRIBUTES_HEADER_ROW' : ATTRIBUTES_HEADER_ROW,
-            'ATTRIBUTES_VALUES_ROW' : ATTRIBUTES_VALUES_ROW,
-            'PROPERTIES_HEADER_ROW' : PROPERTIES_HEADER_ROW,
-            'PROPERTIES_VALUES_ROW_START' : PROPERTIES_VALUES_ROW_START
+            'DEFINITION_TYPE_ROW': DEFINITION_TYPE_ROW,
+            'DEFINITION_TYPE_CELL': DEFINITION_TYPE_CELL,
+            'ATTRIBUTES_HEADER_ROW': ATTRIBUTES_HEADER_ROW,
+            'ATTRIBUTES_VALUES_ROW': ATTRIBUTES_VALUES_ROW,
+            'PROPERTIES_HEADER_ROW': PROPERTIES_HEADER_ROW,
+            'PROPERTIES_VALUES_ROW_START': PROPERTIES_VALUES_ROW_START
         }
 
         poi_definition = PoiCleaner.clean_data(poi_definition, row_numbers)
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/poi_cleaner.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/poi_cleaner.py
similarity index 90%
rename from openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/poi_cleaner.py
rename to openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/poi_cleaner.py
index df03358a840..e4698a6c51d 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/poi_cleaner.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/poi_cleaner.py
@@ -25,13 +25,14 @@ class PoiCleaner(object):
                    If there's no corresponding value, None is inserted.
             4. Headers to lowercase
         '''
-        definition[DEFINITION_TYPE_ROW] = {0:definition[DEFINITION_TYPE_ROW][DEFINITION_TYPE_CELL]}
+        definition[DEFINITION_TYPE_ROW] = {0: definition[DEFINITION_TYPE_ROW][DEFINITION_TYPE_CELL]}
 
         if ATTRIBUTES_HEADER_ROW is not None:
             PoiCleaner.delete_empty_cells_from(definition, ATTRIBUTES_HEADER_ROW)
             if ATTRIBUTES_VALUES_ROW is not None:
                 PoiCleaner.delete_cells_if_no_header(definition, ATTRIBUTES_HEADER_ROW, ATTRIBUTES_VALUES_ROW)
-                PoiCleaner.create_cells_if_no_value_but_header_exists(definition, ATTRIBUTES_HEADER_ROW, ATTRIBUTES_VALUES_ROW)
+                PoiCleaner.create_cells_if_no_value_but_header_exists(definition, ATTRIBUTES_HEADER_ROW,
+                                                                      ATTRIBUTES_VALUES_ROW)
             definition[ATTRIBUTES_HEADER_ROW] = PoiCleaner.dict_values_to_lowercase(definition[ATTRIBUTES_HEADER_ROW])
 
         if PROPERTIES_HEADER_ROW is not None and PoiCleaner.hasProperties(definition, PROPERTIES_HEADER_ROW):
@@ -39,7 +40,8 @@ class PoiCleaner(object):
             if PROPERTIES_VALUES_ROW_START is not None:
                 for property_value_row_num in range(PROPERTIES_VALUES_ROW_START, len(definition)):
                     PoiCleaner.delete_cells_if_no_header(definition, PROPERTIES_HEADER_ROW, property_value_row_num)
-                    PoiCleaner.create_cells_if_no_value_but_header_exists(definition, PROPERTIES_HEADER_ROW, property_value_row_num)
+                    PoiCleaner.create_cells_if_no_value_but_header_exists(definition, PROPERTIES_HEADER_ROW,
+                                                                          property_value_row_num)
             definition[PROPERTIES_HEADER_ROW] = PoiCleaner.dict_values_to_lowercase(definition[PROPERTIES_HEADER_ROW])
 
         return definition
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/poi_to_definition.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/poi_to_definition.py
similarity index 100%
rename from openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/poi_to_definition.py
rename to openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/poi_to_definition.py
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/utils/file_handling.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/utils/file_handling.py
index ec48f01ed85..b053b8ccc91 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/utils/file_handling.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/utils/file_handling.py
@@ -4,4 +4,7 @@ class FileHandler(object):
         self.scripts = scripts
 
     def get_script(self, script_path):
+        print("SSSSSSS")
+        print(script_path)
+        print(self.scripts)
         return self.scripts[script_path]
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java
index 19f8b06059e..19e01483f8d 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java
@@ -10,16 +10,14 @@ import ch.systemsx.cisd.openbis.generic.server.util.TestInitializer;
 import ch.systemsx.cisd.openbis.generic.shared.Constants;
 import ch.systemsx.cisd.openbis.generic.shared.coreplugin.CorePluginsUtils;
 
-public class AbstractImportTest extends AbstractTransactionalTestNGSpringContextTests
-{
+public class AbstractImportTest extends AbstractTransactionalTestNGSpringContextTests {
 
     protected String FILES_DIR;
 
     private String XLS_VERSIONING_DIR = "xls-import.version-data-file";
 
     @BeforeSuite
-    public void setupSuite()
-    {
+    public void setupSuite() {
         System.setProperty(XLS_VERSIONING_DIR, "./versioning.bin");
         System.setProperty(CorePluginsUtils.CORE_PLUGINS_FOLDER_KEY, "dist/core-plugins");
         System.setProperty(Constants.ENABLED_MODULES_KEY, "xls-import");
@@ -27,8 +25,7 @@ public class AbstractImportTest extends AbstractTransactionalTestNGSpringContext
     }
 
     @AfterMethod
-    public void afterTest()
-    {
+    public void afterTest() {
         File f = new File("./versioning.bin");
         f.delete();
     }
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportFromCsvTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportFromCsvTest.java
new file mode 100644
index 00000000000..0bebe62075d
--- /dev/null
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportFromCsvTest.java
@@ -0,0 +1,59 @@
+package ch.ethz.sis.openbis.systemtest.plugin.excelimport;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.Vocabulary;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.transaction.annotation.Transactional;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+import static org.testng.Assert.assertNotNull;
+
+@ContextConfiguration(locations = "classpath:applicationContext.xml")
+@Transactional(transactionManager = "transaction-manager")
+@Rollback
+public class ImportFromCsvTest extends AbstractImportTest {
+
+    @Autowired
+    private IApplicationServerInternalApi v3api;
+
+    private static final String TEST_USER = "test";
+
+    private static final String PASSWORD = "password";
+
+    private static final String FULL_TYPES_CSV = "csv/types.csv";
+
+    private String sessionToken;
+
+    @BeforeClass
+    public void setupClass() throws IOException {
+        String f = ImportExperimentTypesTest.class.getName().replace(".", "/");
+        FILES_DIR = f.substring(0, f.length() - ImportExperimentTypesTest.class.getSimpleName().length()) + "/test_files/";
+    }
+
+    @BeforeMethod
+    public void beforeTest() {
+        sessionToken = v3api.login(TEST_USER, PASSWORD);
+    }
+
+    @Test
+    @DirtiesContext
+    public void testNormalExperimentTypesAreCreated() throws Exception {
+        // GIVEN
+        TestUtils.createFromCsv(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, FULL_TYPES_CSV)));
+        // WHEN
+        Vocabulary detection = TestUtils.getVocabulary(v3api, sessionToken, "DETECTION");
+        // THEN
+        assertNotNull(detection);
+    }
+
+
+}
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportVocabularyTypesTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportVocabularyTypesTest.java
index 4a8f9088f44..9ebd457bd8e 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportVocabularyTypesTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportVocabularyTypesTest.java
@@ -75,7 +75,6 @@ public class ImportVocabularyTypesTest extends AbstractImportTest
     {
         String f = ImportVocabularyTypesTest.class.getName().replace(".", "/");
         FILES_DIR = f.substring(0, f.length() - ImportVocabularyTypesTest.class.getSimpleName().length()) + "/test_files/";
-
     }
 
     @BeforeMethod
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/TestUtils.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/TestUtils.java
index 8991380e794..f81328e301b 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/TestUtils.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/TestUtils.java
@@ -9,6 +9,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 import org.apache.commons.io.IOUtils;
@@ -53,58 +54,51 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.VocabularyPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.search.VocabularySearchCriteria;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi;
 
-public class TestUtils
-{
+public class TestUtils {
     private static final String TEST_XLS = "TEST-XLS";
 
     private static final String XLS_NAME = "xls_name";
 
     private static final String UPDATE_MODE = "update_mode";
 
-    public static final String XLS_PARAM = "xls";
+    private static final String XLS_PARAM = "xls";
 
-    public static final String SCRIPTS_PARAM = "scripts";
+    public static final String CSV_PARAM = "csv";
 
-    public static final String XLS_IMPORT_API = "xls-import-api";
+    private static final String SCRIPTS_PARAM = "scripts";
 
-    static Vocabulary getVocabulary(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    private static final String XLS_IMPORT_API = "xls-import-api";
+
+    static Vocabulary getVocabulary(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         VocabularySearchCriteria criteria = new VocabularySearchCriteria();
         criteria.withId().thatEquals(new VocabularyPermId(code));
-
         VocabularyFetchOptions fo = new VocabularyFetchOptions();
         fo.withTerms();
 
         SearchResult<Vocabulary> result = v3api.searchVocabularies(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static List<Vocabulary> getAllVocabularies(IApplicationServerInternalApi v3api, String sessionToken)
-    {
+    static List<Vocabulary> getAllVocabularies(IApplicationServerInternalApi v3api, String sessionToken) {
         VocabularySearchCriteria criteria = new VocabularySearchCriteria();
         VocabularyFetchOptions fo = new VocabularyFetchOptions();
         fo.withTerms();
 
         SearchResult<Vocabulary> result = v3api.searchVocabularies(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects();
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static SampleType getSampleType(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    static SampleType getSampleType(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         SampleTypeSearchCriteria criteria = new SampleTypeSearchCriteria();
         criteria.withCode().thatEquals(code);
 
@@ -116,17 +110,14 @@ public class TestUtils
 
         SearchResult<SampleType> result = v3api.searchSampleTypes(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static ExperimentType getExperimentType(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    static ExperimentType getExperimentType(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         ExperimentTypeSearchCriteria criteria = new ExperimentTypeSearchCriteria();
         criteria.withCode().thatEquals(code);
 
@@ -138,17 +129,14 @@ public class TestUtils
 
         SearchResult<ExperimentType> result = v3api.searchExperimentTypes(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static DataSetType getDatasetType(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    static DataSetType getDatasetType(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         DataSetTypeSearchCriteria criteria = new DataSetTypeSearchCriteria();
         criteria.withCode().thatEquals(code);
 
@@ -160,17 +148,14 @@ public class TestUtils
 
         SearchResult<DataSetType> result = v3api.searchDataSetTypes(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static PropertyType getPropertyType(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    static PropertyType getPropertyType(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         PropertyTypeSearchCriteria criteria = new PropertyTypeSearchCriteria();
         criteria.withCode().thatEquals(code);
 
@@ -179,17 +164,14 @@ public class TestUtils
 
         SearchResult<PropertyType> result = v3api.searchPropertyTypes(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static Space getSpace(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    static Space getSpace(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         SpaceSearchCriteria criteria = new SpaceSearchCriteria();
         criteria.withCode().thatEquals(code);
 
@@ -197,17 +179,14 @@ public class TestUtils
 
         SearchResult<Space> result = v3api.searchSpaces(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static Project getProject(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    static Project getProject(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         ProjectSearchCriteria criteria = new ProjectSearchCriteria();
         criteria.withCode().thatEquals(code);
 
@@ -216,18 +195,15 @@ public class TestUtils
 
         SearchResult<Project> result = v3api.searchProjects(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
     static Experiment getExperiment(IApplicationServerInternalApi v3api, String sessionToken, String experimentCode, String projectCode,
-            String spaceCode)
-    {
+                                    String spaceCode) {
         List<IExperimentId> ids = new ArrayList<>();
         ids.add(new ExperimentIdentifier(spaceCode, projectCode, experimentCode));
 
@@ -238,33 +214,28 @@ public class TestUtils
 
         List<Experiment> result = v3api.getExperiments(sessionToken, ids, fo).values().stream().collect(Collectors.toList());
 
-        if (result.size() > 0)
-        {
+        if (result.size() > 0) {
             return result.get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static Sample getSample(IApplicationServerInternalApi v3api, String sessionToken, String sampleCode, String spaceCode)
-    {
+    static Sample getSample(IApplicationServerInternalApi v3api, String sessionToken, String sampleCode, String spaceCode) {
         List<ISampleId> ids = new ArrayList<>();
         ids.add(new SampleIdentifier(spaceCode, null, null, sampleCode));
 
         return getSamples(v3api, sessionToken, ids);
     }
 
-    static Sample getSampleByPermId(IApplicationServerInternalApi v3api, String sessionToken, String permId)
-    {
+    static Sample getSampleByPermId(IApplicationServerInternalApi v3api, String sessionToken, String permId) {
         List<ISampleId> ids = new ArrayList<>();
         ids.add(new SamplePermId(permId));
 
         return getSamples(v3api, sessionToken, ids);
     }
 
-    private static Sample getSamples(IApplicationServerInternalApi v3api, String sessionToken, List<ISampleId> ids)
-    {
+    private static Sample getSamples(IApplicationServerInternalApi v3api, String sessionToken, List<ISampleId> ids) {
         SampleFetchOptions fo = new SampleFetchOptions();
         SampleFetchOptions childrenFo = fo.withChildren();
         childrenFo.withSpace();
@@ -280,20 +251,16 @@ public class TestUtils
 
         List<Sample> result = v3api.getSamples(sessionToken, ids, fo).values().stream().collect(Collectors.toList());
 
-        if (result.size() > 0)
-        {
+        if (result.size() > 0) {
             return result.get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static String createFrom(IApplicationServerInternalApi v3api, String sessionToken, Path... xls_paths) throws IOException
-    {
+    static String createFrom(IApplicationServerInternalApi v3api, String sessionToken, Path... xls_paths) throws IOException {
         List<byte[]> excels = new ArrayList<>();
-        for (Path xls_path : xls_paths)
-        {
+        for (Path xls_path : xls_paths) {
             byte[] xls = readData(xls_path);
             excels.add(xls);
         }
@@ -304,18 +271,28 @@ public class TestUtils
         return (String) v3api.executeCustomASService(sessionToken, new CustomASServiceCode(XLS_IMPORT_API), options);
     }
 
+    static String createFromCsv(IApplicationServerInternalApi v3api, String sessionToken, Path... csv_paths) throws IOException {
+        List<byte[]> csvs = new ArrayList<>();
+        for (Path csv_path : csv_paths) {
+            byte[] csv = readData(csv_path);
+            csvs.add(csv);
+        }
+        CustomASServiceExecutionOptions options = new CustomASServiceExecutionOptions();
+        options.withParameter(CSV_PARAM, csvs);
+        options.withParameter(UPDATE_MODE, UpdateMode.IGNORE_EXISTING.name());
+        options.withParameter(XLS_NAME, TEST_XLS);
+        return (String) v3api.executeCustomASService(sessionToken, new CustomASServiceCode(XLS_IMPORT_API), options);
+    }
+
     static String createFrom(IApplicationServerInternalApi v3api, String sessionToken, Map<String, String> scripts, Path... xls_paths)
-            throws IOException
-    {
+            throws IOException {
         return TestUtils.createFrom(v3api, sessionToken, scripts, UpdateMode.IGNORE_EXISTING, xls_paths);
     }
 
     static String createFrom(IApplicationServerInternalApi v3api, String sessionToken, Map<String, String> scripts, UpdateMode updateMode,
-            Path... xls_paths) throws IOException
-    {
+                             Path... xls_paths) throws IOException {
         List<byte[]> excels = new ArrayList<>();
-        for (Path xls_path : xls_paths)
-        {
+        for (Path xls_path : xls_paths) {
             byte[] xls = readData(xls_path);
             excels.add(xls);
         }
@@ -327,18 +304,15 @@ public class TestUtils
         return (String) v3api.executeCustomASService(sessionToken, new CustomASServiceCode(XLS_IMPORT_API), options);
     }
 
-    static String getValidationScript()
-    {
+    static String getValidationScript() {
         return "def validate(entity, isNew):\n  if isNew:\n    return";
     }
 
-    static String getDynamicScript()
-    {
+    static String getDynamicScript() {
         return "def calculate():\n    return 1";
     }
 
-    static Map<String, String> getValidationPluginMap()
-    {
+    static Map<String, String> getValidationPluginMap() {
         String dynamicScriptString = getValidationScript();
         Map<String, String> scriptsMap = new HashMap<>();
         scriptsMap.put("valid.py", dynamicScriptString);
@@ -346,8 +320,7 @@ public class TestUtils
         return scriptsMap;
     }
 
-    static Map<String, String> getDynamicPluginMap()
-    {
+    static Map<String, String> getDynamicPluginMap() {
         String dynamicScriptString = getDynamicScript();
         Map<String, String> scriptsMap = new HashMap<>();
         scriptsMap.put("dynamic/dynamic.py", dynamicScriptString);
@@ -355,8 +328,7 @@ public class TestUtils
         return scriptsMap;
     }
 
-    static String extractSamplePermIdFromResults(String result)
-    {
+    static String extractSamplePermIdFromResults(String result) {
         // Note this will work only if we created single sample!!
         String permId = result.substring(result.indexOf("CreateSamplesOperationResult") + "CreateSamplesOperationResult".length());
         permId = StringUtils.strip(permId, "[]");
@@ -364,15 +336,13 @@ public class TestUtils
     }
 
     static List<PropertyAssignment> extractAndSortPropertyAssignmentsPerGivenPropertyName(IEntityType rawData, List<String> propertyNames)
-            throws Exception
-    {
+            throws Exception {
         List<PropertyAssignment> propertyAssignments = rawData.getPropertyAssignments();
         List<PropertyAssignment> sortedPropertyAssignments = propertyNames.stream().map(propertyName -> {
             return propertyAssignments.stream().filter(prop -> prop.getPropertyType().getPermId().toString().equals(propertyName)).findFirst().get();
         }).collect(Collectors.toList());
 
-        if (sortedPropertyAssignments.stream().anyMatch(property -> property == null))
-        {
+        if (sortedPropertyAssignments.stream().anyMatch(Objects::isNull)) {
             throw new Exception("Some properties are missing"
                     + "\nFollowing properties are expected " + Arrays.toString(propertyNames.toArray())
                     + "\n Available properties are: " + Arrays.toString(propertyAssignments.toArray()));
@@ -381,21 +351,12 @@ public class TestUtils
         return sortedPropertyAssignments;
     }
 
-    private static byte[] readData(Path xls_path) throws IOException
-    {
+    private static byte[] readData(Path xls_path) throws IOException {
         String path = xls_path.toString();
-        InputStream resourceAsStream = TestUtils.class.getClassLoader().getResourceAsStream(path);
-        try
-        {
+        try (InputStream resourceAsStream = TestUtils.class.getClassLoader().getResourceAsStream(path)) {
             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             IOUtils.copy(resourceAsStream, byteArrayOutputStream);
             return byteArrayOutputStream.toByteArray();
-        } finally
-        {
-            if (resourceAsStream != null)
-            {
-                resourceAsStream.close();
-            }
         }
     }
 
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/csv/types.csv b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/csv/types.csv
new file mode 100644
index 00000000000..f316aaed6c9
--- /dev/null
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/csv/types.csv
@@ -0,0 +1,7 @@
+VOCABULARY_TYPE,,,,,,,,,,,,,,,,,
+Version,Code,Description,,,,,,,,,,,,,,,
+225,DETECTION,Protein detection system,,,,,,,,,,,,,,,
+Version,Code,Label,Description,,,,,,,,,,,,,,
+225,HRP,horseradish peroxydase,The antibody is conjugated with the horseradish peroxydase,,,,,,,,,,,,,,
+,,,,,,,,,,,,,,,,,
+,,,,,,,,,,,,,,,,,
-- 
GitLab