From 52907fc0232dd61fb60ebbb98274c32c934cf36f Mon Sep 17 00:00:00 2001
From: buczekp <buczekp>
Date: Tue, 7 Jun 2011 19:13:45 +0000
Subject: [PATCH] [LMS-2281] modify all properties of an entity in one
 transaction; show error messages

SVN: 21627
---
 .../ui/grid/AbstractBrowserGrid.java          |  37 ++++--
 .../ui/grid/AbstractEntityGrid.java           |   2 +-
 .../web/client/dto/EntityPropertyUpdates.java |  22 ++--
 .../dto/EntityPropertyUpdatesResult.java      |  22 ++--
 .../web/client/dto/PropertyUpdates.java       |  71 +++++++++++
 .../web/server/CommonClientService.java       |  50 ++++----
 .../openbis/generic/server/CommonServer.java  | 111 +++++++++++-------
 .../generic/server/CommonServerLogger.java    |  34 +++---
 .../openbis/generic/shared/ICommonServer.java |  29 +++--
 .../generic/shared/util/EntityHelper.java     |  21 ++--
 10 files changed, 264 insertions(+), 135 deletions(-)
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/PropertyUpdates.java

diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractBrowserGrid.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractBrowserGrid.java
index e4985d1fbc2..e084122ff98 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractBrowserGrid.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractBrowserGrid.java
@@ -17,7 +17,6 @@
 package ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -1011,7 +1010,6 @@ public abstract class AbstractBrowserGrid<T/* Entity */, M extends BaseEntityMod
 
     private void clearModifications()
     {
-        // allModifications = 0;
         finishedModifications = 0;
         failedModifications.clear();
         modificationsByModel.clear();
@@ -1893,12 +1891,10 @@ public abstract class AbstractBrowserGrid<T/* Entity */, M extends BaseEntityMod
         }
     }
 
-    private final Map<M, List<String>> failedModifications = new LinkedHashMap<M, List<String>>();
+    private final Map<M, String> failedModifications = new HashMap<M, String>();
 
     private int finishedModifications = 0;
 
-    // private int allModifications = 0;
-
     private final Map<M, List<IModification>> modificationsByModel =
             new LinkedHashMap<M, List<IModification>>();
 
@@ -1912,7 +1908,6 @@ public abstract class AbstractBrowserGrid<T/* Entity */, M extends BaseEntityMod
             modificationsByModel.put(model, modificationsForModel);
         }
         modificationsForModel.add(new Modification(columnID, newValueOrNull));
-        // allModifications++;
     }
 
     /**
@@ -1948,9 +1943,10 @@ public abstract class AbstractBrowserGrid<T/* Entity */, M extends BaseEntityMod
                 protected void process(EntityPropertyUpdatesResult result)
                 {
                     finishedModifications++;
-                    if (result.getErrors().size() > 0)
+                    String errorMessage = result.tryGetErrorMessage();
+                    if (errorMessage != null)
                     {
-                        handleErrors(result.getErrors());
+                        handleError(errorMessage);
                     }
                     if (isApplyModificationsComplete())
                     {
@@ -1962,16 +1958,16 @@ public abstract class AbstractBrowserGrid<T/* Entity */, M extends BaseEntityMod
                 public void finishOnFailure(Throwable caught)
                 {
                     finishedModifications++;
-                    handleErrors(Arrays.asList(caught.getMessage()));
+                    handleError(caught.getMessage());
                     if (isApplyModificationsComplete())
                     {
                         onApplyModificationsComplete();
                     }
                 }
 
-                private void handleErrors(List<String> errors)
+                private void handleError(String errorMessage)
                 {
-                    failedModifications.put(model, errors);
+                    failedModifications.put(model, errorMessage);
                 }
             };
     }
