From 79846211750147bc989d4a748bf33c3d3eb35d2c Mon Sep 17 00:00:00 2001
From: felmer <felmer>
Date: Wed, 16 May 2012 06:24:19 +0000
Subject: [PATCH] SP-39 *TypeBuilder refactored and extended.
 MaterialReportingTask: Checking of mapping file and report database schema by
 material types as defined in openBIS.

SVN: 25270
---
 .../generic/server/CommonServerLogger.java    |   9 +-
 .../server/task/MaterialReportingTask.java    | 140 ++++++++++----
 .../task/MaterialReportingTaskTest.java       | 179 ++++++++++++++++++
 .../builders/AbstractEntityTypeBuilder.java   |  21 +-
 .../dto/builders/DataSetTypeBuilder.java      |  13 +-
 .../dto/builders/ExperimentTypeBuilder.java   |  33 +---
 .../dto/builders/MaterialTypeBuilder.java     |  13 +-
 .../basic/dto/builders/SampleTypeBuilder.java |  35 +---
 8 files changed, 353 insertions(+), 90 deletions(-)

diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java
index ed2386b58a7..54b78475737 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java
@@ -539,7 +539,7 @@ final class CommonServerLogger extends AbstractServerLogger implements ICommonSe
 
     public MaterialType getMaterialType(String sessionToken, String code)
     {
-        logAccess(sessionToken, "list_material_types");
+        logAccess(sessionToken, "get_material_type", "CODE(%s)", code);
         return null;
     }
 
@@ -1219,10 +1219,11 @@ final class CommonServerLogger extends AbstractServerLogger implements ICommonSe
         return null;
     }
 
-    public TableModel createReportFromAggregationService(String sessionToken, DatastoreServiceDescription serviceDescription, Map<String, Object> parameters)
+    public TableModel createReportFromAggregationService(String sessionToken,
+            DatastoreServiceDescription serviceDescription, Map<String, Object> parameters)
     {
-        logAccess(sessionToken, "createReportFromAggregationService", "SERVICE(%s), PARAMETERS(%s)",
-                serviceDescription, parameters);
+        logAccess(sessionToken, "createReportFromAggregationService",
+                "SERVICE(%s), PARAMETERS(%s)", serviceDescription, parameters);
         return null;
     }
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/task/MaterialReportingTask.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/task/MaterialReportingTask.java
index 42c006cbe8c..315e0c0d048 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/task/MaterialReportingTask.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/task/MaterialReportingTask.java
@@ -64,6 +64,8 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchField;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialAttributeSearchFieldKind;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialTypePropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SearchCriteriaConnection;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SessionContextDTO;
@@ -137,6 +139,11 @@ public class MaterialReportingTask implements IMaintenanceTask
             this.codeColumnName = codeColumnName;
         }
 
+        String getMaterialTypeCode()
+        {
+            return materialTypeCode;
+        }
+
         String getTableName()
         {
             return tableName;
@@ -158,7 +165,8 @@ public class MaterialReportingTask implements IMaintenanceTask
             propertyMapping.put(propertyTypeCode, column);
         }
 
-        void injectDataTypeCodes(Map<String, DataTypeCode> columns)
+        void injectDataTypeCodes(Map<String, DataTypeCode> columns,
+                Map<String, PropertyType> propertyTypes)
         {
             DataTypeCode codeColumnType = columns.remove(codeColumnName);
             if (codeColumnType == null)
@@ -171,19 +179,48 @@ public class MaterialReportingTask implements IMaintenanceTask
                 throw new EnvironmentFailureException("Column '" + codeColumnName + "' of table '"
                         + tableName + "' is not of type VARCHAR.");
             }
