From cb80c8252663aefc3851ff293d288860441e3e06 Mon Sep 17 00:00:00 2001
From: felmer <felmer>
Date: Wed, 5 Sep 2012 13:21:11 +0000
Subject: [PATCH] BIS-166: Refactoring
 TypedTablegrid.TableModificationsManager. Supressing cell editing when
 modifications are applied.

SVN: 26521
---
 .../web/client/ICommonClientService.java      |   4 +-
 .../web/client/ICommonClientServiceAsync.java |   4 +-
 .../client/application/ui/TypedTableGrid.java | 207 +++++-------------
 .../ui/grid/AbstractEntityGrid.java           |   7 +-
 .../ui/grid/ITableModificationsManager.java   |  48 ----
 .../application/ui/grid/Modification.java     |  45 ++++
 .../ui/grid/ModificationsData.java            | 135 ++++++++++++
 .../dto/EntityPropertyUpdatesResult.java      |   3 +-
 .../client/web/client/dto/IUpdateResult.java  |  31 +++
 9 files changed, 281 insertions(+), 203 deletions(-)
 delete mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ITableModificationsManager.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/Modification.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ModificationsData.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/IUpdateResult.java

diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientService.java
index edcf33984cf..c03e8fe8e35 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientService.java
@@ -27,8 +27,8 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.dto.DisplayedCriteriaO
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.DisplayedOrSelectedDatasetCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.DisplayedOrSelectedIdHolderCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.EntityPropertyUpdates;
-import ch.systemsx.cisd.openbis.generic.client.web.client.dto.EntityPropertyUpdatesResult;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.IResultSetConfig;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.IUpdateResult;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListEntityHistoryCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListExperimentsCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListMaterialDisplayCriteria;
@@ -1116,7 +1116,7 @@ public interface ICommonClientService extends IClientService
     /**
      * Updates specified properties of an entity.
      */
-    public EntityPropertyUpdatesResult updateProperties(EntityPropertyUpdates updates)
+    public IUpdateResult updateProperties(EntityPropertyUpdates updates)
             throws UserFailureException;
 
     /**
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientServiceAsync.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientServiceAsync.java
index eaa2dd6b9a0..2566bfdd816 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientServiceAsync.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientServiceAsync.java
@@ -29,8 +29,8 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.dto.DisplayedCriteriaO
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.DisplayedOrSelectedDatasetCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.DisplayedOrSelectedIdHolderCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.EntityPropertyUpdates;
-import ch.systemsx.cisd.openbis.generic.client.web.client.dto.EntityPropertyUpdatesResult;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.IResultSetConfig;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.IUpdateResult;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListEntityHistoryCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListExperimentsCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListMaterialDisplayCriteria;
@@ -1020,7 +1020,7 @@ public interface ICommonClientServiceAsync extends IClientServiceAsync
      * @see ICommonClientService#updateProperties(EntityPropertyUpdates)
      */
     public void updateProperties(EntityPropertyUpdates entityPropertyUpdates,
-            AsyncCallback<EntityPropertyUpdatesResult> callback);
+            AsyncCallback<IUpdateResult> callback);
 
     /** @see ICommonClientService#listDeletions(DefaultResultSetConfig) */
     public void listDeletions(
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TypedTableGrid.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TypedTableGrid.java
index 7f5cceb472b..82b3fdcbf27 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TypedTableGrid.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TypedTableGrid.java
@@ -22,10 +22,8 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 
 import com.extjs.gxt.ui.client.GXT;
@@ -123,7 +121,7 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.IC
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.IDisplayTypeIDProvider;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.IDisposableComponent;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.IModification;
-import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.ITableModificationsManager;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.ModificationsData;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.PendingFetchManager;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.TableExportType;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.expressions.filter.FilterToolbar;
@@ -135,14 +133,13 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.GWTUt
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IDelegatedAction;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IMessageProvider;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.WindowUtils;
-import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.lang.StringEscapeUtils;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.CommonGridColumnIDs;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.Constants;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.DefaultResultSetConfig;
-import ch.systemsx.cisd.openbis.generic.client.web.client.dto.EntityPropertyUpdatesResult;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.GridCustomColumnInfo;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.GridFilters;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.GridRowModels;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.IUpdateResult;
 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;
@@ -309,38 +306,11 @@ public abstract class TypedTableGrid<T extends Serializable> extends LayoutConta
         }
     }
 
