From be9fe9c233225270b60749c5ab1e5841f2a1ac8b Mon Sep 17 00:00:00 2001
From: kaloyane <kaloyane>
Date: Fri, 24 Jun 2011 12:59:02 +0000
Subject: [PATCH] [LMS-2347] Jython Dropbox API extension: add the possibility
 to create/fetch materials within transactions

SVN: 21840
---
 .../DefaultEntityOperationService.java        |   7 +-
 .../v1/IDataSetRegistrationTransaction.java   |  15 +
 .../registrator/api/v1/IMaterial.java         |  30 ++
 .../api/v1/IMaterialImmutable.java            |  46 +++
 .../api/v1/impl/AbstractTransactionState.java |  36 ++-
 .../api/v1/impl/ConversionUtils.java          |  10 +
 .../impl/DataSetRegistrationTransaction.java  |  16 +
 .../registrator/api/v1/impl/Material.java     |  60 ++++
 .../api/v1/impl/MaterialImmutable.java        |  83 ++++++
 .../server/EncapsulatedOpenBISService.java    |   7 +
 .../shared/IEncapsulatedOpenBISService.java   |   8 +
 .../dto/AtomicEntityOperationDetails.java     |  12 +
 .../EncapsulatedOpenBISServiceTest.java       |   4 +
 .../openbis/generic/server/ETLService.java    |  45 ++-
 .../generic/server/ETLServiceLogger.java      |   8 +
 .../generic/server/MaterialHelper.java        | 273 ++++++++++++++++++
 .../bo/IAbstractBussinessObjectFactory.java   |  35 +++
 .../bo/ICommonBusinessObjectFactory.java      |   9 +-
 .../generic/shared/IETLLIMSService.java       |  31 +-
 .../dto/AtomicEntityOperationDetails.java     |  14 +
 .../dto/AtomicEntityOperationResult.java      |  15 +-
 .../server/GenericBusinessObjectFactory.java  |   8 +
 .../plugin/generic/server/GenericServer.java  | 211 +-------------
 .../server/IGenericBusinessObjectFactory.java |  15 +-
 .../generic/server/ETLServiceTest.java        |  43 ++-
 .../shared/IETLLIMSService.java.expected      |  31 +-
 .../dto/AtomicEntityOperationDetailsTest.java |  15 +-
 .../generic/server/GenericServerTest.java     |   8 +-
 28 files changed, 845 insertions(+), 250 deletions(-)
 create mode 100644 datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IMaterial.java
 create mode 100644 datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IMaterialImmutable.java
 create mode 100644 datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/Material.java
 create mode 100644 datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/MaterialImmutable.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/MaterialHelper.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IAbstractBussinessObjectFactory.java

diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DefaultEntityOperationService.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DefaultEntityOperationService.java
index 9188f312dd5..4a0d533f91d 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DefaultEntityOperationService.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DefaultEntityOperationService.java
@@ -18,6 +18,7 @@ package ch.systemsx.cisd.etlserver.registrator;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 import ch.systemsx.cisd.etlserver.ITopLevelDataSetRegistratorDelegate;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
@@ -25,6 +26,7 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.dto.AtomicEntityOperationDeta
 import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
 import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetRegistrationInformation;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewProject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSpace;
@@ -78,6 +80,7 @@ public class DefaultEntityOperationService<T extends DataSetInformation> impleme
         List<NewExperiment> experimentRegistrations = details.getExperimentRegistrations();
         List<SampleUpdatesDTO> sampleUpdates = details.getSampleUpdates();
         List<NewSample> sampleRegistrations = details.getSampleRegistrations();
+        Map<String, List<NewMaterial>> materialRegistrations = details.getMaterialRegistrations();
         List<DataSetUpdatesDTO> dataSetUpdates = details.getDataSetUpdates();
 
         List<NewExternalData> dataSetRegistrations = new ArrayList<NewExternalData>();
@@ -89,8 +92,8 @@ public class DefaultEntityOperationService<T extends DataSetInformation> impleme
 
         return new ch.systemsx.cisd.openbis.generic.shared.dto.AtomicEntityOperationDetails(
                 details.tryUserIdOrNull(), spaceRegistrations, projectRegistrations,
-                experimentRegistrations, sampleUpdates, sampleRegistrations, dataSetRegistrations,
-                dataSetUpdates);
+                experimentRegistrations, sampleUpdates, sampleRegistrations, materialRegistrations,
+                dataSetRegistrations, dataSetUpdates);
     }
 
 }
\ No newline at end of file
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IDataSetRegistrationTransaction.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IDataSetRegistrationTransaction.java
index 2ed6b074ed7..47118f62789 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IDataSetRegistrationTransaction.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IDataSetRegistrationTransaction.java
@@ -129,6 +129,21 @@ public interface IDataSetRegistrationTransaction
      */
     ISpaceImmutable getSpace(String spaceCode);
 
+    /**
+     * Get a material from the openBIS AS. Returns null if the space does not exist.
+     * 
+     * @return A material or null
+     */
+    IMaterialImmutable getMaterial(String materialCode, String materialType);
+
+    /**
+     * Create a new space to register with the openBIS AS.
+     * 
+     * @param materialCode the code of the material
+     * @param materialType the type of the material
+     */
+    IMaterial createNewMaterial(String materialCode, String materialType);
+
     // File operations -- The source and destination paths are local to the incoming data set folder
     // or incoming directory if the data set is just one file
 
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IMaterial.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IMaterial.java
new file mode 100644
index 00000000000..cd3a29f038f
--- /dev/null
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IMaterial.java
@@ -0,0 +1,30 @@
+/*
+ * 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.systemsx.cisd.etlserver.registrator.api.v1;
+
+
+/**
+ * @author Kaloyan Enimanev
+ */
+public interface IMaterial extends IMaterialImmutable
+{
+    /**
+     * Set the value for a property.
+     */
+    void setPropertyValue(String propertyCode, String propertyValue);
+
+}
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IMaterialImmutable.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IMaterialImmutable.java
new file mode 100644
index 00000000000..bf45c55ab90
--- /dev/null
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IMaterialImmutable.java
@@ -0,0 +1,46 @@
+/*
+ * 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.systemsx.cisd.etlserver.registrator.api.v1;
+
+/**
+ * @author Kaloyan Enimanev
+ */
+public interface IMaterialImmutable
+{
+
+    /**
+     * Return the code for this material.
+     */
+    String getCode();
+
+    /**
+     * Return the type for this material. May be null.
+     */
+    String getMaterialType();
+
+    /**
+     * Return true if the material exists in the database.
+     */
+    boolean isExistingMaterial();
+
+    /**
+     * Return the value of a property specified by a code. May return null of no such property with
+     * code <code>propertyCode</code> is found.
+     */
+    String getPropertyValue(String propertyCode);
+
+}
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/AbstractTransactionState.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/AbstractTransactionState.java
index d2fdff50800..6e2900d9459 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/AbstractTransactionState.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/AbstractTransactionState.java
@@ -18,7 +18,9 @@ package ch.systemsx.cisd.etlserver.registrator.api.v1.impl;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import ch.systemsx.cisd.common.exceptions.NotImplementedException;
 import ch.systemsx.cisd.common.filesystem.FileUtilities;
@@ -32,6 +34,7 @@ import ch.systemsx.cisd.etlserver.registrator.api.v1.IDataSetImmutable;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.IDataSetUpdatable;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.IExperiment;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.IExperimentImmutable;
+import ch.systemsx.cisd.etlserver.registrator.api.v1.IMaterial;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.IProject;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.ISample;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.ISampleImmutable;
@@ -42,6 +45,7 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
 import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetRegistrationInformation;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewProject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSpace;
@@ -112,6 +116,8 @@ abstract class AbstractTransactionState<T extends DataSetInformation>
 
         private final List<Sample> samplesToBeUpdated = new ArrayList<Sample>();
 
+        private final List<Material> materialsToBeRegistered = new ArrayList<Material>();
+
         private String userIdOrNull = null;
 
         public LiveTransactionState(DataSetRegistrationTransaction<T> parent,
@@ -301,6 +307,13 @@ abstract class AbstractTransactionState<T extends DataSetInformation>
             return project;
         }
 