-            Collection<Column> values = propertyMapping.values();
-            for (Column column : values)
+            for (Entry<String, Column> entry : propertyMapping.entrySet())
             {
+                String propertyTypeCode = entry.getKey();
+                PropertyType propertyType = propertyTypes.get(propertyTypeCode);
+                if (propertyType == null)
+                {
+                    throw new ConfigurationFailureException(
+                            "Mapping file refers to an unknown property type: " + propertyTypeCode);
+                }
+                Column column = entry.getValue();
                 DataTypeCode dataTypeCode = columns.get(column.name);
                 if (dataTypeCode == null)
                 {
                     throw new EnvironmentFailureException("Missing column '" + column.name
                             + "' in table '" + tableName + "' of report database.");
                 }
+                DataTypeCode correspondingType =
+                        getCorrespondingType(propertyType.getDataType().getCode());
+                if (dataTypeCode.equals(correspondingType) == false)
+                {
+                    throw new EnvironmentFailureException("Column '" + column.name + "' in table '"
+                            + tableName
+                            + "' of report database should be of a type which corresponds to "
+                            + correspondingType + ".");
+                }
                 column.dataTypeCode = dataTypeCode;
             }
         }
 
+        private DataTypeCode getCorrespondingType(DataTypeCode code)
+        {
+            switch (code)
+            {
+                case INTEGER:
+                case REAL:
+                case TIMESTAMP:
+                    return code;
+                default:
+                    return DataTypeCode.VARCHAR;
+            }
+        }
+
         Map<String, Map<String, Object>> groupByMaterials(List<Map<String, Object>> rows)
         {
             HashMap<String, Map<String, Object>> result =
@@ -335,9 +372,17 @@ public class MaterialReportingTask implements IMaintenanceTask
         // "write-timestamp-sql");
         String mappingFileName = PropertyUtils.getMandatoryProperty(properties, MAPPING_FILE_KEY);
         mapping = readMappingFile(mappingFileName);
+        Map<String, Map<String, PropertyType>> materialTypes = getMaterialTypes();
         Map<String, Map<String, DataTypeCode>> metaData = retrieveDatabaseMetaData();
         for (MappingInfo mappingInfo : mapping.values())
         {
+            String materialTypeCode = mappingInfo.getMaterialTypeCode();
+            Map<String, PropertyType> propertyTypes = materialTypes.get(materialTypeCode);
+            if (propertyTypes == null)
+            {
+                throw new ConfigurationFailureException(
+                        "Mapping file refers to an unknown material type: " + materialTypeCode);
+            }
             String tableName = mappingInfo.getTableName();
             Map<String, DataTypeCode> columns = metaData.get(tableName);
             if (columns == null)
@@ -345,11 +390,60 @@ public class MaterialReportingTask implements IMaintenanceTask
                 throw new EnvironmentFailureException("Missing table '" + tableName
                         + "' in report database.");
             }
-            mappingInfo.injectDataTypeCodes(columns);
+            mappingInfo.injectDataTypeCodes(columns, propertyTypes);
         }
         jdbcTemplate = new JdbcTemplate(dbConfigurationContext.getDataSource());
     }
 