-    private static class Modification implements IModification
-    {
-        private final String columnID;
-
-        private final String newValueOrNull;
-
-        public Modification(String columnID, String newValueOrNull)
-        {
-            super();
-            this.columnID = columnID;
-            this.newValueOrNull =
-                    newValueOrNull == null ? null : StringEscapeUtils.unescapeHtml(newValueOrNull);
-        }
-
-        @Override
-        public String getColumnID()
-        {
-            return columnID;
-        }
-
-        @Override
-        public String tryGetNewValue()
-        {
-            return newValueOrNull;
-        }
-    }
-
     protected final IViewContext<ICommonClientServiceAsync> viewContext;
 
     protected final ICellListener<TableModelRowWithObject<T>> showEntityViewerLinkClickListener;
 
-    protected final ITableModificationsManager<BaseEntityModel<TableModelRowWithObject<T>>> tableModificationsManager;
+    protected final TableModificationsManager tableModificationsManager;
 
     // ------ private section. NOTE: it should remain unaccessible to subclasses! ---------------
 
@@ -476,6 +446,7 @@ public abstract class TypedTableGrid<T extends Serializable> extends LayoutConta
             removeButtons(PagingToolBarButtonKind.CONFIG, PagingToolBarButtonKind.REFRESH);
         }
         setId(browserId);
+
     }
 
     public void removeButtons(PagingToolBarButtonKind... buttonKinds)
@@ -2105,6 +2076,12 @@ public abstract class TypedTableGrid<T extends Serializable> extends LayoutConta
                                                 "Sorry, table cell editing is not allowed in current viewing mode",
                                                 null);
                                 event.setCancelled(true);
+                            } else if (tableModificationsManager.isSaving())
+                            {
+                                MessageBox.info("Not Allowed",
+                                        "Sorry, table cell editing is not allowed during "
+                                                + "saving of recently changed table cells.", null);
+                                event.setCancelled(true);
                             } else
                             {
                                 BaseEntityModel<TableModelRowWithObject<T>> model =
@@ -2325,81 +2302,42 @@ public abstract class TypedTableGrid<T extends Serializable> extends LayoutConta
      * implementation does nothing.
      */
     protected void applyModifications(BaseEntityModel<TableModelRowWithObject<T>> model,
-            List<IModification> modifications)
+            List<IModification> modifications, AsyncCallback<IUpdateResult> callBack)
     {
 
     }
 