+        public IMaterial createNewMaterial(String materialCode, String materialType)
+        {
+            Material material = new Material(materialCode, materialType);
+            materialsToBeRegistered.add(material);
+            return material;
+        }
+
         public String moveFile(String src, IDataSet dst)
         {
             File srcFile = new File(src);
@@ -436,6 +449,7 @@ abstract class AbstractTransactionState<T extends DataSetInformation>
             List<NewExperiment> experimentRegistrations = convertExperimentsToBeRegistered();
             List<SampleUpdatesDTO> sampleUpdates = convertSamplesToBeUpdated();
             List<NewSample> sampleRegistrations = convertSamplesToBeRegistered();
+            Map<String, List<NewMaterial>> materialRegistrations = convertMaterialsToBeRegistered();
             List<DataSetUpdatesDTO> dataSetUpdates = convertDataSetsToBeUpdated();
 
             // experiment updates not yet supported
@@ -444,8 +458,8 @@ abstract class AbstractTransactionState<T extends DataSetInformation>
             AtomicEntityOperationDetails<T> registrationDetails =
                     new AtomicEntityOperationDetails<T>(getUserId(), spaceRegistrations,
                             projectRegistrations, experimentUpdates, experimentRegistrations,
-                            sampleUpdates, sampleRegistrations, dataSetRegistrations,
-                            dataSetUpdates);
+                            sampleUpdates, sampleRegistrations, materialRegistrations,
+                            dataSetRegistrations, dataSetUpdates);
             return registrationDetails;
         }
 
@@ -509,6 +523,24 @@ abstract class AbstractTransactionState<T extends DataSetInformation>
             return result;
         }
 
+        private Map<String, List<NewMaterial>> convertMaterialsToBeRegistered()
+        {
+            Map<String, List<NewMaterial>> result = new HashMap<String, List<NewMaterial>>();
+            for (Material material : materialsToBeRegistered)
+            {
+                NewMaterial converted = ConversionUtils.convertToNewMaterial(material);
+                String materialType = material.getMaterialType();
+                List<NewMaterial> materialsOfSameType = result.get(materialType);
+                if (materialsOfSameType == null)
+                {
+                    materialsOfSameType = new ArrayList<NewMaterial>();
+                    result.put(materialType, materialsOfSameType);
+                }
+                materialsOfSameType.add(converted);
+            }
+            return result;
+        }
+
         @Override
         public boolean isCommitted()
         {
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/ConversionUtils.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/ConversionUtils.java
index cc32cb2e3b7..0291f915869 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/ConversionUtils.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/ConversionUtils.java
@@ -29,6 +29,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewAttachment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewProject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSpace;
@@ -222,4 +223,13 @@ public class ConversionUtils
         return dataSetUpdate;
     }
 
+    public static NewMaterial convertToNewMaterial(Material material)
+    {
+        NewMaterial newMaterial = new NewMaterial(material.getCode());
+        IEntityProperty[] properties =
+                material.getMaterial().getProperties().toArray(new IEntityProperty[0]);
+        newMaterial.setProperties(properties);
+        return newMaterial;
+    }
+
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransaction.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransaction.java
index 1a848328041..07adfb05f05 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransaction.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransaction.java
@@ -39,6 +39,8 @@ import ch.systemsx.cisd.etlserver.registrator.api.v1.IDataSetRegistrationTransac
 import ch.systemsx.cisd.etlserver.registrator.api.v1.IDataSetUpdatable;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.IExperiment;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.IExperimentImmutable;
+import ch.systemsx.cisd.etlserver.registrator.api.v1.IMaterial;
+import ch.systemsx.cisd.etlserver.registrator.api.v1.IMaterialImmutable;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.IProject;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.IProjectImmutable;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.ISample;
@@ -53,6 +55,7 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
 import ch.systemsx.cisd.openbis.dss.generic.shared.dto.AtomicEntityOperationDetails;
 import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
 import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetRegistrationInformation;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifierFactory;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
@@ -277,6 +280,19 @@ public class DataSetRegistrationTransaction<T extends DataSetInformation> implem
         return (null == spaceOrNull) ? null : new SpaceImmutable(spaceOrNull);
     }
 
+    public IMaterialImmutable getMaterial(String materialCode, String materialType)
+    {
+        MaterialIdentifier materialIdentifier = new MaterialIdentifier(materialCode, materialType);
+        ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material materialOrNull =
+                openBisService.tryGetMaterial(materialIdentifier);
+        return (null == materialOrNull) ? null : new MaterialImmutable(materialOrNull);
+    }
+
+    public IMaterial createNewMaterial(String materialCode, String materialType)
+    {
+        return getStateAsLiveState().createNewMaterial(materialCode, materialType);
+    }
+
     public String moveFile(String src, IDataSet dst)
     {
         return getStateAsLiveState().moveFile(src, dst);
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/Material.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/Material.java
new file mode 100644
index 00000000000..8d583bf15ac
--- /dev/null
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/Material.java
@@ -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.
+ */
+
+package ch.systemsx.cisd.etlserver.registrator.api.v1.impl;
+
+import java.util.ArrayList;
+
+import ch.systemsx.cisd.etlserver.registrator.api.v1.IMaterial;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialType;
+import ch.systemsx.cisd.openbis.generic.shared.util.EntityHelper;
+
+/**
+ * @author Kaloyan Enimanev
+ */
+public class Material extends MaterialImmutable implements IMaterial
+{
+
+    private static ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material buildMaterialWithCodeAndType(
+            String code, String type)
+    {
+        ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material material =
+                new ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material();
+        material.setCode(code);
+        MaterialType materialType = new MaterialType();
+        materialType.setCode(type);
+        material.setMaterialType(materialType);
+        material.setProperties(new ArrayList<IEntityProperty>());
+
+        return material;
+    }
+
+    public Material(ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material material)
+    {
+        super(material);
+    }
+
+    public Material(String code, String type)
+    {
+        super(buildMaterialWithCodeAndType(code, type), false);
+    }
+
+    public void setPropertyValue(String propertyCode, String propertyValue)
+    {
+        EntityHelper.createOrUpdateProperty(getMaterial(), propertyCode, propertyValue);
+    }
+}
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/MaterialImmutable.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/MaterialImmutable.java
new file mode 100644
index 00000000000..f88f0657f97
--- /dev/null
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/MaterialImmutable.java
@@ -0,0 +1,83 @@
+/*
+ * 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.systemsx.cisd.etlserver.registrator.api.v1.impl;
+
+import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.etlserver.registrator.api.v1.IMaterialImmutable;
+import ch.systemsx.cisd.openbis.generic.shared.util.EntityHelper;
+
+/**
+ * @author Kaloyan Enimanev
+ */
+public class MaterialImmutable implements IMaterialImmutable
+{
+    private final ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material material;
+
+    private final boolean existingMaterial;
+
+    public MaterialImmutable(ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material material)
+    {
+        this(material, true);
+    }
+
+    public MaterialImmutable(ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material material,
+            boolean existingMaterial)
+    {
+        this.material = material;
+        this.existingMaterial = existingMaterial;
+    }
+
+    public String getCode()
+    {
+        return material.getCode();
+    }
+
+    public String getMaterialType()
+    {
+        if (material.getMaterialType() != null)
+        {
+            return material.getMaterialType().getCode();
+        }
+        return null;
+    }
+
+    public boolean isExistingMaterial()
+    {
+        return existingMaterial;
+    }
+
+    public ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material getMaterial()
+    {
+        return material;
+    }
+
+    /**
+     * Throw an exception if the sample does not exist
+     */
+    protected void checkExists()
+    {
+        if (false == isExistingMaterial())
+        {
+            throw new UserFailureException("Material does not exist.");
+        }
+    }
+
+    public String getPropertyValue(String propertyCode)
+    {
+        return EntityHelper.tryFindPropertyValue(material, propertyCode);
+    }
+}
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java
index 9ae22fd6dd4..12b0f51c762 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java
@@ -47,6 +47,8 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListSampleCriteria;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSamplesWithTypes;
@@ -558,4 +560,9 @@ public final class EncapsulatedOpenBISService implements IEncapsulatedOpenBISSer
         return service.listExperiments(session.getToken(), projectIdentifier);
     }
 
+    public Material tryGetMaterial(MaterialIdentifier materialIdentifier)
+    {
+        return service.tryGetMaterial(session.getToken(), materialIdentifier);
+    }
+
 }
\ No newline at end of file
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IEncapsulatedOpenBISService.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IEncapsulatedOpenBISService.java
index 2867468414a..9140450f3a6 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IEncapsulatedOpenBISService.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IEncapsulatedOpenBISService.java
@@ -34,6 +34,8 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListSampleCriteria;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSamplesWithTypes;
@@ -153,6 +155,12 @@ public interface IEncapsulatedOpenBISService
     public SampleIdentifier tryToGetSampleIdentifier(String samplePermID)
             throws UserFailureException;
 