+    public void execute()
+    {
+        SessionContextDTO contextOrNull = server.tryToAuthenticateAsSystem();
+        if (contextOrNull == null)
+        {
+            return;
+        }
+        operationLog.info("Start reporting added or changed materials to the report database.");
+        Map<String, List<Material>> materialsByType =
+                getRecentlyAddedOrChangedMaterials(contextOrNull.getSessionToken());
+        for (Entry<String, List<Material>> entry : materialsByType.entrySet())
+        {
+            String materialTypeCode = entry.getKey();
+            final List<Material> materials = entry.getValue();
+            MappingInfo mappingInfo = mapping.get(materialTypeCode);
+            if (mappingInfo != null)
+            {
+                addOrUpdate(mappingInfo, materials);
+            }
+        }
+        operationLog.info("Reporting finished.");
+    }
+
+    private Map<String, Map<String, PropertyType>> getMaterialTypes()
+    {
+        SessionContextDTO contextOrNull = server.tryToAuthenticateAsSystem();
+        if (contextOrNull == null)
+        {
+            throw new EnvironmentFailureException("Can not authenticate as system.");
+        }
+        List<MaterialType> materialTypes =
+                server.listMaterialTypes(contextOrNull.getSessionToken());
+        Map<String, Map<String, PropertyType>> result =
+                new HashMap<String, Map<String, PropertyType>>();
+        for (MaterialType materialType : materialTypes)
+        {
+            List<MaterialTypePropertyType> assignedPropertyTypes =
+                    materialType.getAssignedPropertyTypes();
+            Map<String, PropertyType> propertyTypes = new HashMap<String, PropertyType>();
+            for (MaterialTypePropertyType materialTypePropertyType : assignedPropertyTypes)
+            {
+                PropertyType propertyType = materialTypePropertyType.getPropertyType();
+                propertyTypes.put(propertyType.getCode(), propertyType);
+            }
+            result.put(materialType.getCode(), propertyTypes);
+        }
+        return result;
+    }
+
     private Map<String, Map<String, DataTypeCode>> retrieveDatabaseMetaData()
     {
         Collection<MappingInfo> values = mapping.values();
@@ -365,7 +459,6 @@ public class MaterialReportingTask implements IMaintenanceTask
             JdbcUtils.extractDatabaseMetaData(dbConfigurationContext.getDataSource(),
                     new DatabaseMetaDataCallback()
                         {
-
                             public Object processMetaData(DatabaseMetaData metaData)
                                     throws SQLException, MetaDataAccessException
                             {
@@ -383,12 +476,13 @@ public class MaterialReportingTask implements IMaintenanceTask
                                         }
                                         String columnName =
                                                 rs.getString("COLUMN_NAME").toLowerCase();
+                                        int sqlTypeCode = rs.getInt("DATA_TYPE");
                                         DataTypeCode dataTypeCode =
-                                                DataTypeUtils.getDataTypeCode(rs
-                                                        .getInt("DATA_TYPE"));
+                                                DataTypeUtils.getDataTypeCode(sqlTypeCode);
                                         columns.put(columnName, dataTypeCode);
                                     }
                                 }
+                                rs.close();
                                 return null;
                             }
                         });
@@ -399,31 +493,6 @@ public class MaterialReportingTask implements IMaintenanceTask
         }
     }
 
-    public void execute()
-    {
-        SessionContextDTO contextOrNull = server.tryToAuthenticateAsSystem();
-        if (contextOrNull == null)
-        {
-            return;
-        }
-        String sessionToken = contextOrNull.getSessionToken();
-
-        Map<String, List<Material>> materialsByType =
-                getRecentlyAddedOrChangedMaterials(sessionToken);
-        for (Entry<String, List<Material>> entry : materialsByType.entrySet())
-        {
-            String materialTypeCode = entry.getKey();
-            final List<Material> materials = entry.getValue();
-            MappingInfo mappingInfo = mapping.get(materialTypeCode);
-            if (mappingInfo != null)
-            {
-                addOrUpdate(mappingInfo, materials);
-                operationLog.info(materials.size() + " materials of type " + materialTypeCode
-                        + " reported.");
-            }
-        }
-    }
-
     private void addOrUpdate(MappingInfo mappingInfo, final List<Material> materials)
     {
         String sql = mappingInfo.createSelectStatement(materials);
@@ -447,12 +516,17 @@ public class MaterialReportingTask implements IMaintenanceTask
             String updateStatement = mappingInfo.createUpdateStatement();
             jdbcTemplate.batchUpdate(updateStatement,
                     mappingInfo.createSetter(updateMaterials, IndexingSchema.UPDATE));
+            operationLog.info(updateMaterials.size() + " materials of type "
+                    + mappingInfo.getMaterialTypeCode() + " have been updated in report database.");
         }
         if (newMaterials.isEmpty() == false)
         {
             String insertStatement = mappingInfo.createInsertStatement();
             jdbcTemplate.batchUpdate(insertStatement,
                     mappingInfo.createSetter(newMaterials, IndexingSchema.INSERT));
+            operationLog.info(newMaterials.size() + " materials of type "
+                    + mappingInfo.getMaterialTypeCode()
+                    + " have been inserted into report database.");
         }
     }
 