-    private class TableModificationsManager implements
-            ITableModificationsManager<BaseEntityModel<TableModelRowWithObject<T>>>
+    private class TableModificationsManager
     {
-        private final Map<BaseEntityModel<TableModelRowWithObject<T>>, List<IModification>> modificationsByModel =
-                new LinkedHashMap<BaseEntityModel<TableModelRowWithObject<T>>, List<IModification>>();
+        private final ModificationsData<T> modificationsData = new ModificationsData<T>();
 
-        private final Map<BaseEntityModel<TableModelRowWithObject<T>>, String> failedModifications =
-                new HashMap<BaseEntityModel<TableModelRowWithObject<T>>, String>();
-
-        private int finishedModifications = 0;
-
-        private IDelegatedAction afterSaveActionOrNull;
-
-        //
-        // ITableModificationsManager
-        //
+        public boolean isSaving()
+        {
+            return modificationsData.isSaving();
+        }
 
-        // @Override
-        @Override
         public boolean isTableDirty()
         {
-            return isApplyModificationsComplete() == false;
+            return modificationsData.isApplyModificationsComplete() == false
+                    && modificationsData.isSaving() == false;
         }
 
-        @Override
         public void saveModifications()
         {
-            saveModifications(new IDelegatedAction()
+            modificationsData.handleModifications(new ModificationsData.IModificationsHandler<T>()
                 {
-
                     @Override
-                    public void execute()
+                    public void applyModifications(
+                            BaseEntityModel<TableModelRowWithObject<T>> model,
+                            List<IModification> modifications)
                     {
-                        DefaultResultSetConfig<String, TableModelRowWithObject<T>> config =
-                                createPagingConfig(new BasePagingLoadConfig(),
-                                        filterToolbar.getFilters(), getGridDisplayTypeID());
-                        config.setCacheConfig(ResultSetFetchConfig
-                                .createRecomputeAndCache(resultSetKeyOrNull));
-                        final int id = TypedTableGrid.this.log("refreshing cache silently");
-                        listTableRows(config, new AbstractAsyncCallback<TypedTableResultSet<T>>(
-                                viewContext)
-                            {
-                                @Override
-                                protected void process(TypedTableResultSet<T> result)
-                                {
-                                    viewContext.logStop(id);
-                                }
-                            });
+                        AsyncCallback<IUpdateResult> callBack =
+                                createApplyModificationsCallback(model, modifications);
+                        TypedTableGrid.this.applyModifications(model, modifications, callBack);
                     }
                 });
         }
 
-        private void setAfterSaveAction(IDelegatedAction afterSaveAction)
-        {
-            this.afterSaveActionOrNull = afterSaveAction;
-        }
-
-        @Override
-        public void saveModifications(IDelegatedAction afterSaveAction)
-        {
-            setAfterSaveAction(afterSaveAction);
-            finishedModifications = 0;
-            for (Entry<BaseEntityModel<TableModelRowWithObject<T>>, List<IModification>> entry : modificationsByModel
-                    .entrySet())
-            {
-                applyModifications(entry.getKey(), entry.getValue());
-            }
-        }
-
-        @Override
         public void cancelModifications()
         {
             clearModifications();
@@ -2407,74 +2345,48 @@ public abstract class TypedTableGrid<T extends Serializable> extends LayoutConta
             refresh(); // WORKAROUND remove this refresh after LMS-2397 is resolved
         }
 
-        @Override
         public void handleEditingEvent(BaseEntityModel<TableModelRowWithObject<T>> model,
                 String columnID, String newValueOrNull)
         {
-            List<IModification> modificationsForModel = modificationsByModel.get(model);
-            if (modificationsForModel == null)
-            {
-                modificationsForModel = new ArrayList<IModification>();
-                modificationsByModel.put(model, modificationsForModel);
-            }
-            modificationsForModel.add(new Modification(columnID, newValueOrNull));
+            modificationsData.addModification(model, columnID, newValueOrNull);
         }
 
-        @Override
-        public AsyncCallback<EntityPropertyUpdatesResult> createApplyModificationsCallback(
+        private AsyncCallback<IUpdateResult> createApplyModificationsCallback(
                 final BaseEntityModel<TableModelRowWithObject<T>> model,
                 final List<IModification> modifications)
         {
-            return new AbstractAsyncCallback<EntityPropertyUpdatesResult>(viewContext)
+            return new AbstractAsyncCallback<IUpdateResult>(viewContext)
                 {
                     @Override
-                    protected void process(EntityPropertyUpdatesResult result)
+                    protected void process(IUpdateResult result)
                     {
-                        finishedModifications++;
-                        String errorMessage = result.tryGetErrorMessage();
-                        if (errorMessage != null)
-                        {
-                            handleError(errorMessage);
-                        }
-                        if (isApplyModificationsComplete())
-                        {
-                            onApplyModificationsComplete(model);
-                        }
+                        processErrorMessage(result.tryGetErrorMessage());
                     }
 
                     @Override
                     public void finishOnFailure(Throwable caught)
                     {
-                        finishedModifications++;
-                        handleError(caught.getMessage());
-                        if (isApplyModificationsComplete())
-                        {
-                            onApplyModificationsComplete(model);
-                        }
+                        processErrorMessage(caught.getMessage());
                     }
 
-                    private void handleError(String errorMessage)
+                    private void processErrorMessage(String errorMessageOrNull)
                     {
-                        failedModifications.put(model, errorMessage);
+                        modificationsData.handleResponseAfterModificationHasBeenApplied(model,
+                                errorMessageOrNull);
+                        if (modificationsData.isApplyModificationsComplete())
+                        {
+                            onApplyModificationsComplete(model);
+                        }
                     }
                 };
         }
 
-        //
-
-        private boolean isApplyModificationsComplete()
-        {
-            return finishedModifications == modificationsByModel.size();
-        }
-
         private void onApplyModificationsComplete(BaseEntityModel<TableModelRowWithObject<T>> model)
         {
-            if (failedModifications.size() > 0)
+            if (modificationsData.hasFailedModifications())
             {
-                String failureTitle =
-                        (failedModifications.size() == modificationsByModel.size()) ? "Operation failed"
-                                : "Operation partly failed";
-                String failureReport = createFailedModificationsReport();
+                String failureTitle = modificationsData.createFailureTitle();
+                String failureReport = modificationsData.createFailedModificationsReport();
                 MessageBox.alert(failureTitle, failureReport, null);
                 refresh();
             } else
@@ -2487,29 +2399,30 @@ public abstract class TypedTableGrid<T extends Serializable> extends LayoutConta
                 grid.getStore().commitChanges(); // no need to refresh - everything should be valid
             }
             clearModifications();
-            if (afterSaveActionOrNull != null)
-            {
-                afterSaveActionOrNull.execute();
-            }
+            refreshCacheSilently();
         }
 
-        private String createFailedModificationsReport()
+        private void refreshCacheSilently()
         {
-            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();
+            DefaultResultSetConfig<String, TableModelRowWithObject<T>> config =
+                    createPagingConfig(new BasePagingLoadConfig(), filterToolbar.getFilters(),
+                            getGridDisplayTypeID());
+            config.setCacheConfig(ResultSetFetchConfig.createRecomputeAndCache(resultSetKeyOrNull));
+            final int id = TypedTableGrid.this.log("refreshing cache silently");
+            listTableRows(config, new AbstractAsyncCallback<TypedTableResultSet<T>>(
+                    TypedTableGrid.this.viewContext)
+                {
+                    @Override
+                    protected void process(TypedTableResultSet<T> result)
+                    {
+                        viewContext.logStop(id);
+                    }
+                });
         }
 
         private void clearModifications()
         {
-            finishedModifications = 0;
-            failedModifications.clear();
-            modificationsByModel.clear();
+            modificationsData.clearData();
             hideModificationsBar();
         }
 
@@ -2520,7 +2433,7 @@ public abstract class TypedTableGrid<T extends Serializable> extends LayoutConta
     {
 
         public TableModificationsToolbar(final IMessageProvider messageProvider,
-                final ITableModificationsManager<?> manager)
+                final TableModificationsManager manager)
         {
             add(new Label(messageProvider.getMessage(Dict.TABLE_MODIFICATIONS)));
 
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 64d3fd36ac0..0314d4fca84 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
@@ -20,6 +20,7 @@ import java.util.Arrays;
 import java.util.List;
 
 import com.extjs.gxt.ui.client.widget.grid.GridCellRenderer;
+import com.google.gwt.user.client.rpc.AsyncCallback;
 
 import ch.systemsx.cisd.openbis.generic.client.web.client.ICommonClientServiceAsync;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.Dict;
@@ -29,6 +30,7 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.application.model.Base
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.LinkRenderer;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.TypedTableGrid;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.EntityPropertyUpdates;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.IUpdateResult;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.SampleGridColumnIDs;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IColumnDefinition;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithProperties;
@@ -79,7 +81,7 @@ public abstract class AbstractEntityGrid<E extends IEntityInformationHolderWithP
 
     @Override
     protected void applyModifications(BaseEntityModel<TableModelRowWithObject<E>> model,
-            List<IModification> modifications)
+            List<IModification> modifications, AsyncCallback<IUpdateResult> callBack)
     {
         final EntityKind entityKind = getEntityKindOrNull();
         final TechId entityId = new TechId(model.getBaseObject().getId());
@@ -91,8 +93,7 @@ public abstract class AbstractEntityGrid<E extends IEntityInformationHolderWithP
                             SampleGridColumnIDs.PROPERTIES_PREFIX.length());
             updates.addModifiedProperty(propertyCode, modification.tryGetNewValue());
         }
-        viewContext.getService().updateProperties(updates,
-                tableModificationsManager.createApplyModificationsCallback(model, modifications));
+        viewContext.getService().updateProperties(updates, callBack);
     }
 
     @Override
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ITableModificationsManager.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ITableModificationsManager.java
deleted file mode 100644
index 28df2be7a9c..00000000000
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ITableModificationsManager.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2012 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.application.ui.grid;
-
-import java.util.List;
-
-import com.extjs.gxt.ui.client.data.ModelData;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-
-import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IDelegatedAction;
-import ch.systemsx.cisd.openbis.generic.client.web.client.dto.EntityPropertyUpdatesResult;
-
-/** Manager of table modifications */
-public interface ITableModificationsManager<M extends ModelData>
-{
-    /** @return <code>true</code> iff there are any uncommitted modifications */
-    boolean isTableDirty();
-
-    /** save all modifications made in the table to the DB */
-    void saveModifications();
-
-    /** save all modifications made in the table to the DB and call the after save action */
-    void saveModifications(IDelegatedAction afterSaveAction);
-
-    /** cancel all modifications made in the table */
-    void cancelModifications();
-
-    /** handle cell editing event */
-    void handleEditingEvent(M model, String columnID, String stringOrNull);
-
-    /** @return callback for given modifications made to specified model. */
-    AsyncCallback<EntityPropertyUpdatesResult> createApplyModificationsCallback(final M model,
-            final List<IModification> modifications);
-}
\ No newline at end of file
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/Modification.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/Modification.java
new file mode 100644
index 00000000000..a7d95f389a8
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/Modification.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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.application.ui.grid;
+
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.lang.StringEscapeUtils;
+
+public class Modification implements IModification
+{
+    private final String columnID;
+
+    private final String newValueOrNull;
+
+    public Modification(String columnID, String newValueOrNull)
+    {
+        this.columnID = columnID;
+        this.newValueOrNull =
+                newValueOrNull == null ? null : StringEscapeUtils.unescapeHtml(newValueOrNull);
+    }
+
+    @Override
+    public String getColumnID()
+    {
+        return columnID;
+    }
+
+    @Override
+    public String tryGetNewValue()
+    {
+        return newValueOrNull;
+    }
+}
\ No newline at end of file
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ModificationsData.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ModificationsData.java
new file mode 100644
index 00000000000..6f4b2048375
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ModificationsData.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2012 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.application.ui.grid;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.model.BaseEntityModel;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
+
+/**
+ * Helper class for handling {@link IModification}s.
+ * 
+ * @author Franz-Josef Elmer
+ */
+public class ModificationsData<T extends Serializable>
+{
+    /**
+     * Call back interface for applying modifications.
+     * 
+     * @author Franz-Josef Elmer
+     */
+    public static interface IModificationsHandler<T extends Serializable>
+    {
+        public void applyModifications(BaseEntityModel<TableModelRowWithObject<T>> model,
+                List<IModification> modifications);
+    }
+
+    private final Map<BaseEntityModel<TableModelRowWithObject<T>>, List<IModification>> modificationsByModel =
+            new LinkedHashMap<BaseEntityModel<TableModelRowWithObject<T>>, List<IModification>>();
+
+    private final Map<BaseEntityModel<TableModelRowWithObject<T>>, String> failedModifications =
+            new HashMap<BaseEntityModel<TableModelRowWithObject<T>>, String>();
+
+    private int finishedModifications;
+
+    private boolean saving;
+
+    public void clearData()
+    {
+        saving = false;
+        finishedModifications = 0;
+        failedModifications.clear();
+        modificationsByModel.clear();
+    }
+
+    public boolean isSaving()
+    {
+        return saving;
+    }
+
+    public void handleModifications(IModificationsHandler<T> handler)
+    {
+        saving = true;
+        finishedModifications = 0;
+        for (Entry<BaseEntityModel<TableModelRowWithObject<T>>, List<IModification>> entry : modificationsByModel
+                .entrySet())
+        {
+            BaseEntityModel<TableModelRowWithObject<T>> model = entry.getKey();
+            List<IModification> modifications = entry.getValue();
+            handler.applyModifications(model, modifications);
+        }
+    }
+
+    public void addModification(BaseEntityModel<TableModelRowWithObject<T>> model, String columnID,
+            String newValueOrNull)
+    {
+        List<IModification> modificationsForModel = modificationsByModel.get(model);
+        if (modificationsForModel == null)
+        {
+            modificationsForModel = new ArrayList<IModification>();
+            modificationsByModel.put(model, modificationsForModel);
+        }
+        modificationsForModel.add(new Modification(columnID, newValueOrNull));
+
+    }
+
+    public void handleResponseAfterModificationHasBeenApplied(
+            BaseEntityModel<TableModelRowWithObject<T>> model, String errorMessageOrNull)
+    {
+        finishedModifications++;
+        if (errorMessageOrNull != null)
+        {
+            failedModifications.put(model, errorMessageOrNull);
+        }
+    }
+
+    public boolean isApplyModificationsComplete()
+    {
+        return finishedModifications == modificationsByModel.size();
+    }
+
+    public boolean hasFailedModifications()
+    {
+        return failedModifications.isEmpty() == false;
+    }
+
+    public String createFailureTitle()
+    {
+        return (failedModifications.size() == modificationsByModel.size()) ? "Operation failed"
+                : "Operation partly failed";
+    }
+
+    public 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();
+    }
+
+}
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 57af4244853..1a0ca194c7b 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
@@ -26,7 +26,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
  * 
  * @author Piotr Buczek
  */
-public class EntityPropertyUpdatesResult implements Serializable
+public class EntityPropertyUpdatesResult implements Serializable, IUpdateResult
 {
     private static final long serialVersionUID = ServiceVersionHolder.VERSION;
 
@@ -41,6 +41,7 @@ public class EntityPropertyUpdatesResult implements Serializable
         this.errorMessage = errorMessage;
     }
 
+    @Override
     public String tryGetErrorMessage()
     {
         return errorMessage;
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/IUpdateResult.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/IUpdateResult.java
new file mode 100644
index 00000000000..a11a8a29079
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/IUpdateResult.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 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;
+
+/**
+ * Interface of an update result.
+ * 
+ * @author Franz-Josef Elmer
+ */
+public interface IUpdateResult
+{
+    /**
+     * Returns <code>null</code> if the update was successfully otherwise an error message is
+     * return.
+     */
+    public String tryGetErrorMessage();
+}
-- 
GitLab