+    /**
+     * For given {@link MaterialIdentifier} returns the corresponding {@link Material}.
+     */
+    @ManagedAuthentication
+    public Material tryGetMaterial(MaterialIdentifier materialIdentifier);
+
     /**
      * Lists vocabulary terms.
      */
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/AtomicEntityOperationDetails.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/AtomicEntityOperationDetails.java
index 218a7ae9dc7..78b7cea0d1a 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/AtomicEntityOperationDetails.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/AtomicEntityOperationDetails.java
@@ -18,9 +18,12 @@ package ch.systemsx.cisd.openbis.dss.generic.shared.dto;
 
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewProject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSpace;
@@ -53,6 +56,8 @@ public class AtomicEntityOperationDetails<T extends DataSetInformation> implemen
 
     private final List<NewSample> sampleRegistrations;
 
+    private final Map<String /* material type */, List<NewMaterial>> materialRegistrations;
+
     private final List<DataSetRegistrationInformation<T>> dataSetRegistrations;
 
     private final List<DataSetUpdatesDTO> dataSetUpdates;
@@ -62,6 +67,7 @@ public class AtomicEntityOperationDetails<T extends DataSetInformation> implemen
             List<ExperimentUpdatesDTO> experimentUpdates,
             List<NewExperiment> experimentRegistrations, List<SampleUpdatesDTO> sampleUpdates,
             List<NewSample> sampleRegistrations,
+            Map<String, List<NewMaterial>> materialRegistrations,
             List<DataSetRegistrationInformation<T>> dataSetRegistrations,
             List<DataSetUpdatesDTO> dataSetUpdates)
     {
@@ -72,6 +78,7 @@ public class AtomicEntityOperationDetails<T extends DataSetInformation> implemen
         this.experimentRegistrations = new ArrayList<NewExperiment>(experimentRegistrations);
         this.sampleUpdates = new ArrayList<SampleUpdatesDTO>(sampleUpdates);
         this.sampleRegistrations = new ArrayList<NewSample>(sampleRegistrations);
+        this.materialRegistrations = new HashMap<String, List<NewMaterial>>(materialRegistrations);
         this.dataSetRegistrations =
                 new ArrayList<DataSetRegistrationInformation<T>>(dataSetRegistrations);
         this.dataSetUpdates = new ArrayList<DataSetUpdatesDTO>(dataSetUpdates);
@@ -122,4 +129,9 @@ public class AtomicEntityOperationDetails<T extends DataSetInformation> implemen
         return projectRegistrations;
     }
 
+    public Map<String, List<NewMaterial>> getMaterialRegistrations()
+    {
+        return materialRegistrations;
+    }
+
 }
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISServiceTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISServiceTest.java
index 6d86c4aaccf..e6dd35f7bcb 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISServiceTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISServiceTest.java
@@ -17,7 +17,9 @@
 package ch.systemsx.cisd.openbis.dss.generic.server;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
+import java.util.List;
 
 import org.jmock.Expectations;
 import org.jmock.Mockery;
@@ -33,6 +35,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewAttachment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewProject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSpace;
@@ -161,6 +164,7 @@ public class EncapsulatedOpenBISServiceTest
                 new AtomicEntityOperationDetails(null, Arrays.<NewSpace> asList(),
                         Arrays.<NewProject> asList(), Arrays.<NewExperiment> asList(),
                         Arrays.<SampleUpdatesDTO> asList(), Arrays.<NewSample> asList(),
+                        Collections.<String, List<NewMaterial>> emptyMap(),
                         Arrays.asList(data), Arrays.<DataSetUpdatesDTO> asList());
         context.checking(new Expectations()
         {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
index 9770d87d735..b8a7dd7ae12 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
@@ -23,6 +23,8 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 import ch.systemsx.cisd.authentication.IAuthenticationService;
@@ -39,6 +41,7 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.IDataBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IExperimentBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IExperimentTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IGroupBO;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.IMaterialBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IProjectBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IRoleAssignmentTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleBO;
@@ -76,7 +79,10 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Grantee;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListOrSearchSampleCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListSampleCriteria;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewProject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSamplesWithTypes;
@@ -143,6 +149,7 @@ import ch.systemsx.cisd.openbis.generic.shared.translator.ExperimentTranslator;
 import ch.systemsx.cisd.openbis.generic.shared.translator.ExperimentTranslator.LoadableFields;
 import ch.systemsx.cisd.openbis.generic.shared.translator.ExperimentTypeTranslator;
 import ch.systemsx.cisd.openbis.generic.shared.translator.GroupTranslator;
+import ch.systemsx.cisd.openbis.generic.shared.translator.MaterialTranslator;
 import ch.systemsx.cisd.openbis.generic.shared.translator.PersonTranslator;
 import ch.systemsx.cisd.openbis.generic.shared.translator.ProjectTranslator;
 import ch.systemsx.cisd.openbis.generic.shared.translator.SampleTranslator;
@@ -1070,7 +1077,6 @@ public class ETLService extends AbstractCommonServer<IETLService> implements IET
             // space does not exist
             return null;
         }
-
     }
 
     public Project tryGetProject(String sessionToken, ProjectIdentifier projectIdentifier)
@@ -1089,6 +1095,21 @@ public class ETLService extends AbstractCommonServer<IETLService> implements IET
         }
     }
 
+    public Material tryGetMaterial(String sessionToken, MaterialIdentifier materialIdentifier)
+    {
+        final Session session = getSession(sessionToken);
+        final IMaterialBO bo = businessObjectFactory.createMaterialBO(session);
+        try
+        {
+            bo.loadByMaterialIdentifier(materialIdentifier);
+            return MaterialTranslator.translate(bo.getMaterial());
+        } catch (UserFailureException ufe)
+        {
+            // material does not exist
+            return null;
+        }
+    }
+
     public AtomicEntityOperationResult performEntityOperations(String sessionToken,
             AtomicEntityOperationDetails operationDetails)
     {
@@ -1096,6 +1117,8 @@ public class ETLService extends AbstractCommonServer<IETLService> implements IET
 
         List<Space> spacesCreated = createSpaces(session, operationDetails);
 
+        List<Material> materialsCreated = createMaterials(session, operationDetails);
+
         List<Project> projectsCreated = createProjects(session, operationDetails);
 
         List<Experiment> experimentsCreated = createExperiments(session, operationDetails);
@@ -1109,7 +1132,7 @@ public class ETLService extends AbstractCommonServer<IETLService> implements IET
         List<ExternalData> dataSetsUpdated = updateDataSets(session, operationDetails);
 
         return new AtomicEntityOperationResult(spacesCreated, projectsCreated, experimentsCreated,
-                samplesUpdated, samplesCreated, dataSetsCreated, dataSetsUpdated);
+                samplesUpdated, samplesCreated, materialsCreated, dataSetsCreated, dataSetsUpdated);
     }
 
     private List<Space> createSpaces(Session session, AtomicEntityOperationDetails operationDetails)
@@ -1125,6 +1148,24 @@ public class ETLService extends AbstractCommonServer<IETLService> implements IET
         return GroupTranslator.translate(spacePEsCreated);
     }
 
+    private List<Material> createMaterials(Session session,
+            AtomicEntityOperationDetails operationDetails)
+    {
+        MaterialHelper materialHelper =
+                new MaterialHelper(session, businessObjectFactory, getDAOFactory(),
+                        getPropertiesBatchManager());
+        Map<String, List<NewMaterial>> materialRegs = operationDetails.getMaterialRegistrations();
+        List<Material> registeredMaterials = new ArrayList<Material>();
+        for (Entry<String, List<NewMaterial>> newMaterialsEntry : materialRegs.entrySet())
+        {
+            String materialType = newMaterialsEntry.getKey();
+            List<NewMaterial> newMaterials = newMaterialsEntry.getValue();
+            List<Material> materials = materialHelper.registerMaterials(materialType, newMaterials);
+            registeredMaterials.addAll(materials);
+        }
+        return registeredMaterials;
+    }
+
     private SpacePE registerSpaceInternal(Session session, NewSpace newSpace,
             String registratorUserIdOrNull)
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceLogger.java
index cb7bac3b082..83c305668bd 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceLogger.java
@@ -36,6 +36,8 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListSampleCriteria;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSamplesWithTypes;
@@ -487,4 +489,10 @@ public class ETLServiceLogger extends AbstractServerLogger implements IETLServic
         logAccess(sessionToken, "searchForDataSets", "%s", searchCriteria);
         return null;
     }