@@ -1985,8 +1981,11 @@ public abstract class AbstractBrowserGrid<T/* Entity */, M extends BaseEntityMod
     {
         if (failedModifications.size() > 0)
         {
-            MessageBox.alert("Operation failed", failedModifications.size()
-                    + " modification(s) couldn't be applied.", null);
+            String failureTitle =
+                    (failedModifications.size() == modificationsByModel.size()) ? "Operation failed"
+                            : "Operation partly failed";
+            String failureReport = createFailedModificationsReport();
+            MessageBox.alert(failureTitle, failureReport, null);
             refresh();
         } else
         {
@@ -1996,6 +1995,18 @@ public abstract class AbstractBrowserGrid<T/* Entity */, M extends BaseEntityMod
         clearModifications();
     }
 
+    private String createFailedModificationsReport()
+    {
+        assert failedModifications.size() > 0;
+        StringBuilder result = new StringBuilder();
+        result.append("Modifications of " + failedModifications.size() + " entities failed:");
+        for (String error : failedModifications.values())
+        {
+            result.append("<br/>- " + error);
+        }
+        return result.toString();
+    }
+
     /**
      * Creates a callback object which invokes specified refresh action after server-side editing
      * action took place.
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractEntityGrid.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractEntityGrid.java
index 51ca5c4682a..61f874287dc 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractEntityGrid.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractEntityGrid.java
@@ -79,7 +79,7 @@ public abstract class AbstractEntityGrid<E extends IEntityInformationHolderWithP
             List<IModification> modifications)
     {
         final EntityKind entityKind = getEntityKind();
-        final TechId entityId = new TechId(model.getBaseObject().getObjectOrNull());
+        final TechId entityId = new TechId(model.getBaseObject().getId());
         final EntityPropertyUpdates updates = new EntityPropertyUpdates(entityKind, entityId);
         for (IModification modification : modifications)
         {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/EntityPropertyUpdates.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/EntityPropertyUpdates.java
index 3bcdc07090b..b7e0852e1dd 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/EntityPropertyUpdates.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/EntityPropertyUpdates.java
@@ -16,8 +16,8 @@
 
 package ch.systemsx.cisd.openbis.generic.client.web.client.dto;
 
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.ISerializable;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
@@ -37,8 +37,7 @@ public class EntityPropertyUpdates implements ISerializable
 
     private TechId entityId;
 
-    private Map<String /* property code */, String /* new value */> modifiedProperties =
-            new LinkedHashMap<String, String>();
+    private List<PropertyUpdates> modifiedProperties = new ArrayList<PropertyUpdates>();
 
     public EntityPropertyUpdates()
     {
@@ -70,17 +69,26 @@ public class EntityPropertyUpdates implements ISerializable
         this.entityId = entityId;
     }
 
-    public Map<String, String> getModifiedProperties()
+    public List<PropertyUpdates> getModifiedProperties()
     {
         return modifiedProperties;
     }
 
+    public void addModifiedProperty(PropertyUpdates propertyUpdates)
+    {
+        modifiedProperties.add(propertyUpdates);
+    }
+
     public void addModifiedProperty(String propertyCode, String propertyValue)
     {
-        modifiedProperties.put(propertyCode, propertyValue);
+        if (modifiedProperties == null)
+        {
+            modifiedProperties = new ArrayList<PropertyUpdates>();
+        }
+        modifiedProperties.add(new PropertyUpdates(propertyCode, propertyValue));
     }
 
-    public void setModifiedProperties(Map<String, String> modifiedProperties)
+    public void setModifiedProperties(List<PropertyUpdates> modifiedProperties)
     {
         this.modifiedProperties = modifiedProperties;
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/EntityPropertyUpdatesResult.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/EntityPropertyUpdatesResult.java
index 30a222a0fc8..d58d66dedce 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/EntityPropertyUpdatesResult.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/EntityPropertyUpdatesResult.java
@@ -16,15 +16,12 @@
 
 package ch.systemsx.cisd.openbis.generic.client.web.client.dto;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import ch.systemsx.cisd.openbis.generic.shared.basic.ISerializable;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
 
 /**
- * Stores result of update of properties of an entity. Currently used only to transfer grouped
- * errors to the client side.
+ * Stores result of update of properties of an entity. Currently used only to transfer error
+ * messages to the client side.
  * 
  * @author Piotr Buczek
  */
@@ -32,25 +29,20 @@ public class EntityPropertyUpdatesResult implements ISerializable
 {
     private static final long serialVersionUID = ServiceVersionHolder.VERSION;
 
-    private List<String> errors = new ArrayList<String>();
+    private String errorMessage;
 
     public EntityPropertyUpdatesResult()
     {
     }
 
-    public void addError(String errorMessage)
-    {
-        errors.add(errorMessage);
-    }
-
-    public List<String> getErrors()
+    public void setErrorMessage(String errorMessage)
     {
-        return errors;
+        this.errorMessage = errorMessage;
     }
 
-    public void setErrors(List<String> errors)
+    public String tryGetErrorMessage()
     {
-        this.errors = errors;
+        return errorMessage;
     }
 
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/PropertyUpdates.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/PropertyUpdates.java
new file mode 100644
index 00000000000..023f769408a
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/PropertyUpdates.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2009 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.client.web.client.dto;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.ISerializable;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
+
+/**
+ * Updates of a single property.
+ * 
+ * @author Piotr Buczek
+ */
+public class PropertyUpdates implements ISerializable
+{
+    private static final long serialVersionUID = ServiceVersionHolder.VERSION;
+
+    private String propertyCode;
+
+    private String value;
+
+    public PropertyUpdates()
+    {
+    }
+
+    public PropertyUpdates(String propertyCode, String value)
+    {
+        this.propertyCode = propertyCode;
+        this.value = value;
+    }
+
+    public String getPropertyCode()
+    {
+        return propertyCode;
+    }
+
+    public void setPropertyCode(String propertyCode)
+    {
+        this.propertyCode = propertyCode;
+    }
+
+    public String getValue()
+    {
+        return value;
+    }
+
+    public void setValue(String value)
+    {
+        this.value = value;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "[property=" + propertyCode + ",value='" + value + "']";
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/CommonClientService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/CommonClientService.java
index 17377ed6a06..41a8c4ff816 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/CommonClientService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/CommonClientService.java
@@ -24,7 +24,6 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map.Entry;
 import java.util.Set;
 
 import javax.servlet.http.HttpSession;
@@ -58,6 +57,7 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListPersonsCriteri
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListSampleDisplayCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListSampleDisplayCriteria2;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListScriptsCriteria;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.PropertyUpdates;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.RelatedDataSetCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSet;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSetFetchConfig;
@@ -2817,37 +2817,33 @@ public final class CommonClientService extends AbstractClientService implements
 
         final EntityKind entityKind = updates.getEntityKind();
         final TechId entityId = updates.getEntityId();
+        final List<PropertyUpdates> modifiedProperties = updates.getModifiedProperties();
+
         final EntityPropertyUpdatesResult result = new EntityPropertyUpdatesResult();
 
-        for (Entry<String, String> entry : updates.getModifiedProperties().entrySet())
+        try
         {
-            try
-            {
-                String propertyCode = entry.getKey();
-                String value = entry.getValue();
-                switch (entityKind)
-                {
-                    case DATA_SET:
-                        commonServer.updateDataSetProperty(sessionToken, entityId, propertyCode,
-                                value);
-                        break;
-                    case EXPERIMENT:
-                        commonServer.updateExperimentProperty(sessionToken, entityId, propertyCode,
-                                value);
-                        break;
-                    case MATERIAL:
-                        commonServer.updateMaterialProperty(sessionToken, entityId, propertyCode,
-                                value);
-                        break;
-                    case SAMPLE:
-                        commonServer.updateSampleProperty(sessionToken, entityId, propertyCode,
-                                value);
-                        break;
-                }
-            } catch (final UserFailureException e)
+            switch (entityKind)
             {
-                result.addError(e.getMessage());
+                case DATA_SET:
+                    commonServer
+                            .updateDataSetProperties(sessionToken, entityId, modifiedProperties);
+                    break;
+                case EXPERIMENT:
+                    commonServer.updateExperimentProperties(sessionToken, entityId,
+                            modifiedProperties);
+                    break;
+                case MATERIAL:
+                    commonServer.updateMaterialProperties(sessionToken, entityId,
+                            modifiedProperties);
+                    break;
+                case SAMPLE:
+                    commonServer.updateSampleProperties(sessionToken, entityId, modifiedProperties);
+                    break;
             }
+        } catch (final UserFailureException e)
+        {
+            result.setErrorMessage(UserFailureExceptionTranslator.translate(e).getMessage());
         }
         return result;
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
index 36af0c1598b..a450a9b58b1 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
@@ -37,6 +37,7 @@ import ch.systemsx.cisd.authentication.ISessionManager;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.common.spring.IInvocationLoggerContext;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.PropertyUpdates;
 import ch.systemsx.cisd.openbis.generic.server.business.DetailedSearchManager;
 import ch.systemsx.cisd.openbis.generic.server.business.IPropertiesBatchManager;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.DataAccessExceptionTranslator;
@@ -84,6 +85,7 @@ import ch.systemsx.cisd.openbis.generic.server.util.GroupIdentifierHelper;
 import ch.systemsx.cisd.openbis.generic.shared.basic.BasicEntityInformationHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.CodeConverter;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolder;
+import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithPermId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IdentifierExtractor;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
@@ -2500,74 +2502,103 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt
         return dataStores.get(0).getDownloadUrl();
     }
 
-    public void updateDataSetProperty(String sessionToken, TechId entityId, String propertyName,
-            String value)
+    public void updateDataSetProperties(String sessionToken, TechId entityId,
+            List<PropertyUpdates> modifiedProperties)
     {
         checkSession(sessionToken);
         ExternalData dataSet = getDataSetInfo(sessionToken, entityId);
-        DataSetUpdatesDTO updates = new DataSetUpdatesDTO();
-        updates.setDatasetId(entityId);
-        updates.setVersion(dataSet.getModificationDate());
-        Map<String, String> properties = createPropertiesMap(propertyName, value);
-        updates.setProperties(EntityHelper.translatePropertiesMapToList(properties));
-        Experiment exp = dataSet.getExperiment();
-        if (exp != null)
-        {
-            updates.setExperimentIdentifierOrNull(ExperimentIdentifierFactory.parse(exp
-                    .getIdentifier()));
-        }
-        String sampleIdentifier = dataSet.getSampleIdentifier();
-        if (sampleIdentifier != null)
+        try
         {
-            updates.setSampleIdentifierOrNull(SampleIdentifierFactory.parse(sampleIdentifier));
-        }
-        if (dataSet instanceof DataSet)
+            DataSetUpdatesDTO updates = new DataSetUpdatesDTO();
+            updates.setDatasetId(entityId);
+            updates.setVersion(dataSet.getModificationDate());
+            Map<String, String> properties = createPropertiesMap(modifiedProperties);
+            updates.setProperties(EntityHelper.translatePropertiesMapToList(properties));
+            Experiment exp = dataSet.getExperiment();
+            if (exp != null)
+            {
+                updates.setExperimentIdentifierOrNull(ExperimentIdentifierFactory.parse(exp
+                        .getIdentifier()));
+            }
+            String sampleIdentifier = dataSet.getSampleIdentifier();
+            if (sampleIdentifier != null)
+            {
+                updates.setSampleIdentifierOrNull(SampleIdentifierFactory.parse(sampleIdentifier));
+            }
+            if (dataSet instanceof DataSet)
+            {
+                updates.setFileFormatTypeCode(((DataSet) dataSet).getFileFormatType().getCode());
+            }
+            updateDataSet(sessionToken, updates);
+        } catch (UserFailureException e)
         {
-            updates.setFileFormatTypeCode(((DataSet) dataSet).getFileFormatType().getCode());
+            throw wrapExceptionWithEntityIdentifier(e, dataSet);
         }
-        updateDataSet(sessionToken, updates);
     }
 
-    public void updateExperimentProperty(String sessionToken, TechId entityId, String propertyCode,
-            String value)
+    public void updateExperimentProperties(String sessionToken, TechId entityId,
+            List<PropertyUpdates> modifiedProperties)
     {
         checkSession(sessionToken);
         Experiment experiment = getExperimentInfo(sessionToken, entityId);
-        ExperimentUpdatesDTO updates = new ExperimentUpdatesDTO();
-        updates.setVersion(experiment.getModificationDate());
-        updates.setExperimentId(entityId);
-        updates.setAttachments(Collections.<NewAttachment> emptySet());
-        updates.setProjectIdentifier(new ProjectIdentifierFactory(experiment.getProject()
-                .getIdentifier()).createIdentifier());
-        Map<String, String> properties = createPropertiesMap(propertyCode, value);
-        updates.setProperties(EntityHelper.translatePropertiesMapToList(properties));
-        updateExperiment(sessionToken, updates);
+        try
+        {
+            ExperimentUpdatesDTO updates = new ExperimentUpdatesDTO();
+            updates.setVersion(experiment.getModificationDate());
+            updates.setExperimentId(entityId);
+            updates.setAttachments(Collections.<NewAttachment> emptySet());
+            updates.setProjectIdentifier(new ProjectIdentifierFactory(experiment.getProject()
+                    .getIdentifier()).createIdentifier());
+            Map<String, String> properties = createPropertiesMap(modifiedProperties);
+            updates.setProperties(EntityHelper.translatePropertiesMapToList(properties));
+            updateExperiment(sessionToken, updates);
+        } catch (UserFailureException e)
+        {
+            throw wrapExceptionWithEntityIdentifier(e, experiment);
+        }
     }
 
-    public void updateSampleProperty(String sessionToken, TechId entityId, String propertyCode,
-            String value)
+    public void updateSampleProperties(String sessionToken, TechId entityId,
+            List<PropertyUpdates> modifiedProperties)
     {
         checkSession(sessionToken);
-        Map<String, String> properties = createPropertiesMap(propertyCode, value);
-        EntityHelper.updateSampleProperties(this, sessionToken, entityId, properties);
+        Map<String, String> properties = createPropertiesMap(modifiedProperties);
+        Sample sample = getSampleInfo(sessionToken, entityId).getParent();
+        try
+        {
+            EntityHelper.updateSampleProperties(this, sessionToken, entityId, properties);
+        } catch (UserFailureException e)
+        {
+            throw wrapExceptionWithEntityIdentifier(e, sample);
+        }
     }
 
-    public void updateMaterialProperty(String sessionToken, TechId entityId, String propertyCode,
-            String value)
+    public void updateMaterialProperties(String sessionToken, TechId entityId,
+            List<PropertyUpdates> modifiedProperties)
     {
         checkSession(sessionToken);
         Date modificationDate =
                 getDAOFactory().getMaterialDAO().tryGetByTechId(entityId).getModificationDate();
-        Map<String, String> properties = createPropertiesMap(propertyCode, value);
+        Map<String, String> properties = createPropertiesMap(modifiedProperties);
         updateMaterial(sessionToken, entityId,
                 EntityHelper.translatePropertiesMapToList(properties), modificationDate);
     }
 
-    private Map<String, String> createPropertiesMap(String propertyCode, String value)
+    private Map<String, String> createPropertiesMap(List<PropertyUpdates> updates)
     {
         Map<String, String> properties = new HashMap<String, String>();
-        properties.put(CodeConverter.getPropertyTypeCode(propertyCode), value);
+        for (PropertyUpdates p : updates)
+        {
+            properties.put(CodeConverter.getPropertyTypeCode(p.getPropertyCode()), p.getValue());
+        }
         return properties;
     }
 
+    private static UserFailureException wrapExceptionWithEntityIdentifier(
+            UserFailureException exception, IEntityInformationHolderWithIdentifier entity)
+    {
+        return UserFailureException.fromTemplate(exception, "%s '%s': %s", entity.getEntityKind()
+                .getDescription(), entity.getIdentifier(), exception.getMessage());
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java
index 7e7bfe0c81f..e08414b8a21 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java
@@ -25,6 +25,7 @@ import org.apache.log4j.Level;
 
 import ch.systemsx.cisd.authentication.ISessionManager;
 import ch.systemsx.cisd.common.spring.IInvocationLoggerContext;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.PropertyUpdates;
 import ch.systemsx.cisd.openbis.generic.shared.AbstractServerLogger;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithPermId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
@@ -1080,32 +1081,33 @@ final class CommonServerLogger extends AbstractServerLogger implements ICommonSe
         return null;
     }
 
-    public void updateDataSetProperty(String sessionToken, TechId entityId, String propertyCode,
-            String value)
+    public void updateDataSetProperties(String sessionToken, TechId entityId,
+            List<PropertyUpdates> modifiedProperties)
     {
-        logTracking(sessionToken, "updateDataSetProperty", "ID(%s) PROPERTY(%s) VALUE(%s)",
-                entityId, propertyCode, value);
+        logTracking(sessionToken, "updateDataSetProperty", "ID(%s) MODIFIED_PROPERTIES(%s)",
+                entityId, abbreviate(modifiedProperties));
     }
 
-    public void updateExperimentProperty(String sessionToken, TechId entityId, String propertyCode,
-            String value)
+    public void updateExperimentProperties(String sessionToken, TechId entityId,
+            List<PropertyUpdates> modifiedProperties)
     {
-        logTracking(sessionToken, "updateExperimentProperty", "ID(%s) PROPERTY(%s) VALUE(%s)",
-                entityId, propertyCode, value);
+        logTracking(sessionToken, "updateExperimentProperty", "ID(%s) MODIFIED_PROPERTIES(%s)",
+                entityId, abbreviate(modifiedProperties));
     }
 
-    public void updateSampleProperty(String sessionToken, TechId entityId, String propertyCode,
-            String value)
+    public void updateSampleProperties(String sessionToken, TechId entityId,
+            List<PropertyUpdates> modifiedProperties)
     {
-        logTracking(sessionToken, "updateSampleProperty", "ID(%s) PROPERTY(%s) VALUE(%s)",
-                entityId, propertyCode, value);
+        logTracking(sessionToken, "updateSampleProperty", "ID(%s) MODIFIED_PROPERTIES(%s)",
+                entityId, abbreviate(modifiedProperties));
     }
 
-    public void updateMaterialProperty(String sessionToken, TechId entityId, String propertyCode,
-            String value)
+    public void updateMaterialProperties(String sessionToken, TechId entityId,
+            List<PropertyUpdates> modifiedProperties)
+
     {
-        logTracking(sessionToken, "updateMaterialProperty", "ID(%s) PROPERTY(%s) VALUE(%s)",
-                entityId, propertyCode, value);
+        logTracking(sessionToken, "updateMaterialProperty", "ID(%s) MODIFIED_PROPERTIES(%s)",
+                entityId, abbreviate(modifiedProperties));
     }
 
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java
index 09c133053f4..b8bc7063bfb 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java
@@ -24,6 +24,7 @@ import java.util.Set;
 import org.springframework.transaction.annotation.Transactional;
 
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.PropertyUpdates;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.AuthorizationGuard;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.ReturnValueFilter;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.RolesAllowed;
@@ -1360,27 +1361,39 @@ public interface ICommonServer extends IServer
     @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
     public String getDefaultPutDataStoreBaseURL(String sessionToken);
 
+    /**
+     * Updates properties of a data set with given id.
+     */
     @Transactional
     @RolesAllowed(RoleWithHierarchy.SPACE_USER)
-    public void updateDataSetProperty(String sessionToken,
+    public void updateDataSetProperties(String sessionToken,
             @AuthorizationGuard(guardClass = DataSetTechIdPredicate.class) TechId entityId,
-            String propertyCode, String value);
+            List<PropertyUpdates> modifiedProperties);
 
+    /**
+     * Updates properties of an experiment with given id.
+     */
     @Transactional
     @RolesAllowed(RoleWithHierarchy.SPACE_USER)
-    public void updateExperimentProperty(String sessionToken,
+    public void updateExperimentProperties(String sessionToken,
             @AuthorizationGuard(guardClass = ExperimentTechIdPredicate.class) TechId entityId,
-            String propertyCode, String value);
+            List<PropertyUpdates> modifiedProperties);
 
+    /**
+     * Updates properties of a sample with given id.
+     */
     @Transactional
     @RolesAllowed(RoleWithHierarchy.SPACE_USER)
-    public void updateSampleProperty(String sessionToken,
+    public void updateSampleProperties(String sessionToken,
             @AuthorizationGuard(guardClass = SampleTechIdPredicate.class) TechId entityId,
-            String propertyCode, String value);
+            List<PropertyUpdates> modifiedProperties);
 
+    /**
+     * Updates properties of a material with given id.
+     */
     @Transactional
     @RolesAllowed(RoleWithHierarchy.INSTANCE_ADMIN)
-    public void updateMaterialProperty(String sessionToken, TechId entityId, String propertyCode,
-            String value);
+    public void updateMaterialProperties(String sessionToken, TechId entityId,
+            List<PropertyUpdates> modifiedProperties);
 
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/util/EntityHelper.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/util/EntityHelper.java
index e1799004535..9e9f5600841 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/util/EntityHelper.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/util/EntityHelper.java
@@ -134,11 +134,9 @@ public class EntityHelper
         return (property != null) ? property.tryGetOriginalValue() : null;
     }
 
-
-    public static void updateSampleProperties(ICommonServer server, String sessionToken, TechId id,
-            Map<String, String> properties)
+    public static void updateSampleProperties(ICommonServer server, String sessionToken,
+            Sample sample, Map<String, String> properties)
     {
-        Sample sample = server.getSampleInfo(sessionToken, id).getParent();
         List<IEntityProperty> props = translatePropertiesMapToList(properties);
         Experiment experiment = sample.getExperiment();
         ExperimentIdentifier experimentIdentifier =
@@ -148,12 +146,19 @@ public class EntityHelper
         Sample container = sample.getContainer();
         String containerIdentifier = container == null ? null : container.getIdentifier();
         SampleUpdatesDTO updates =
-                new SampleUpdatesDTO(id, props, experimentIdentifier,
+                new SampleUpdatesDTO(new TechId(sample), props, experimentIdentifier,
                         Collections.<NewAttachment> emptySet(), sample.getModificationDate(),
                         sampleIdentifier, containerIdentifier, null);
         server.updateSample(sessionToken, updates);
     }
 
+    public static void updateSampleProperties(ICommonServer server, String sessionToken, TechId id,
+            Map<String, String> properties)
+    {
+        Sample sample = server.getSampleInfo(sessionToken, id).getParent();
+        updateSampleProperties(server, sessionToken, sample, properties);
+    }
+
     public static List<IEntityProperty> translatePropertiesMapToList(Map<String, String> properties)
     {
         List<IEntityProperty> props = new ArrayList<IEntityProperty>();
@@ -163,10 +168,10 @@ public class EntityHelper
         }
         return props;
     }
-    
+
     /**
-     * Creates a property with specified code and value. An already existing property with same
-     * code will be removed.
+     * Creates a property with specified code and value. An already existing property with same code
+     * will be removed.
      */
     public static void createOrUpdateProperty(IEntityPropertiesHolder holder, String propertyCode,
             String propertyValue)
-- 
GitLab