@@ -498,7 +572,7 @@ public class MaterialReportingTask implements IMaintenanceTask
 
     private String readTimestamp()
     {
-        return "2012-02-22 10:33:44.6667";
+        return "2012-02-20 10:33:44.6667";
     }
 
     @Private
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/task/MaterialReportingTaskTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/task/MaterialReportingTaskTest.java
index 0c2cefdecaa..e3cfb0631d7 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/task/MaterialReportingTaskTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/task/MaterialReportingTaskTest.java
@@ -20,6 +20,7 @@ import java.io.File;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
@@ -48,7 +49,9 @@ import ch.systemsx.cisd.openbis.generic.server.task.MaterialReportingTask.Mappin
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.MaterialBuilder;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.MaterialTypeBuilder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.VocabularyTermBuilder;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SessionContextDTO;
 
@@ -277,6 +280,7 @@ public class MaterialReportingTaskTest extends AbstractFileSystemTestCase
     public void testDatabaseMetaDataMissingCodeColumn() throws Exception
     {
         FileUtilities.writeToFile(mappingFile, "[T1:REPORT1,C]");
+        prepareListMaterialTypes("T1:A=REAL");
         try
         {
             materialReportingTask.setUp("", properties);
@@ -286,12 +290,14 @@ public class MaterialReportingTaskTest extends AbstractFileSystemTestCase
             assertEquals("Missing column 'c' in table 'report1' of report database.",
                     ex.getMessage());
         }
+        context.assertIsSatisfied();
     }
 
     @Test
     public void testDatabaseMetaDataMissingTable() throws Exception
     {
         FileUtilities.writeToFile(mappingFile, "[T1:REPORT3,C]");
+        prepareListMaterialTypes("T1:A=REAL");
         try
         {
             materialReportingTask.setUp("", properties);
@@ -300,12 +306,14 @@ public class MaterialReportingTaskTest extends AbstractFileSystemTestCase
         {
             assertEquals("Missing table 'report3' in report database.", ex.getMessage());
         }
+        context.assertIsSatisfied();
     }
 
     @Test
     public void testDatabaseMetaDataCodeColumnOfWrongType() throws Exception
     {
         FileUtilities.writeToFile(mappingFile, "[T1:REPORT1,ID]");
+        prepareListMaterialTypes("T1:A=REAL");
         try
         {
             materialReportingTask.setUp("", properties);
@@ -314,12 +322,14 @@ public class MaterialReportingTaskTest extends AbstractFileSystemTestCase
         {
             assertEquals("Column 'id' of table 'report1' is not of type VARCHAR.", ex.getMessage());
         }
+        context.assertIsSatisfied();
     }
 
     @Test
     public void testDatabaseMetaDataMissingPropertyColumn() throws Exception
     {
         FileUtilities.writeToFile(mappingFile, "[T1:REPORT2,CODE]\nP1:my_prop");
+        prepareListMaterialTypes("T1:P1=REAL");
         try
         {
             materialReportingTask.setUp("", properties);
@@ -329,6 +339,141 @@ public class MaterialReportingTaskTest extends AbstractFileSystemTestCase
             assertEquals("Missing column 'my_prop' in table 'report2' of report database.",
                     ex.getMessage());
         }
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public void testMappingFileWithUnknownMaterialType() throws Exception
+    {
+        FileUtilities.writeToFile(mappingFile, "[T1:REPORT2,CODE]\nP1:my_prop");
+        prepareListMaterialTypes("T2:P1=REAL");
+        try
+        {
+            materialReportingTask.setUp("", properties);
+            fail("ConfigurationFailureException expected");
+        } catch (ConfigurationFailureException ex)
+        {
+            assertEquals("Mapping file refers to an unknown material type: T1", ex.getMessage());
+        }
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public void testMappingFileWithUnknownPropertyType() throws Exception
+    {
+        FileUtilities.writeToFile(mappingFile, "[T1:REPORT2,CODE]\nP1:my_prop");
+        prepareListMaterialTypes("T1:P2=REAL");
+        try
+        {
+            materialReportingTask.setUp("", properties);
+            fail("ConfigurationFailureException expected");
+        } catch (ConfigurationFailureException ex)
+        {
+            assertEquals("Mapping file refers to an unknown property type: P1", ex.getMessage());
+        }
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public void testMappingFileWithNonmatchingDataTypeREAL() throws Exception
+    {
+        FileUtilities.writeToFile(mappingFile, "[T1:REPORT2,CODE]\nP1:rank");
+        prepareListMaterialTypes("T1:P1=REAL");
+        try
+        {
+            materialReportingTask.setUp("", properties);
+            fail("EnvironmentFailureException expected");
+        } catch (EnvironmentFailureException ex)
+        {
+            assertEquals("Column 'rank' in table 'report2' of report database should be of "
+                    + "a type which corresponds to REAL.", ex.getMessage());
+        }
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public void testMappingFileWithNonmatchingDataTypeINTEGER() throws Exception
+    {
+        FileUtilities.writeToFile(mappingFile, "[T1:REPORT2,CODE]\nP1:greetings");
+        prepareListMaterialTypes("T1:P1=INTEGER");
+        try
+        {
+            materialReportingTask.setUp("", properties);
+            fail("EnvironmentFailureException expected");
+        } catch (EnvironmentFailureException ex)
+        {
+            assertEquals("Column 'greetings' in table 'report2' of report database should be of "
+                    + "a type which corresponds to INTEGER.", ex.getMessage());
+        }
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public void testMappingFileWithNonmatchingDataTypeTIMESTAMP() throws Exception
+    {
+        FileUtilities.writeToFile(mappingFile, "[T1:REPORT2,CODE]\nP1:greetings");
+        prepareListMaterialTypes("T1:P1=TIMESTAMP");
+        try
+        {
+            materialReportingTask.setUp("", properties);
+            fail("EnvironmentFailureException expected");
+        } catch (EnvironmentFailureException ex)
+        {
+            assertEquals("Column 'greetings' in table 'report2' of report database should be of "
+                    + "a type which corresponds to TIMESTAMP.", ex.getMessage());
+        }
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public void testMappingFileWithNonmatchingDataTypeMATERIAL() throws Exception
+    {
+        FileUtilities.writeToFile(mappingFile, "[T1:REPORT2,CODE]\nP1:RANK");
+        prepareListMaterialTypes("T1:P1=MATERIAL");
+        try
+        {
+            materialReportingTask.setUp("", properties);
+            fail("EnvironmentFailureException expected");
+        } catch (EnvironmentFailureException ex)
+        {
+            assertEquals("Column 'rank' in table 'report2' of report database should be of "
+                    + "a type which corresponds to VARCHAR.", ex.getMessage());
+        }
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public void testMappingFileWithNonmatchingDataTypeCONTROLLEDVOCABULARY() throws Exception
+    {
+        FileUtilities.writeToFile(mappingFile, "[T1:REPORT2,CODE]\nP1:RANK");
+        prepareListMaterialTypes("T1:P1=CONTROLLEDVOCABULARY");
+        try
+        {
+            materialReportingTask.setUp("", properties);
+            fail("EnvironmentFailureException expected");
+        } catch (EnvironmentFailureException ex)
+        {
+            assertEquals("Column 'rank' in table 'report2' of report database should be of "
+                    + "a type which corresponds to VARCHAR.", ex.getMessage());
+        }
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public void testMappingFileWithNonmatchingDataTypeVARCHAR() throws Exception
+    {
+        FileUtilities.writeToFile(mappingFile, "[T1:REPORT2,CODE]\nP1:RANK");
+        prepareListMaterialTypes("T1:P1=VARCHAR");
+        try
+        {
+            materialReportingTask.setUp("", properties);
+            fail("EnvironmentFailureException expected");
+        } catch (EnvironmentFailureException ex)
+        {
+            assertEquals("Column 'rank' in table 'report2' of report database should be of "
+                    + "a type which corresponds to VARCHAR.", ex.getMessage());
+        }
+        context.assertIsSatisfied();
     }
 
     @Test
@@ -337,6 +482,8 @@ public class MaterialReportingTaskTest extends AbstractFileSystemTestCase
         FileUtilities.writeToFile(mappingFile, "# my mapping\n[T1:REPORT1,CODE]\n\n"
                 + "[T2: REPORT2, code]\nM:MATERIAL\nS:size\nP2: GREETINGS\nP1:RANK\nORG:ORGANISM\n"
                 + "T:timestamp");
+        prepareListMaterialTypes("T1:D=VARCHAR",
+                "T2:M=MATERIAL,S=REAL,P2=VARCHAR,P1=INTEGER,ORG=CONTROLLEDVOCABULARY,T=TIMESTAMP");
         materialReportingTask.setUp("", properties);
         final Material m1 =
                 new MaterialBuilder().code("M1").type("T1").property("P1", "42").getMaterial();
@@ -383,6 +530,8 @@ public class MaterialReportingTaskTest extends AbstractFileSystemTestCase
         FileUtilities.writeToFile(mappingFile, "[T1:REPORT1,CODE]\n"
                 + "[T2: REPORT2, code]\nM:MATERIAL\nS:size\nP1:RANK\nORG:ORGANISM\n"
                 + "P2: GREETINGS\nT:timestamp");
+        prepareListMaterialTypes("T1:D=VARCHAR",
+                "T2:M=MATERIAL,S=REAL,P2=VARCHAR,P1=INTEGER,ORG=CONTROLLEDVOCABULARY,T=TIMESTAMP");
         materialReportingTask.setUp("", properties);
         final Material m1 =
                 new MaterialBuilder().code("M1").type("T1").property("P1", "42").getMaterial();
@@ -434,6 +583,36 @@ public class MaterialReportingTaskTest extends AbstractFileSystemTestCase
         context.assertIsSatisfied();
     }
 
+    private void prepareListMaterialTypes(final String... materialTypeDescriptions)
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(server).tryToAuthenticateAsSystem();
+                    SessionContextDTO session = new SessionContextDTO();
+                    session.setSessionToken(SESSION_TOKEN);
+                    will(returnValue(session));
+
+                    List<MaterialType> materialTypes = new ArrayList<MaterialType>();
+                    for (String description : materialTypeDescriptions)
+                    {
+                        String[] split1 = description.split(":");
+                        MaterialTypeBuilder builder = new MaterialTypeBuilder().code(split1[0]);
+                        String[] split2 = split1[1].split(",");
+                        for (String propertyDescription : split2)
+                        {
+                            String[] split3 = propertyDescription.split("=");
+                            builder.propertyType(split3[0], split3[0],
+                                    DataTypeCode.valueOf(split3[1]));
+                        }
+                        materialTypes.add(builder.getMaterialType());
+                    }
+                    one(server).listMaterialTypes(SESSION_TOKEN);
+                    will(returnValue(materialTypes));
+                }
+            });
+    }
+
     private List<?> loadTable(String tableName)
     {
         return new JdbcTemplate(dbConfigContext.getDataSource()).query("select * from " + tableName
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/AbstractEntityTypeBuilder.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/AbstractEntityTypeBuilder.java
index 78ed48b7c97..6f4b53e3a00 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/AbstractEntityTypeBuilder.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/AbstractEntityTypeBuilder.java
@@ -16,13 +16,30 @@
 
 package ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders;
 
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityTypePropertyType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
 
 /**
  * Abstract super class of builder of subclasses of {@link EntityType}.
- *
+ * 
  * @author Franz-Josef Elmer
  */
-abstract class AbstractEntityTypeBuilder
+abstract class AbstractEntityTypeBuilder<E extends EntityType>
 {
+    protected void fillEntityTypePropertyType(E entityType,
+            EntityTypePropertyType<E> entityTypePropertyType, String code, String label,
+            DataTypeCode type)
+    {
+        PropertyType propertyType = new PropertyType();
+        propertyType.setCode(code);
+        propertyType.setSimpleCode(code);
+        propertyType.setLabel(label);
+        propertyType.setDataType(new DataType(type));
+        entityTypePropertyType.setPropertyType(propertyType);
+        entityTypePropertyType.setEntityType(entityType);
+    }
+
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/DataSetTypeBuilder.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/DataSetTypeBuilder.java
index 77e12f152c2..16fe1881d8d 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/DataSetTypeBuilder.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/DataSetTypeBuilder.java
@@ -17,16 +17,18 @@
 package ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders;
 
 import java.util.ArrayList;
+import java.util.List;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetTypePropertyType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
 
 /**
  * Builder class of {@link DataSetType} instances.
  * 
  * @author Franz-Josef Elmer
  */
-public class DataSetTypeBuilder extends AbstractEntityTypeBuilder
+public class DataSetTypeBuilder extends AbstractEntityTypeBuilder<DataSetType>
 {
     private DataSetType dataSetType = new DataSetType();
 
@@ -45,4 +47,13 @@ public class DataSetTypeBuilder extends AbstractEntityTypeBuilder
         dataSetType.setCode(code);
         return this;
     }
+
+    public DataSetTypeBuilder propertyType(String code, String label, DataTypeCode dataType)
+    {
+        DataSetTypePropertyType entityTypePropertyType = new DataSetTypePropertyType();
+        List<DataSetTypePropertyType> types = dataSetType.getAssignedPropertyTypes();
+        fillEntityTypePropertyType(dataSetType, entityTypePropertyType, code, label, dataType);
+        types.add(entityTypePropertyType);
+        return this;
+    }
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/ExperimentTypeBuilder.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/ExperimentTypeBuilder.java
index 81e589bee96..54bd33344d3 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/ExperimentTypeBuilder.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/ExperimentTypeBuilder.java
@@ -19,54 +19,39 @@ package ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders;
 import java.util.ArrayList;
 import java.util.List;
 
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentTypePropertyType;
 
 /**
- * 
- *
  * @author Franz-Josef Elmer
  */
-public class ExperimentTypeBuilder extends AbstractEntityTypeBuilder
+public class ExperimentTypeBuilder extends AbstractEntityTypeBuilder<ExperimentType>
 {
     private ExperimentType experimentType = new ExperimentType();
-    
+
     public ExperimentTypeBuilder()
     {
         experimentType.setExperimentTypePropertyTypes(new ArrayList<ExperimentTypePropertyType>());
     }
-    
+
     public ExperimentTypeBuilder code(String code)
     {
         experimentType.setCode(code);
         return this;
     }
-    
+
     public ExperimentType getExperimentType()
     {
         return experimentType;
     }
-    
+
     public ExperimentTypeBuilder propertyType(String code, String label, DataTypeCode dataType)
     {
-        addPropertyType(experimentType, new ExperimentTypePropertyType(), code, label, dataType);
-        return this;
-    }
-    
-    protected void addPropertyType(ExperimentType entityType, ExperimentTypePropertyType entityTypePropertyType, String code, String label,
-            DataTypeCode type)
-    {
-        List<ExperimentTypePropertyType> types = entityType.getAssignedPropertyTypes();
-        PropertyType propertyType = new PropertyType();
-        propertyType.setCode(code);
-        propertyType.setSimpleCode(code);
-        propertyType.setLabel(label);
-        propertyType.setDataType(new DataType(type));
-        entityTypePropertyType.setPropertyType(propertyType);
-        entityTypePropertyType.setEntityType(entityType);
+        ExperimentTypePropertyType entityTypePropertyType = new ExperimentTypePropertyType();
+        List<ExperimentTypePropertyType> types = experimentType.getAssignedPropertyTypes();
+        fillEntityTypePropertyType(experimentType, entityTypePropertyType, code, label, dataType);
         types.add(entityTypePropertyType);
+        return this;
     }
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/MaterialTypeBuilder.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/MaterialTypeBuilder.java
index 0e2e6556a52..d6140ae205b 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/MaterialTypeBuilder.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/MaterialTypeBuilder.java
@@ -17,7 +17,9 @@
 package ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders;
 
 import java.util.ArrayList;
+import java.util.List;
 
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialTypePropertyType;
 
@@ -26,7 +28,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialTypePropertyTyp
  * 
  * @author Franz-Josef Elmer
  */
-public class MaterialTypeBuilder extends AbstractEntityTypeBuilder
+public class MaterialTypeBuilder extends AbstractEntityTypeBuilder<MaterialType>
 {
     private MaterialType materialType = new MaterialType();
 
@@ -45,4 +47,13 @@ public class MaterialTypeBuilder extends AbstractEntityTypeBuilder
         materialType.setCode(code);
         return this;
     }
+
+    public MaterialTypeBuilder propertyType(String code, String label, DataTypeCode dataType)
+    {
+        MaterialTypePropertyType entityTypePropertyType = new MaterialTypePropertyType();
+        List<MaterialTypePropertyType> types = materialType.getAssignedPropertyTypes();
+        fillEntityTypePropertyType(materialType, entityTypePropertyType, code, label, dataType);
+        types.add(entityTypePropertyType);
+        return this;
+    }
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/SampleTypeBuilder.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/SampleTypeBuilder.java
index cfaacefbd2e..e847d2d5804 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/SampleTypeBuilder.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/SampleTypeBuilder.java
@@ -19,60 +19,45 @@ package ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders;
 import java.util.ArrayList;
 import java.util.List;
 
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleTypePropertyType;
 
 /**
- * 
- *
  * @author Franz-Josef Elmer
  */
-public class SampleTypeBuilder extends AbstractEntityTypeBuilder
+public class SampleTypeBuilder extends AbstractEntityTypeBuilder<SampleType>
 {
     private SampleType sampleType = new SampleType();
-    
+
     public SampleTypeBuilder()
     {
         sampleType.setSampleTypePropertyTypes(new ArrayList<SampleTypePropertyType>());
     }
-    
+
     public SampleTypeBuilder id(long id)
     {
         sampleType.setId(id);
         return this;
     }
-    
+
     public SampleTypeBuilder code(String code)
     {
         sampleType.setCode(code);
         return this;
     }
-    
+
     public SampleType getSampleType()
     {
         return sampleType;
     }
-    
+
     public SampleTypeBuilder propertyType(String code, String label, DataTypeCode dataType)
     {
-        addPropertyType(sampleType, new SampleTypePropertyType(), code, label, dataType);
-        return this;
-    }
-    
-    protected void addPropertyType(SampleType entityType, SampleTypePropertyType entityTypePropertyType, String code, String label,
-            DataTypeCode type)
-    {
-        List<SampleTypePropertyType> types = entityType.getAssignedPropertyTypes();
-        PropertyType propertyType = new PropertyType();
-        propertyType.setCode(code);
-        propertyType.setSimpleCode(code);
-        propertyType.setLabel(label);
-        propertyType.setDataType(new DataType(type));
-        entityTypePropertyType.setPropertyType(propertyType);
-        entityTypePropertyType.setEntityType(entityType);
+        SampleTypePropertyType entityTypePropertyType = new SampleTypePropertyType();
+        List<SampleTypePropertyType> types = sampleType.getAssignedPropertyTypes();
+        fillEntityTypePropertyType(sampleType, entityTypePropertyType, code, label, dataType);
         types.add(entityTypePropertyType);
+        return this;
     }
 }
-- 
GitLab