+
+    public Material tryGetMaterial(String sessionToken, MaterialIdentifier materialIdentifier)
+    {
+        logAccess(sessionToken, "tryGetMaterial", "%s", materialIdentifier);
+        return null;
+    }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/MaterialHelper.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/MaterialHelper.java
new file mode 100644
index 00000000000..7db559088f0
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/MaterialHelper.java
@@ -0,0 +1,273 @@
+/*
+ * 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.systemsx.cisd.openbis.generic.server;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.log4j.Logger;
+
+import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
+import ch.systemsx.cisd.openbis.generic.server.batch.BatchOperationExecutor;
+import ch.systemsx.cisd.openbis.generic.server.batch.IBatchOperation;
+import ch.systemsx.cisd.openbis.generic.server.business.IPropertiesBatchManager;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.IAbstractBussinessObjectFactory;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.IMaterialTable;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.MaterialUpdateDTO;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.materiallister.IMaterialLister;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
+import ch.systemsx.cisd.openbis.generic.shared.basic.CodeConverter;
+import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListMaterialCriteria;
+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.NewMaterial;
+import ch.systemsx.cisd.openbis.generic.shared.dto.EntityTypePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialTypePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
+import ch.systemsx.cisd.openbis.generic.shared.dto.properties.EntityKind;
+import ch.systemsx.cisd.openbis.generic.shared.translator.MaterialTranslator;
+import ch.systemsx.cisd.openbis.generic.shared.translator.MaterialTypeTranslator;
+import ch.systemsx.cisd.openbis.generic.shared.util.ServerUtils;
+
+/**
+ * @author Kaloyan Enimanev
+ */
+public class MaterialHelper
+{
+
+    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION,
+            MaterialHelper.class);
+
+    private final Session session;
+
+    private final IAbstractBussinessObjectFactory businessObjectFactory;
+
+    private final IDAOFactory daoFactory;
+
+    private final IPropertiesBatchManager propertiesBatchManager;
+
+    public MaterialHelper(Session session,
+            IAbstractBussinessObjectFactory businessObjectFactory, IDAOFactory daoFactory,
+            IPropertiesBatchManager propertiesBatchManager)
+    {
+        this.session = session;
+        this.businessObjectFactory = businessObjectFactory;
+        this.daoFactory = daoFactory;
+        this.propertiesBatchManager = propertiesBatchManager;
+    }
+
+    public List<Material> registerMaterials(String materialTypeCode,
+            final List<NewMaterial> newMaterials)
+    {
+        assert materialTypeCode != null : "Unspecified material type.";
+        assert newMaterials != null : "Unspecified new materials.";
+
+        // Does nothing if material list is empty.
+        if (newMaterials.size() == 0)
+        {
+            return Collections.emptyList();
+        }
+        ServerUtils.prevalidate(newMaterials, "material");
+        final MaterialTypePE materialTypePE = findMaterialType(materialTypeCode);
+        final List<MaterialPE> registeredMaterials = new ArrayList<MaterialPE>();
+        propertiesBatchManager.manageProperties(materialTypePE, newMaterials,
+                session.tryGetPerson());
+        IBatchOperation<NewMaterial> strategy = new IBatchOperation<NewMaterial>()
+            {
+
+                public void execute(List<NewMaterial> entities)
+                {
+                    final IMaterialTable materialTable =
+                            businessObjectFactory.createMaterialTable(session);
+                    materialTable.add(entities, materialTypePE);
+                    materialTable.save();
+                    registeredMaterials.addAll(materialTable.getMaterials());
+                }
+
+                public List<NewMaterial> getAllEntities()
+                {
+                    return newMaterials;
+                }
+
+                public String getEntityName()
+                {
+                    return "material";
+                }
+
+                public String getOperationName()
+                {
+                    return "register";
+                }
+            };
+        BatchOperationExecutor.executeInBatches(strategy);
+
+        return MaterialTranslator.translate(registeredMaterials);
+    }
+
+    public int updateMaterials(String materialTypeCode,
+            final List<NewMaterial> newMaterials, final boolean ignoreUnregisteredMaterials)
+            throws UserFailureException
+    {
+        assert materialTypeCode != null : "Unspecified material type.";
+        assert newMaterials != null : "Unspecified new materials.";
+
+        // Does nothing if material list is empty.
+        if (newMaterials.size() == 0)
+        {
+            return 0;
+        }
+
+        ServerUtils.prevalidate(newMaterials, "material");
+
+        final AtomicInteger count = new AtomicInteger(0);
+        final Map<String/* code */, Material> existingMaterials = listMaterials(materialTypeCode);
+
+        final MaterialTypePE materialTypePE = findMaterialType(materialTypeCode);
+        propertiesBatchManager.manageProperties(materialTypePE, newMaterials,
+                session.tryGetPerson());
+        IBatchOperation<NewMaterial> strategy = new IBatchOperation<NewMaterial>()
+            {
+                public void execute(List<NewMaterial> entities)
+                {
+                    List<MaterialUpdateDTO> materialUpdates = new ArrayList<MaterialUpdateDTO>();
+                    for (NewMaterial material : entities)
+                    {
+                        Material existingMaterial =
+                                existingMaterials.get(CodeConverter.tryToDatabase(material
+                                        .getCode()));
+                        if (existingMaterial != null)
+                        {
+                            materialUpdates.add(createMaterialUpdate(existingMaterial, material));
+                            count.incrementAndGet();
+                        } else if (ignoreUnregisteredMaterials == false)
+                        {
+                            throw new UserFailureException("Can not update unregistered material '"
+                                    + material.getCode()
+                                    + "'. Please use checkbox for ignoring unregistered materials.");
+                        }
+                    }
+                    updateMaterials(materialUpdates);
+                }
+
+                public List<NewMaterial> getAllEntities()
+                {
+                    return newMaterials;
+                }
+
+                public String getEntityName()
+                {
+                    return "material";
+                }
+
+                public String getOperationName()
+                {
+                    return "update";
+                }
+            };
+        BatchOperationExecutor.executeInBatches(strategy);
+        return count.get();
+    }
+
+    public void registerOrUpdateMaterials(String materialTypeCode, List<NewMaterial> materials)
+    {
+        Map<String/* code */, Material> existingMaterials = listMaterials(materialTypeCode);
+        List<NewMaterial> materialsToRegister = new ArrayList<NewMaterial>();
+        List<MaterialUpdateDTO> materialUpdates = new ArrayList<MaterialUpdateDTO>();
+        for (NewMaterial material : materials)
+        {
+            Material existingMaterial =
+                    existingMaterials.get(CodeConverter.tryToDatabase(material.getCode()));
+            if (existingMaterial != null)
+            {
+                materialUpdates.add(createMaterialUpdate(existingMaterial, material));
+            } else
+            {
+                materialsToRegister.add(material);
+            }
+        }
+        registerMaterials(materialTypeCode, materialsToRegister);
+        updateMaterials(materialUpdates);
+        operationLog.info(String.format("Number of newly registered materials: %d, "
+                + "number of existing materials which have been updated: %d",
+                materialsToRegister.size(), materialUpdates.size()));
+
+    }
+
+    private static MaterialUpdateDTO createMaterialUpdate(Material existingMaterial,
+            NewMaterial material)
+    {
+        return new MaterialUpdateDTO(new TechId(existingMaterial.getId()), Arrays.asList(material
+                .getProperties()), existingMaterial.getModificationDate());
+    }
+
+    private void updateMaterials(List<MaterialUpdateDTO> updates)
+    {
+        if (updates.isEmpty())
+        {
+            return;
+        }
+        IMaterialTable materialTable = businessObjectFactory.createMaterialTable(session);
+        materialTable.update(updates);
+        materialTable.save();
+    }
+
+    private Map<String/* code */, Material> listMaterials(String materialTypeCode)
+    {
+        EntityTypePE entityTypePE =
+                daoFactory.getEntityTypeDAO(EntityKind.MATERIAL).tryToFindEntityTypeByCode(
+                        materialTypeCode);
+        MaterialType materialType = MaterialTypeTranslator.translateSimple(entityTypePE);
+
+        IMaterialLister materialLister = businessObjectFactory.createMaterialLister(session);
+        ListMaterialCriteria listByTypeCriteria = new ListMaterialCriteria(materialType);
+        List<Material> materials = materialLister.list(listByTypeCriteria, false);
+        return asCodeToMaterialMap(materials);
+    }
+
+    private static Map<String, Material> asCodeToMaterialMap(List<Material> materials)
+    {
+        Map<String, Material> map = new HashMap<String, Material>();
+        for (Material material : materials)
+        {
+            map.put(material.getCode(), material);
+        }
+        return map;
+    }
+
+    private MaterialTypePE findMaterialType(String materialTypeCode)
+    {
+        final MaterialTypePE materialTypePE =
+                (MaterialTypePE) daoFactory.getEntityTypeDAO(EntityKind.MATERIAL)
+                        .tryToFindEntityTypeByCode(materialTypeCode);
+        if (materialTypePE == null)
+        {
+            throw UserFailureException.fromTemplate("Material type with code '%s' does not exist.",
+                    materialTypeCode);
+        }
+        return materialTypePE;
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IAbstractBussinessObjectFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IAbstractBussinessObjectFactory.java
new file mode 100644
index 00000000000..7eb0f58ccdf
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IAbstractBussinessObjectFactory.java
@@ -0,0 +1,35 @@
+/*
+ * 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.systemsx.cisd.openbis.generic.server.business.bo;
+
+import ch.systemsx.cisd.openbis.generic.server.business.bo.materiallister.IMaterialLister;
+import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
+
+/**
+ * An abstract bussiness object factory.
+ * 
+ * @author Kaloyan Enimanev
+ */
+public interface IAbstractBussinessObjectFactory
+{
+    public IMaterialTable createMaterialTable(Session session);
+
+    public IMaterialBO createMaterialBO(Session session);
+
+    public IMaterialLister createMaterialLister(Session session);
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ICommonBusinessObjectFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ICommonBusinessObjectFactory.java
index dc1a3b9ee4c..a7478f32797 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ICommonBusinessObjectFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ICommonBusinessObjectFactory.java
@@ -17,7 +17,6 @@
 package ch.systemsx.cisd.openbis.generic.server.business.bo;
 
 import ch.systemsx.cisd.openbis.generic.server.business.bo.datasetlister.IDatasetLister;
-import ch.systemsx.cisd.openbis.generic.server.business.bo.materiallister.IMaterialLister;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister.ISampleLister;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
 import ch.systemsx.cisd.openbis.generic.shared.dto.properties.EntityKind;
@@ -28,7 +27,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.properties.EntityKind;
  * 
  * @author Tomasz Pylak
  */
-public interface ICommonBusinessObjectFactory
+public interface ICommonBusinessObjectFactory extends IAbstractBussinessObjectFactory
 {
     public IAttachmentBO createAttachmentBO(final Session session);
 
@@ -63,12 +62,8 @@ public interface ICommonBusinessObjectFactory
 
     public IProjectBO createProjectBO(final Session session);
 
-    public IMaterialTable createMaterialTable(Session session);
-
     public IEntityTypeBO createEntityTypeBO(Session session);
 
-    public IMaterialBO createMaterialBO(Session session);
-
     public IAuthorizationGroupBO createAuthorizationGroupBO(Session session);
 
     public IGridCustomFilterOrColumnBO createGridCustomFilterBO(final Session session);
@@ -78,10 +73,8 @@ public interface ICommonBusinessObjectFactory
     public IInvalidationBO createInvalidationBO(final Session session);
 
     // Fast listing operations
-
     public ISampleLister createSampleLister(Session session);
 
     public IDatasetLister createDatasetLister(Session session);
 
-    public IMaterialLister createMaterialLister(Session session);
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java
index 2d773460d3d..cc540753cfe 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java
@@ -57,6 +57,8 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListSampleCriteria;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSamplesWithTypes;
@@ -120,20 +122,11 @@ public interface IETLLIMSService extends IServer, ISessionProvider
             throws UserFailureException;
 
     /**
-     * Gets a sample with the specified identifier. Sample is enriched with properties and the
-     * experiment with properties.
-     * 
-     * @param sessionToken the user authentication token. Must not be <code>null</code>.
-     * @param sampleIdentifier an identifier which uniquely identifies the sample.
-     * @return <code>null</code> if no sample attached to an experiment could be found for given
-     *         <var>sampleIdentifier</var>.
+     * For given {@link MaterialIdentifier} returns the corresponding {@link Material}.
      */
     @Transactional(readOnly = true)
     @RolesAllowed(RoleWithHierarchy.SPACE_ETL_SERVER)
-    public Sample tryGetSampleWithExperiment(
-            final String sessionToken,
-            @AuthorizationGuard(guardClass = ExistingSampleOwnerIdentifierPredicate.class) final SampleIdentifier sampleIdentifier)
-            throws UserFailureException;
+    public Material tryGetMaterial(String sessionToken, MaterialIdentifier materialIdentifier);
 
     /**
      * Tries to get the identifier of sample with specified permanent ID.
@@ -154,6 +147,22 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     public ExperimentType getExperimentType(String sessionToken, String experimentTypeCode)
             throws UserFailureException;
 
+    /**
+     * Gets a sample with the specified identifier. Sample is enriched with properties and the
+     * experiment with properties.
+     * 
+     * @param sessionToken the user authentication token. Must not be <code>null</code>.
+     * @param sampleIdentifier an identifier which uniquely identifies the sample.
+     * @return <code>null</code> if no sample attached to an experiment could be found for given
+     *         <var>sampleIdentifier</var>.
+     */
+    @Transactional(readOnly = true)
+    @RolesAllowed(RoleWithHierarchy.SPACE_ETL_SERVER)
+    public Sample tryGetSampleWithExperiment(
+            final String sessionToken,
+            @AuthorizationGuard(guardClass = ExistingSampleOwnerIdentifierPredicate.class) final SampleIdentifier sampleIdentifier)
+            throws UserFailureException;
+
     /**
      * Returns a list of terms belonging to given vocabulary.
      */
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AtomicEntityOperationDetails.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AtomicEntityOperationDetails.java
index ab5f5ede932..e768b2523da 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AtomicEntityOperationDetails.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AtomicEntityOperationDetails.java
@@ -19,11 +19,14 @@ package ch.systemsx.cisd.openbis.generic.shared.dto;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 
 import org.apache.commons.lang.builder.ToStringBuilder;
 import org.apache.commons.lang.builder.ToStringStyle;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewProject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSpace;
@@ -55,6 +58,8 @@ public class AtomicEntityOperationDetails implements Serializable
 
     private final List<NewSample> sampleRegistrations;
 
+    private final Map<String /* material type */, List<NewMaterial>> materialRegistrations;
+
     private final List<? extends NewExternalData> dataSetRegistrations;
 
     private final List<DataSetUpdatesDTO> dataSetUpdates;
@@ -62,6 +67,7 @@ public class AtomicEntityOperationDetails implements Serializable
     public AtomicEntityOperationDetails(String userIdOrNull, List<NewSpace> spaceRegistrations,
             List<NewProject> projectRegistrations, List<NewExperiment> experimentRegistrations,
             List<SampleUpdatesDTO> sampleUpdates, List<NewSample> sampleRegistrations,
+            Map<String, List<NewMaterial>> materialRegistrations,
             List<? extends NewExternalData> dataSetRegistrations,
             List<DataSetUpdatesDTO> dataSetUpdates)
     {
@@ -72,6 +78,7 @@ public class AtomicEntityOperationDetails implements Serializable
         this.experimentRegistrations = new ArrayList<NewExperiment>(experimentRegistrations);
         this.sampleUpdates = new ArrayList<SampleUpdatesDTO>(sampleUpdates);
         this.sampleRegistrations = new ArrayList<NewSample>(sampleRegistrations);
+        this.materialRegistrations = new TreeMap<String, List<NewMaterial>>(materialRegistrations);
         this.dataSetRegistrations = new ArrayList<NewExternalData>(dataSetRegistrations);
         this.dataSetUpdates = new ArrayList<DataSetUpdatesDTO>(dataSetUpdates);
     }
@@ -121,6 +128,11 @@ public class AtomicEntityOperationDetails implements Serializable
         return projectRegistrations;
     }
 
+    public Map<String, List<NewMaterial>> getMaterialRegistrations()
+    {
+        return materialRegistrations;
+    }
+
     @Override
     public String toString()
     {
@@ -132,8 +144,10 @@ public class AtomicEntityOperationDetails implements Serializable
         sb.append("experimentRegistrations", experimentRegistrations);
         sb.append("sampleUpdates", sampleUpdates);
         sb.append("sampleRegistrations", sampleRegistrations);
+        sb.append("materialRegistrations", materialRegistrations);
         sb.append("dataSetRegistrations", dataSetRegistrations);
         sb.append("dataSetUpdates", dataSetUpdates);
         return sb.toString();
     }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AtomicEntityOperationResult.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AtomicEntityOperationResult.java
index 1e7bf47897e..a085e4e32dc 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AtomicEntityOperationResult.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AtomicEntityOperationResult.java
@@ -23,6 +23,7 @@ import java.util.List;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Space;
@@ -48,6 +49,8 @@ public class AtomicEntityOperationResult implements Serializable
 
     private final ArrayList<Sample> samplesCreated;
 
+    private final ArrayList<Material> materialsCreated;
+
     private final ArrayList<ExternalData> dataSetsCreated;
 
     private final ArrayList<ExternalData> dataSetsUpdated;
@@ -56,13 +59,15 @@ public class AtomicEntityOperationResult implements Serializable
     {
         this(Collections.<Space> emptyList(), Collections.<Project> emptyList(),
                 Collections.<Experiment> emptyList(), Collections.<Sample> emptyList(), Collections
-                .<Sample> emptyList(), Collections.<ExternalData> emptyList(), Collections
+                .<Sample> emptyList(), Collections.<Material> emptyList(), Collections
+                .<ExternalData> emptyList(), Collections
                 .<ExternalData> emptyList());
     }
 
     public AtomicEntityOperationResult(List<Space> spacesCreated, List<Project> projectsCreated,
             List<Experiment> experimentsCreated, List<Sample> samplesUpdated,
-            List<Sample> samplesCreated, List<ExternalData> dataSetsCreated,
+            List<Sample> samplesCreated, List<Material> materialsCreated,
+            List<ExternalData> dataSetsCreated,
             List<ExternalData> dataSetsUpdated)
     {
         this.spacesCreated = new ArrayList<Space>(spacesCreated);
@@ -71,6 +76,7 @@ public class AtomicEntityOperationResult implements Serializable
         this.experimentsCreated = new ArrayList<Experiment>(experimentsCreated);
         this.samplesUpdated = new ArrayList<Sample>(samplesUpdated);
         this.samplesCreated = new ArrayList<Sample>(samplesCreated);
+        this.materialsCreated = new ArrayList<Material>(materialsCreated);
         this.dataSetsCreated = new ArrayList<ExternalData>(dataSetsCreated);
         this.dataSetsUpdated = new ArrayList<ExternalData>(dataSetsUpdated);
     }
@@ -115,4 +121,9 @@ public class AtomicEntityOperationResult implements Serializable
         return projectsCreated;
     }
 
+    public ArrayList<Material> getMaterialsCreated()
+    {
+        return materialsCreated;
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericBusinessObjectFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericBusinessObjectFactory.java
index 795344517a4..9ecaefa2c61 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericBusinessObjectFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericBusinessObjectFactory.java
@@ -28,6 +28,8 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.IMaterialTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IProjectBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleTable;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.materiallister.IMaterialLister;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.materiallister.MaterialLister;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister.ISampleLister;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister.SampleLister;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
@@ -102,4 +104,10 @@ public final class GenericBusinessObjectFactory extends AbstractPluginBusinessOb
     {
         return SampleLister.create(getDaoFactory(), session.getBaseIndexURL());
     }
+
+    public IMaterialLister createMaterialLister(Session session)
+    {
+        return MaterialLister.create(getDaoFactory(), session.getBaseIndexURL());
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServer.java
index 4ed12dc9945..cba34dec461 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServer.java
@@ -20,38 +20,31 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 import javax.annotation.Resource;
 
-import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
 import ch.rinn.restrictions.Private;
 import ch.systemsx.cisd.authentication.ISessionManager;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
-import ch.systemsx.cisd.common.logging.LogCategory;
-import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.common.spring.IInvocationLoggerContext;
 import ch.systemsx.cisd.openbis.generic.server.AbstractServer;
+import ch.systemsx.cisd.openbis.generic.server.MaterialHelper;
 import ch.systemsx.cisd.openbis.generic.server.batch.BatchOperationExecutor;
 import ch.systemsx.cisd.openbis.generic.server.batch.IBatchOperation;
 import ch.systemsx.cisd.openbis.generic.server.business.IPropertiesBatchManager;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IExperimentBO;
-import ch.systemsx.cisd.openbis.generic.server.business.bo.IMaterialTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IProjectBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleBO;
-import ch.systemsx.cisd.openbis.generic.server.business.bo.MaterialUpdateDTO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister.ISampleLister;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
 import ch.systemsx.cisd.openbis.generic.server.plugin.IDataSetTypeSlaveServerPlugin;
 import ch.systemsx.cisd.openbis.generic.server.plugin.ISampleTypeSlaveServerPlugin;
 import ch.systemsx.cisd.openbis.generic.shared.ICommonServer;
-import ch.systemsx.cisd.openbis.generic.shared.basic.CodeConverter;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AttachmentWithContent;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Code;
@@ -60,10 +53,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentUpdateResult;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListMaterialCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListOrSearchSampleCriteria;
-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.NewAttachment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewBasicExperiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewDataSet;
@@ -83,12 +73,10 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.UpdatedExperimentsWithT
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.UpdatedSample;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetUpdatesDTO;
-import ch.systemsx.cisd.openbis.generic.shared.dto.EntityTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentBatchUpdatesDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentUpdatesDTO;
-import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleBatchUpdatesDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleTypePE;
@@ -105,7 +93,6 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifierFa
 import ch.systemsx.cisd.openbis.generic.shared.dto.properties.EntityKind;
 import ch.systemsx.cisd.openbis.generic.shared.translator.AttachmentTranslator;
 import ch.systemsx.cisd.openbis.generic.shared.translator.ExperimentTranslator;
-import ch.systemsx.cisd.openbis.generic.shared.translator.MaterialTypeTranslator;
 import ch.systemsx.cisd.openbis.generic.shared.translator.SampleTranslator;
 import ch.systemsx.cisd.openbis.generic.shared.util.ServerUtils;
 import ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServer;
@@ -120,9 +107,6 @@ import ch.systemsx.cisd.openbis.plugin.generic.shared.ResourceNames;
 public final class GenericServer extends AbstractServer<IGenericServer> implements
         ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServer
 {
-    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION,
-            GenericServer.class);
-
     @Resource(name = ResourceNames.GENERIC_BUSINESS_OBJECT_FACTORY)
     private IGenericBusinessObjectFactory businessObjectFactory;
 
@@ -516,46 +500,9 @@ public final class GenericServer extends AbstractServer<IGenericServer> implemen
             final List<NewMaterial> newMaterials)
     {
         assert sessionToken != null : "Unspecified session token.";
-        assert materialTypeCode != null : "Unspecified material type.";
-        assert newMaterials != null : "Unspecified new materials.";
+        Session session = getSession(sessionToken);
+        getMaterialHelper(session).registerMaterials(materialTypeCode, newMaterials);
 
-        // Does nothing if material list is empty.
-        if (newMaterials.size() == 0)
-        {
-            return;
-        }
-        ServerUtils.prevalidate(newMaterials, "material");
-        final Session session = getSession(sessionToken);
-        final MaterialTypePE materialTypePE = findMaterialType(materialTypeCode);
-        getPropertiesBatchManager().manageProperties(materialTypePE, newMaterials,
-                session.tryGetPerson());
-        IBatchOperation<NewMaterial> strategy = new IBatchOperation<NewMaterial>()
-            {
-
-                public void execute(List<NewMaterial> entities)
-                {
-                    final IMaterialTable materialTable =
-                            businessObjectFactory.createMaterialTable(session);
-                    materialTable.add(entities, materialTypePE);
-                    materialTable.save();
-                }
-
-                public List<NewMaterial> getAllEntities()
-                {
-                    return newMaterials;
-                }
-
-                public String getEntityName()
-                {
-                    return "material";
-                }
-
-                public String getOperationName()
-                {
-                    return "register";
-                }
-            };
-        BatchOperationExecutor.executeInBatches(strategy);
     }
 
     public int updateMaterials(String sessionToken, String materialTypeCode,
@@ -563,81 +510,9 @@ public final class GenericServer extends AbstractServer<IGenericServer> implemen
             throws UserFailureException
     {
         assert sessionToken != null : "Unspecified session token.";
-        assert materialTypeCode != null : "Unspecified material type.";
-        assert newMaterials != null : "Unspecified new materials.";
-
-        // Does nothing if material list is empty.
-        if (newMaterials.size() == 0)
-        {
-            return 0;
-        }
-        class Counter
-        {
-            int count;
-        }
-        final Counter counter = new Counter();
-        ServerUtils.prevalidate(newMaterials, "material");
-        final Map<String/* code */, Material> existingMaterials =
-                listMaterials(sessionToken, materialTypeCode);
-        final Session session = getSession(sessionToken);
-
-        final MaterialTypePE materialTypePE = findMaterialType(materialTypeCode);
-        getPropertiesBatchManager().manageProperties(materialTypePE, newMaterials,
-                session.tryGetPerson());
-        IBatchOperation<NewMaterial> strategy = new IBatchOperation<NewMaterial>()
-            {
-                public void execute(List<NewMaterial> entities)
-                {
-                    List<MaterialUpdateDTO> materialUpdates = new ArrayList<MaterialUpdateDTO>();
-                    for (NewMaterial material : entities)
-                    {
-                        Material existingMaterial =
-                                existingMaterials.get(CodeConverter.tryToDatabase(material
-                                        .getCode()));
-                        if (existingMaterial != null)
-                        {
-                            materialUpdates.add(createMaterialUpdate(existingMaterial, material));
-                            counter.count++;
-                        } else if (ignoreUnregisteredMaterials == false)
-                        {
-                            throw new UserFailureException("Can not update unregistered material '"
-                                    + material.getCode()
-                                    + "'. Please use checkbox for ignoring unregistered materials.");
-                        }
-                    }
-                    updateMaterials(session.getSessionToken(), materialUpdates);
-                }
-
-                public List<NewMaterial> getAllEntities()
-                {
-                    return newMaterials;
-                }
-
-                public String getEntityName()
-                {
-                    return "material";
-                }
-
-                public String getOperationName()
-                {
-                    return "update";
-                }
-            };
-        BatchOperationExecutor.executeInBatches(strategy);
-        return counter.count;
-    }
-
-    private MaterialTypePE findMaterialType(String materialTypeCode)
-    {
-        final MaterialTypePE materialTypePE =
-                (MaterialTypePE) getDAOFactory().getEntityTypeDAO(EntityKind.MATERIAL)
-                        .tryToFindEntityTypeByCode(materialTypeCode);
-        if (materialTypePE == null)
-        {
-            throw UserFailureException.fromTemplate("Material type with code '%s' does not exist.",
-                    materialTypeCode);
-        }
-        return materialTypePE;
+        Session session = getSession(sessionToken);
+        return getMaterialHelper(session).updateMaterials(materialTypeCode, newMaterials,
+                ignoreUnregisteredMaterials);
     }
 
     public AttachmentWithContent getProjectFileAttachment(String sessionToken, TechId projectId,
@@ -707,71 +582,9 @@ public final class GenericServer extends AbstractServer<IGenericServer> implemen
     public void registerOrUpdateMaterials(String sessionToken, String materialTypeCode,
             List<NewMaterial> materials)
     {
-        Map<String/* code */, Material> existingMaterials =
-                listMaterials(sessionToken, materialTypeCode);
-        List<NewMaterial> materialsToRegister = new ArrayList<NewMaterial>();
-        List<MaterialUpdateDTO> materialUpdates = new ArrayList<MaterialUpdateDTO>();
-        for (NewMaterial material : materials)
-        {
-            Material existingMaterial =
-                    existingMaterials.get(CodeConverter.tryToDatabase(material.getCode()));
-            if (existingMaterial != null)
-            {
-                materialUpdates.add(createMaterialUpdate(existingMaterial, material));
-            } else
-            {
-                materialsToRegister.add(material);
-            }
-        }
-        registerMaterials(sessionToken, materialTypeCode, materialsToRegister);
-        updateMaterials(sessionToken, materialUpdates);
-        operationLog.info(String.format("Number of newly registered materials: %d, "
-                + "number of existing materials which have been updated: %d",
-                materialsToRegister.size(), materialUpdates.size()));
-
-    }
-
-    private static MaterialUpdateDTO createMaterialUpdate(Material existingMaterial,
-            NewMaterial material)
-    {
-        return new MaterialUpdateDTO(new TechId(existingMaterial.getId()), Arrays.asList(material
-                .getProperties()), existingMaterial.getModificationDate());
-    }
-
-    private void updateMaterials(String sessionToken, List<MaterialUpdateDTO> updates)
-    {
-        if (updates.size() == 0)
-        {
-            return;
-        }
+        assert sessionToken != null : "Unspecified session token.";
         final Session session = getSession(sessionToken);
-        IMaterialTable materialTable = businessObjectFactory.createMaterialTable(session);
-        materialTable.update(updates);
-        materialTable.save();
-    }
-
-    private Map<String/* code */, Material> listMaterials(String sessionToken,
-            String materialTypeCode)
-    {
-        checkSession(sessionToken);
-        EntityTypePE entityTypePE =
-                getDAOFactory().getEntityTypeDAO(EntityKind.MATERIAL).tryToFindEntityTypeByCode(
-                        materialTypeCode);
-        MaterialType materialType = MaterialTypeTranslator.translateSimple(entityTypePE);
-        List<Material> materials =
-                commonServer.listMaterials(sessionToken, new ListMaterialCriteria(materialType),
-                        false);
-        return asCodeToMaterialMap(materials);
-    }
-
-    private static Map<String, Material> asCodeToMaterialMap(List<Material> materials)
-    {
-        Map<String, Material> map = new HashMap<String, Material>();
-        for (Material material : materials)
-        {
-            map.put(material.getCode(), material);
-        }
-        return map;
+        getMaterialHelper(session).registerOrUpdateMaterials(materialTypeCode, materials);
     }
 
     public void registerExperiments(String sessionToken, NewExperimentsWithType experiments)
@@ -888,4 +701,12 @@ public final class GenericServer extends AbstractServer<IGenericServer> implemen
         return experiments;
     }
 
+    private MaterialHelper getMaterialHelper(final Session session)
+    {
+        final MaterialHelper materialHelper =
+                new MaterialHelper(session, businessObjectFactory, getDAOFactory(),
+                        getPropertiesBatchManager());
+        return materialHelper;
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/IGenericBusinessObjectFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/IGenericBusinessObjectFactory.java
index cd36a3b0985..eadeb1dd2ae 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/IGenericBusinessObjectFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/IGenericBusinessObjectFactory.java
@@ -16,13 +16,12 @@
 
 package ch.systemsx.cisd.openbis.plugin.generic.server;
 
+import ch.systemsx.cisd.openbis.generic.server.business.bo.IAbstractBussinessObjectFactory;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IDataBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IDataSetTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IExperimentBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IExperimentTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IInvalidationBO;
-import ch.systemsx.cisd.openbis.generic.server.business.bo.IMaterialBO;
-import ch.systemsx.cisd.openbis.generic.server.business.bo.IMaterialTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IProjectBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleTable;
@@ -34,7 +33,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
  * 
  * @author Christian Ribeaud
  */
-public interface IGenericBusinessObjectFactory
+public interface IGenericBusinessObjectFactory extends IAbstractBussinessObjectFactory
 {
     /**
      * Creates a {@link ISampleBO} <i>Business Object</i>.
@@ -56,11 +55,6 @@ public interface IGenericBusinessObjectFactory
      */
     public IExperimentBO createExperimentBO(final Session session);
 
-    /**
-     * Creates a {@link IMaterialBO} <i>Business Object</i>.
-     */
-    public IMaterialBO createMaterialBO(final Session session);
-
     /**
      * Creates a {@link IDataBO} <i>Business Object</i>.
      */
@@ -71,11 +65,6 @@ public interface IGenericBusinessObjectFactory
      */
     public ISampleTable createSampleTable(final Session session);
 
-    /**
-     * Creates a {@link IMaterialTable} <i>Business Object</i>.
-     */
-    public IMaterialTable createMaterialTable(Session session);
-
     /**
      * Creates a {@link IDataSetTable} <i>Business Object</i>.
      */
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java
index 3ea32e1518a..2c63bb0434b 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java
@@ -23,9 +23,11 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.hamcrest.BaseMatcher;
@@ -57,6 +59,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListOrSearchSampleCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewAttachment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewProject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSpace;
@@ -81,6 +84,8 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DatastoreServiceDescriptions;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExternalDataPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.InvalidationPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PropertyTypePE;
@@ -93,6 +98,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.SampleTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleTypePropertyTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleUpdatesDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SimpleDataSetInformationDTO;
+import ch.systemsx.cisd.openbis.generic.shared.dto.builders.DatabaseInstancePEBuilder;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.DatabaseInstanceIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.properties.EntityKind;
@@ -914,6 +920,17 @@ public class ETLServiceTest extends AbstractServerTestCase
                 new SampleUpdatesDTO(CommonTestUtils.TECH_ID, null, null, attachments, version,
                         sampleIdentifier, null, null);
 
+        final MaterialPE material = new MaterialPE();
+        material.setCode("new-material");
+        final MaterialTypePE materialType = new MaterialTypePE();
+        materialType.setCode("new-material-type");
+        materialType.setDatabaseInstance(new DatabaseInstancePEBuilder().code("db")
+                .getDatabaseInstance());
+        final NewMaterial newMaterial = new NewMaterial(material.getCode());
+        Map<String, List<NewMaterial>> materialRegistrations =
+                new HashMap<String, List<NewMaterial>>();
+        materialRegistrations.put(materialType.getCode(), Arrays.asList(newMaterial));
+
         final SamplePE newSamplePE = createSampleWithExperiment(experiment);
         newSamplePE.setCode("sample code new");
         final SampleIdentifier newSampleIdentifier = newSamplePE.getSampleIdentifier();
@@ -932,6 +949,30 @@ public class ETLServiceTest extends AbstractServerTestCase
         dataSetUpdate.setModifiedContainedDatasetCodesOrNull(new String[]
             { "c1", "c2" });
 
+        context.checking(new Expectations()
+            {
+            {
+                    allowing(daoFactory).getEntityTypeDAO(EntityKind.MATERIAL);
+                    will(returnValue(entityTypeDAO));
+
+                    allowing(entityTypeDAO).tryToFindEntityTypeByCode(
+                            materialType.getCode());
+                    will(returnValue(materialType));
+
+                    final List<NewMaterial> newMaterials = Arrays.asList(newMaterial);
+                    one(propertiesBatchManager).manageProperties(materialType, newMaterials, null);
+
+                    one(boFactory).createMaterialTable(SESSION);
+                    will(returnValue(materialTable));
+
+                    one(materialTable).add(newMaterials, materialType);
+                    one(materialTable).save();
+                    one(materialTable).getMaterials();
+                    will(returnValue(Arrays.asList(material)));
+
+                }
+            });
+
         context.checking(new Expectations()
             {
                 {
@@ -998,7 +1039,7 @@ public class ETLServiceTest extends AbstractServerTestCase
                 new AtomicEntityOperationDetails(null, new ArrayList<NewSpace>(),
                         new ArrayList<NewProject>(), new ArrayList<NewExperiment>(),
                         Collections.singletonList(sampleUpdate),
-                        Collections.singletonList(newSample),
+                        Collections.singletonList(newSample), materialRegistrations,
                         Collections.singletonList(externalData),
                         Collections.singletonList(dataSetUpdate));
 
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected
index 2d773460d3d..cc540753cfe 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected
@@ -57,6 +57,8 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListSampleCriteria;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSamplesWithTypes;
@@ -120,20 +122,11 @@ public interface IETLLIMSService extends IServer, ISessionProvider
             throws UserFailureException;
 
     /**
-     * Gets a sample with the specified identifier. Sample is enriched with properties and the
-     * experiment with properties.
-     * 
-     * @param sessionToken the user authentication token. Must not be <code>null</code>.
-     * @param sampleIdentifier an identifier which uniquely identifies the sample.
-     * @return <code>null</code> if no sample attached to an experiment could be found for given
-     *         <var>sampleIdentifier</var>.
+     * For given {@link MaterialIdentifier} returns the corresponding {@link Material}.
      */
     @Transactional(readOnly = true)
     @RolesAllowed(RoleWithHierarchy.SPACE_ETL_SERVER)
-    public Sample tryGetSampleWithExperiment(
-            final String sessionToken,
-            @AuthorizationGuard(guardClass = ExistingSampleOwnerIdentifierPredicate.class) final SampleIdentifier sampleIdentifier)
-            throws UserFailureException;
+    public Material tryGetMaterial(String sessionToken, MaterialIdentifier materialIdentifier);
 
     /**
      * Tries to get the identifier of sample with specified permanent ID.
@@ -154,6 +147,22 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     public ExperimentType getExperimentType(String sessionToken, String experimentTypeCode)
             throws UserFailureException;
 
+    /**
+     * Gets a sample with the specified identifier. Sample is enriched with properties and the
+     * experiment with properties.
+     * 
+     * @param sessionToken the user authentication token. Must not be <code>null</code>.
+     * @param sampleIdentifier an identifier which uniquely identifies the sample.
+     * @return <code>null</code> if no sample attached to an experiment could be found for given
+     *         <var>sampleIdentifier</var>.
+     */
+    @Transactional(readOnly = true)
+    @RolesAllowed(RoleWithHierarchy.SPACE_ETL_SERVER)
+    public Sample tryGetSampleWithExperiment(
+            final String sessionToken,
+            @AuthorizationGuard(guardClass = ExistingSampleOwnerIdentifierPredicate.class) final SampleIdentifier sampleIdentifier)
+            throws UserFailureException;
+
     /**
      * Returns a list of terms belonging to given vocabulary.
      */
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/AtomicEntityOperationDetailsTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/AtomicEntityOperationDetailsTest.java
index bcde99654f1..8dfee947408 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/AtomicEntityOperationDetailsTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/AtomicEntityOperationDetailsTest.java
@@ -17,7 +17,9 @@
 package ch.systemsx.cisd.openbis.generic.shared.basic.dto;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.testng.AssertJUnit;
 import org.testng.annotations.Test;
@@ -42,6 +44,16 @@ public class AtomicEntityOperationDetailsTest extends AssertJUnit
         spaceRegistrations.add(new NewSpace("SPACE1", "description", "adminUser1"));
         spaceRegistrations.add(new NewSpace("SPACE2", "description", "adminUser2"));
 
+        Map<String, List<NewMaterial>> materialRegistrations =
+                new HashMap<String, List<NewMaterial>>();
+        List<NewMaterial> newMaterials1 = new ArrayList<NewMaterial>();
+        newMaterials1.add(new NewMaterial("material-one"));
+        newMaterials1.add(new NewMaterial("material-two"));
+        List<NewMaterial> newMaterials2 = new ArrayList<NewMaterial>();
+        newMaterials2.add(new NewMaterial("material-three"));
+        materialRegistrations.put("material-type-1", newMaterials1);
+        materialRegistrations.put("material-type-2", newMaterials2);
+
         ArrayList<NewProject> projectRegistrations = new ArrayList<NewProject>();
         projectRegistrations.add(new NewProject("/SPACE/P1", "description"));
         projectRegistrations.add(new NewProject("/SPACE/P2", "description"));
@@ -73,7 +85,7 @@ public class AtomicEntityOperationDetailsTest extends AssertJUnit
         AtomicEntityOperationDetails details =
                 new AtomicEntityOperationDetails(null, spaceRegistrations, projectRegistrations,
                         experimentRegistrations, sampleUpdates, sampleRegistrations,
-                        dataSetRegistrations, dataSetUpdates);
+                        materialRegistrations, dataSetRegistrations, dataSetUpdates);
 
         assertEquals(
                 "AtomicEntityOperationDetails[userIdOrNull=<null>"
@@ -83,6 +95,7 @@ public class AtomicEntityOperationDetailsTest extends AssertJUnit
                         + ",experimentRegistrations=[/SPACE/PROJECT/EXP-ID1, /SPACE/PROJECT/EXP-ID2]"
                         + ",sampleUpdates=[]"
                         + ",sampleRegistrations=[/SPACE/SAMPLE-ID1, /SPACE/SAMPLE-ID2]"
+                        + ",materialRegistrations={material-type-1=[material-one, material-two], material-type-2=[material-three]}"
                         + ",dataSetRegistrations=[NewExternalData[code=DATA-SET-CODE,type=<null>,fileFormat=<null>,properties=[]]]"
                         + ",dataSetUpdates=[1]]",
                 details.toString());
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServerTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServerTest.java
index 4b01f7a5526..e41ed0567f8 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServerTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServerTest.java
@@ -71,6 +71,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleRelationshipPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleUpdatesDTO;
+import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifierFactory;
@@ -461,6 +462,7 @@ public final class GenericServerTest extends AbstractServerTestCase
 
                     one(materialTable).add(newMaterials, materialTypePE);
                     one(materialTable).save();
+                    one(materialTable).getMaterials();
                 }
             });
         createServer().registerMaterials(SESSION_TOKEN, typeCode, newMaterials);
@@ -564,8 +566,10 @@ public final class GenericServerTest extends AbstractServerTestCase
                     }
                     existingMaterials.add(createMaterial(createNewMaterial("A")));
 
-                    one(commonServer).listMaterials(with(SESSION_TOKEN),
-                            with(new BaseMatcher<ListMaterialCriteria>()
+                    one(genericBusinessObjectFactory)
+                            .createMaterialLister(with(any(Session.class)));
+                    will(returnValue(materialLister));
+                    one(materialLister).list(with(new BaseMatcher<ListMaterialCriteria>()
                                 {
                                     public boolean matches(Object item)
                                     {
-- 
GitLab