From 0a14c06c517771a827bcf342080488b7675975c8 Mon Sep 17 00:00:00 2001
From: buczekp <buczekp>
Date: Thu, 16 Jun 2011 20:00:05 +0000
Subject: [PATCH] [LMS-2209] logical deletion of experiments (1 level, no
 cascade); deletion in detail view doesn't close the tab

SVN: 21727
---
 .../web/client/ICommonClientService.java      |  9 +--
 .../web/client/ICommonClientServiceAsync.java | 12 ++--
 .../client/web/client/application/Dict.java   |  8 ++-
 .../client/application/ui/AbstractViewer.java | 19 +++++
 ...erimentListDeletionConfirmationDialog.java | 13 ++--
 ...aterialListDeletionConfirmationDialog.java |  4 +-
 .../SampleListDeletionConfirmationDialog.java |  5 +-
 ...actDataListDeletionConfirmationDialog.java | 55 ++++++++++-----
 .../web/server/CommonClientService.java       |  8 +--
 .../openbis/generic/server/CommonServer.java  | 26 +++++--
 .../generic/server/CommonServerLogger.java    |  7 +-
 .../business/bo/AbstractBusinessObject.java   | 25 ++++---
 .../server/business/bo/ExperimentBO.java      | 43 +++++++++---
 .../server/business/bo/IExperimentBO.java     | 17 ++++-
 .../server/business/bo/SampleTable.java       |  9 ++-
 .../server/dataaccess/IDAOFactory.java        |  3 +
 .../server/dataaccess/IExperimentDAO.java     |  4 ++
 .../server/dataaccess/IInvalidationDAO.java   | 35 ++++++++++
 .../generic/server/dataaccess/ISampleDAO.java |  6 +-
 .../server/dataaccess/db/DAOFactory.java      | 11 ++-
 .../server/dataaccess/db/ExperimentDAO.java   | 16 +++++
 .../server/dataaccess/db/InvalidationDAO.java | 70 +++++++++++++++++++
 .../server/dataaccess/db/SampleDAO.java       | 10 +--
 .../openbis/generic/shared/ICommonServer.java |  9 +--
 .../experiment/GenericExperimentViewer.java   |  2 +-
 .../sample/GenericSampleViewer.java           |  2 +-
 .../cisd/openbis/public/common-dictionary.js  |  6 +-
 .../server/business/bo/ExperimentBOTest.java  |  4 +-
 .../shared/ICommonServer.java.expected        | 12 ++--
 29 files changed, 351 insertions(+), 99 deletions(-)
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IInvalidationDAO.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/InvalidationDAO.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 ed9a85ce0dc..0bd9fe737cf 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
@@ -659,13 +659,14 @@ public interface ICommonClientService extends IClientService
     public void deleteSample(TechId sampleId, String reason, DeletionType deletionType)
             throws UserFailureException;
 
-    /** Deletes the specified experiments. */
+    /** Deletes/Invalidates the specified experiments. */
     public void deleteExperiments(
             DisplayedOrSelectedIdHolderCriteria<TableModelRowWithObject<Experiment>> criteria,
-            String reason) throws UserFailureException;
+            String reason, DeletionType deletionType) throws UserFailureException;
 
-    /** Deletes the specified experiment. */
-    public void deleteExperiment(TechId experimentId, String reason) throws UserFailureException;
+    /** Deletes/Invalidates the specified experiment. */
+    public void deleteExperiment(TechId experimentId, String reason, DeletionType deletionType)
+            throws UserFailureException;
 
     /** Deletes the specified projects. */
     public void deleteProjects(List<TechId> projectIds, String reason) 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 72fb1f7f88d..660ef731da0 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
@@ -564,13 +564,17 @@ public interface ICommonClientServiceAsync extends IClientServiceAsync
     public void deleteSample(TechId sampleIs, String reason, DeletionType type,
             AsyncCallback<Void> asyncCallback);
 
-    /** @see ICommonClientService#deleteExperiments(DisplayedOrSelectedIdHolderCriteria, String) */
+    /**
+     * @param deletionType
+     * @see ICommonClientService#deleteExperiments(DisplayedOrSelectedIdHolderCriteria, String,
+     *      DeletionType)
+     */
     public void deleteExperiments(
             DisplayedOrSelectedIdHolderCriteria<TableModelRowWithObject<Experiment>> criteria,
-            String reason, AsyncCallback<Void> asyncCallback);
+            String reason, DeletionType deletionType, AsyncCallback<Void> asyncCallback);
 
-    /** @see ICommonClientService#deleteExperiment(TechId, String) */
-    public void deleteExperiment(TechId experimentId, String reason,
+    /** @see ICommonClientService#deleteExperiment(TechId, String, DeletionType) */
+    public void deleteExperiment(TechId experimentId, String reason, DeletionType deletionType,
             AsyncCallback<Void> asyncCallback);
 
     /** @see ICommonClientService#deleteProjects(List, String) */
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/Dict.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/Dict.java
index 840b00f5415..17ec7678b62 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/Dict.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/Dict.java
@@ -492,8 +492,12 @@ public abstract class Dict
 
     public static final String DELETE_CONFIRMATION_MESSAGE = "delete_confirmation_message";
 
-    public static final String DELETE_CONFIRMATION_MESSAGE_WITH_REASON =
-            "delete_confirmation_message_with_reason";
+    public static final String DELETE_CONFIRMATION_MESSAGE_WITH_REASON_TEMPLATE =
+            "delete_confirmation_message_with_reason_template";
+
+    public static final String DELETING_PERMANENTLY = "deleting_permanently";
+
+    public static final String INVALIDATING = "invalidating";
 
     public static final String DELETE_PROGRESS_MESSAGE = "delete_progress_message";
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AbstractViewer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AbstractViewer.java
index 19120394b75..31296564884 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AbstractViewer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AbstractViewer.java
@@ -279,6 +279,11 @@ public abstract class AbstractViewer<D extends IEntityInformationHolder> extends
         return new CloseViewerCallback(viewContext);
     }
 
+    protected final AbstractAsyncCallback<Void> createInvalidationCallback()
+    {
+        return new RefreshViewerCallback(viewContext);
+    }
+
     private final class CloseViewerCallback extends AbstractAsyncCallback<Void>
     {
         public CloseViewerCallback(IViewContext<?> viewContext)
@@ -293,6 +298,20 @@ public abstract class AbstractViewer<D extends IEntityInformationHolder> extends
         }
     }
 
+    private final class RefreshViewerCallback extends AbstractAsyncCallback<Void>
+    {
+        public RefreshViewerCallback(IViewContext<?> viewContext)
+        {
+            super(viewContext);
+        }
+
+        @Override
+        protected void process(Void result)
+        {
+            reloadAllData();
+        }
+    }
+
     public void notify(List<IModule> modules)
     {
         moduleSectionManager.initialize(modules);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/experiment/ExperimentListDeletionConfirmationDialog.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/experiment/ExperimentListDeletionConfirmationDialog.java
index 79eea1e8958..0b252103a6e 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/experiment/ExperimentListDeletionConfirmationDialog.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/experiment/ExperimentListDeletionConfirmationDialog.java
@@ -30,6 +30,7 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.widget.
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.WidgetUtils;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.DisplayedOrSelectedIdHolderCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DeletionType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
@@ -57,9 +58,11 @@ public final class ExperimentListDeletionConfirmationDialog extends
 
     public ExperimentListDeletionConfirmationDialog(
             IViewContext<ICommonClientServiceAsync> viewContext,
-            AbstractAsyncCallback<Void> callback, Experiment experiment)
+            AbstractAsyncCallback<Void> deletionCallback,
+            AbstractAsyncCallback<Void> invalidationCallback, Experiment experiment)
     {
-        super(viewContext, Collections.singletonList(experiment), callback, false);
+        super(viewContext, Collections.singletonList(experiment), deletionCallback,
+                invalidationCallback, false);
         this.viewContext = viewContext;
         this.singleDataOrNull = experiment;
         this.selectedAndDisplayedItemsOrNull = null;
@@ -68,16 +71,18 @@ public final class ExperimentListDeletionConfirmationDialog extends
     @Override
     protected void executeDeletion(AsyncCallback<Void> deletionCallback)
     {
+        DeletionType deletionType =
+                isPermanentDeletion() ? DeletionType.PERMANENT : DeletionType.INVALIDATION;
         if (selectedAndDisplayedItemsOrNull != null)
         {
             final DisplayedOrSelectedIdHolderCriteria<TableModelRowWithObject<Experiment>> uploadCriteria =
                     selectedAndDisplayedItemsOrNull.createCriteria(isOnlySelected());
             viewContext.getCommonService().deleteExperiments(uploadCriteria, reason.getValue(),
-                    deletionCallback);
+                    deletionType, deletionCallback);
         } else
         {
             viewContext.getCommonService().deleteExperiment(TechId.create(singleDataOrNull),
-                    reason.getValue(), deletionCallback);
+                    reason.getValue(), deletionType, deletionCallback);
         }
     }
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/material/MaterialListDeletionConfirmationDialog.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/material/MaterialListDeletionConfirmationDialog.java
index 6247e4ee490..85033c89a4b 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/material/MaterialListDeletionConfirmationDialog.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/material/MaterialListDeletionConfirmationDialog.java
@@ -42,10 +42,10 @@ public final class MaterialListDeletionConfirmationDialog extends
 
     public MaterialListDeletionConfirmationDialog(
             IViewContext<ICommonClientServiceAsync> viewContext, List<Material> data,
-            AbstractAsyncCallback<Void> callback,
+            AbstractAsyncCallback<Void> deletionCallback,
             DisplayedAndSelectedMaterials selectedAndDisplayedItems)
     {
-        super(viewContext, data, callback, true);
+        super(viewContext, data, deletionCallback, deletionCallback, true, false);
         this.viewContext = viewContext;
         this.selectedAndDisplayedItems = selectedAndDisplayedItems;
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/sample/SampleListDeletionConfirmationDialog.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/sample/SampleListDeletionConfirmationDialog.java
index 6cf83bc5a7e..cfb0035715a 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/sample/SampleListDeletionConfirmationDialog.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/sample/SampleListDeletionConfirmationDialog.java
@@ -58,9 +58,10 @@ public final class SampleListDeletionConfirmationDialog<T extends IIdHolder> ext
 
     public SampleListDeletionConfirmationDialog(
             IViewContext<ICommonClientServiceAsync> viewContext, List<T> data,
-            AbstractAsyncCallback<Void> callback, T sample)
+            AbstractAsyncCallback<Void> deletionCallback,
+            AbstractAsyncCallback<Void> invalidationCallback, T sample)
     {
-        super(viewContext, data, callback, false);
+        super(viewContext, data, deletionCallback, invalidationCallback, false);
         this.viewContext = viewContext;
         this.singleDataOrNull = sample;
         this.selectedAndDisplayedItemsOrNull = null;
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/widget/AbstractDataListDeletionConfirmationDialog.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/widget/AbstractDataListDeletionConfirmationDialog.java
index a81e00fa1f4..1a60df0fa35 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/widget/AbstractDataListDeletionConfirmationDialog.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/widget/AbstractDataListDeletionConfirmationDialog.java
@@ -50,11 +50,9 @@ public abstract class AbstractDataListDeletionConfirmationDialog<T> extends
 
     private static final String SELECTED = " selected ";
 
-    private static final String TEMPORARILY = "temporarily";
+    private final AbstractAsyncCallback<Void> deletionCallback;
 
-    private static final String PERMANENTLY_EMPHASIZED = "<b>permanently</b>";
-
-    private final AbstractAsyncCallback<Void> callback;
+    private final AbstractAsyncCallback<Void> invalidationCallback;
 
     private final boolean withRadio;
 
@@ -67,27 +65,37 @@ public abstract class AbstractDataListDeletionConfirmationDialog<T> extends
     protected ReasonField reason;
 
     public AbstractDataListDeletionConfirmationDialog(IMessageProvider messageProvider,
-            List<T> data, AbstractAsyncCallback<Void> callback, boolean withRadio,
+            List<T> data, AbstractAsyncCallback<Void> deletionCallback,
+            AbstractAsyncCallback<Void> invalidationCallback, boolean withRadio,
             boolean withInvalidateOption)
     {
         super(messageProvider, data, messageProvider.getMessage(Dict.DELETE_CONFIRMATION_TITLE));
-        this.callback = callback;
+        this.deletionCallback = deletionCallback;
+        this.invalidationCallback = invalidationCallback;
         this.withRadio = withRadio;
         this.withInvalidateOption = withInvalidateOption;
     }
 
-    // maybe with radio & temporarily
+    // maybe with radio & with invalidation as an option & possibly different callbacks
+    public AbstractDataListDeletionConfirmationDialog(IMessageProvider messageProvider,
+            List<T> data, AbstractAsyncCallback<Void> deletionCallback,
+            AbstractAsyncCallback<Void> invalidationCallback, boolean withRadio)
+    {
+        this(messageProvider, data, deletionCallback, invalidationCallback, withRadio, true);
+    }
+
+    // maybe with radio & with invalidation as an option & with one callback
     public AbstractDataListDeletionConfirmationDialog(IMessageProvider messageProvider,
-            List<T> data, AbstractAsyncCallback<Void> callback, boolean withRadio)
+            List<T> data, AbstractAsyncCallback<Void> deletionCallback, boolean withRadio)
     {
-        this(messageProvider, data, callback, withRadio, true);
+        this(messageProvider, data, deletionCallback, deletionCallback, withRadio, true);
     }
 
     // without radio & permanently
     public AbstractDataListDeletionConfirmationDialog(IMessageProvider messageProvider,
-            List<T> data, AbstractAsyncCallback<Void> callback)
+            List<T> data, AbstractAsyncCallback<Void> deletionCallback)
     {
-        this(messageProvider, data, callback, false, false);
+        this(messageProvider, data, deletionCallback, deletionCallback, false, false);
     }
 
     @Override
@@ -122,13 +130,16 @@ public abstract class AbstractDataListDeletionConfirmationDialog<T> extends
             deletedObjects =
                     (isOnlySelected() ? data.size() + SELECTED : ALL_EMPHASIZED) + getEntityName();
         }
-        return messageProvider.getMessage(Dict.DELETE_CONFIRMATION_MESSAGE_WITH_REASON,
-                isPermanentDeletion() ? PERMANENTLY_EMPHASIZED : TEMPORARILY, deletedObjects);
+        final String operationName =
+                messageProvider.getMessage(isPermanentDeletion() ? Dict.DELETING_PERMANENTLY
+                        : Dict.INVALIDATING);
+        return messageProvider.getMessage(Dict.DELETE_CONFIRMATION_MESSAGE_WITH_REASON_TEMPLATE,
+                operationName, deletedObjects);
     }
 
     protected abstract String getEntityName();
 
-    protected abstract void executeDeletion(AsyncCallback<Void> deletionCallback);
+    protected abstract void executeDeletion(AsyncCallback<Void> callback);
 
     @Override
     protected final void executeConfirmedAction()
@@ -170,13 +181,19 @@ public abstract class AbstractDataListDeletionConfirmationDialog<T> extends
     }
 
     /**
-     * Returns deletion callback and shows a progress bar that will be hidden when the callback is
-     * finished.
+     * Returns deletion/invalidation callback and shows a progress bar that will be hidden when the
+     * callback is finished.
      */
     private AsyncCallback<Void> getCallbackWithProgressBar()
     {
-        return AsyncCallbackWithProgressBar.decorate(callback, messageProvider.getMessage(
-                isPermanentDeletion() ? Dict.DELETE_PROGRESS_MESSAGE
-                        : Dict.INVALIDATE_PROGRESS_MESSAGE, getEntityName()));
+        if (isPermanentDeletion())
+        {
+            return AsyncCallbackWithProgressBar.decorate(deletionCallback,
+                    messageProvider.getMessage(Dict.DELETE_PROGRESS_MESSAGE, getEntityName()));
+        } else
+        {
+            return AsyncCallbackWithProgressBar.decorate(invalidationCallback,
+                    messageProvider.getMessage(Dict.INVALIDATE_PROGRESS_MESSAGE, getEntityName()));
+        }
     }
 }
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 e392b983a7b..ac687e2548a 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
@@ -1620,14 +1620,14 @@ public final class CommonClientService extends AbstractClientService implements
         }
     }
 
-    public void deleteExperiment(TechId experimentId, String reason)
+    public void deleteExperiment(TechId experimentId, String reason, DeletionType deletionType)
             throws ch.systemsx.cisd.openbis.generic.client.web.client.exception.UserFailureException
     {
         try
         {
             final String sessionToken = getSessionToken();
             commonServer.deleteExperiments(sessionToken, Collections.singletonList(experimentId),
-                    reason);
+                    reason, deletionType);
         } catch (final UserFailureException e)
         {
             throw UserFailureExceptionTranslator.translate(e);
@@ -1636,14 +1636,14 @@ public final class CommonClientService extends AbstractClientService implements
 
     public void deleteExperiments(
             DisplayedOrSelectedIdHolderCriteria<TableModelRowWithObject<Experiment>> criteria,
-            String reason)
+            String reason, DeletionType deletionType)
             throws ch.systemsx.cisd.openbis.generic.client.web.client.exception.UserFailureException
     {
         try
         {
             final String sessionToken = getSessionToken();
             List<TechId> experimentIds = extractTechIds(criteria);
-            commonServer.deleteExperiments(sessionToken, experimentIds, reason);
+            commonServer.deleteExperiments(sessionToken, experimentIds, reason, deletionType);
         } catch (final UserFailureException e)
         {
             throw UserFailureExceptionTranslator.translate(e);
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 b58ea493799..a2ce216d7f8 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
@@ -182,6 +182,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.FileFormatTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.GridCustomFilterPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.IEntityInformationHolderDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.IEntityInformationWithPropertiesHolder;
+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.NewRoleAssignment;
@@ -602,8 +603,7 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt
             Collection<ExperimentIdentifier> experimentIdentifiers)
     {
         Session session = getSession(sessionToken);
-        IExperimentTable experimentTable =
-                businessObjectFactory.createExperimentTable(session);
+        IExperimentTable experimentTable = businessObjectFactory.createExperimentTable(session);
 
         experimentTable.load(experimentIdentifiers);
 
@@ -1189,15 +1189,22 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt
         }
     }
 
-    public void deleteExperiments(String sessionToken, List<TechId> experimentIds, String reason)
+    public void deleteExperiments(String sessionToken, List<TechId> experimentIds, String reason,
+            DeletionType deletionType)
     {
         Session session = getSession(sessionToken);
         try
         {
             IExperimentBO experimentBO = businessObjectFactory.createExperimentBO(session);
-            for (TechId id : experimentIds)
+            switch (deletionType)
             {
-                experimentBO.deleteByTechId(id, reason);
+                case PERMANENT:
+                    experimentBO.deleteByTechIds(experimentIds, reason);
+                    break;
+                case INVALIDATION:
+                    InvalidationPE invalidation = createInvalidation(reason, session);
+                    experimentBO.invalidateByTechIds(experimentIds, invalidation);
+                    break;
             }
         } catch (final DataAccessException ex)
         {
@@ -1205,6 +1212,15 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt
         }
     }
 
+    private InvalidationPE createInvalidation(String reason, Session session)
+    {
+        InvalidationPE invalidation = new InvalidationPE();
+        invalidation.setReason(reason);
+        invalidation.setRegistrator(session.tryGetPerson());
+        getDAOFactory().getInvalidationDAO().create(invalidation);
+        return invalidation;
+    }
+
     public void deleteVocabularies(String sessionToken, List<TechId> vocabularyIds, String reason)
     {
         Session session = getSession(sessionToken);
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 bc09b0c62b1..f8927be18e1 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
@@ -589,10 +589,11 @@ final class CommonServerLogger extends AbstractServerLogger implements ICommonSe
                 abbreviate(sampleIds), reason);
     }
 
-    public void deleteExperiments(String sessionToken, List<TechId> experimentIds, String reason)
+    public void deleteExperiments(String sessionToken, List<TechId> experimentIds, String reason,
+            DeletionType deletionType)
     {
-        logTracking(sessionToken, "delete_experiments", "IDS(%s) REASON(%s)",
-                abbreviate(experimentIds), reason);
+        logTracking(sessionToken, "delete_experiments", "TYPE(%s) IDS(%s) REASON(%s)",
+                deletionType, abbreviate(experimentIds), reason);
     }
 
     public void deleteVocabularies(String sessionToken, List<TechId> vocabularyIds, String reason)
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java
index bf745ea80b2..684cee07444 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java
@@ -39,6 +39,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.IFileFormatTypeDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IGridCustomColumnDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IGridCustomFilterDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IHibernateSearchDAO;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IInvalidationDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.ILocatorTypeDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IMaterialDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IPersonDAO;
@@ -86,21 +87,24 @@ abstract class AbstractBusinessObject implements IDAOFactory
         this(daoFactory, session, (IEntityPropertiesConverter) null);
     }
 
-    AbstractBusinessObject(final IDAOFactory daoFactory, final Session session, EntityKind entityKindOrNull)
+    AbstractBusinessObject(final IDAOFactory daoFactory, final Session session,
+            EntityKind entityKindOrNull)
     {
-        this(daoFactory, session, entityKindOrNull == null ? null : new EntityPropertiesConverter(entityKindOrNull, daoFactory));
+        this(daoFactory, session, entityKindOrNull == null ? null : new EntityPropertiesConverter(
+                entityKindOrNull, daoFactory));
     }
-    
-    AbstractBusinessObject(final IDAOFactory daoFactory, final Session session, IEntityPropertiesConverter converter)
+
+    AbstractBusinessObject(final IDAOFactory daoFactory, final Session session,
+            IEntityPropertiesConverter converter)
     {
         assert daoFactory != null : "Given DAO factory can not be null.";
         assert session != null : "Given session can not be null.";
-        
+
         this.daoFactory = daoFactory;
         this.session = session;
         entityPropertiesConverter = converter;
     }
-    
+
     public SessionFactory getSessionFactory()
     {
         return daoFactory.getSessionFactory();
@@ -191,8 +195,8 @@ abstract class AbstractBusinessObject implements IDAOFactory
                 propertiesToUpdate.add(property.getPropertyType().getCode());
             }
         }
-        return entityPropertiesConverter.updateProperties(existingProperties, type,
-                properties, registrator, propertiesToUpdate);
+        return entityPropertiesConverter.updateProperties(existingProperties, type, properties,
+                registrator, propertiesToUpdate);
     }
 
     //
@@ -329,6 +333,11 @@ abstract class AbstractBusinessObject implements IDAOFactory
         return daoFactory.getEventDAO();
     }
 
+    public final IInvalidationDAO getInvalidationDAO()
+    {
+        return daoFactory.getInvalidationDAO();
+    }
+
     public void setBatchUpdateMode(boolean batchMode)
     {
         daoFactory.setBatchUpdateMode(batchMode);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBO.java
index 4f4435633af..62705bebc7f 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBO.java
@@ -47,6 +47,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPropertyPE;
 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.InvalidationPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
@@ -207,18 +208,38 @@ public final class ExperimentBO extends AbstractBusinessObject implements IExper
                 + "') not found in experiment '" + experiment.getIdentifier() + "'.");
     }
 
-    public void deleteByTechId(TechId experimentId, String reason) throws UserFailureException
+    public void invalidateByTechIds(List<TechId> experimentIds, InvalidationPE invalidation)
+            throws UserFailureException
     {
-        loadDataByTechId(experimentId);
         try
         {
-            deleteZombieDatasetPlaceholders();
-            getExperimentDAO().delete(experiment);
-            getEventDAO().persist(createDeletionEvent(experiment, session.tryGetPerson(), reason));
+            getSessionFactory().getCurrentSession().flush();
+            getSessionFactory().getCurrentSession().clear();
+
+            getExperimentDAO().invalidate(experimentIds, invalidation);
         } catch (final DataAccessException ex)
         {
-            throwException(ex, String.format("Experiment '%s'", experiment.getCode()),
-                    EntityKind.EXPERIMENT);
+            throwException(ex, "Experiment", EntityKind.EXPERIMENT);
+        }
+    }
+
+    public void deleteByTechIds(List<TechId> experimentIds, String reason)
+            throws UserFailureException
+    {
+        for (TechId experimentId : experimentIds)
+        {
+            loadDataByTechId(experimentId);
+            try
+            {
+                deleteZombieDatasetPlaceholders();
+                getExperimentDAO().delete(experiment);
+                getEventDAO().persist(
+                        createDeletionEvent(experiment, session.tryGetPerson(), reason));
+            } catch (final DataAccessException ex)
+            {
+                throwException(ex, String.format("Experiment '%s'", experiment.getCode()),
+                        EntityKind.EXPERIMENT);
+            }
         }
     }
 
@@ -374,8 +395,8 @@ public final class ExperimentBO extends AbstractBusinessObject implements IExper
             final IEntityProperty[] experimentProperties, PersonPE registrator)
     {
         final List<ExperimentPropertyPE> properties =
-                entityPropertiesConverter.convertProperties(experimentProperties, experimentTypeCode,
-                        registrator);
+                entityPropertiesConverter.convertProperties(experimentProperties,
+                        experimentTypeCode, registrator);
         for (final ExperimentPropertyPE experimentProperty : properties)
         {
             experiment.addProperty(experimentProperty);
@@ -573,8 +594,8 @@ public final class ExperimentBO extends AbstractBusinessObject implements IExper
         final Set<ExperimentPropertyPE> existingProperties = experiment.getProperties();
         final ExperimentTypePE type = experiment.getExperimentType();
         final PersonPE registrator = findRegistrator();
-        experiment.setProperties(entityPropertiesConverter.updateManagedProperty(existingProperties,
-                type, managedProperty, registrator));
+        experiment.setProperties(entityPropertiesConverter.updateManagedProperty(
+                existingProperties, type, managedProperty, registrator));
 
         dataChanged = true;
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IExperimentBO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IExperimentBO.java
index 55d919e331c..8dbc2fbd3ab 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IExperimentBO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IExperimentBO.java
@@ -16,6 +16,8 @@
 
 package ch.systemsx.cisd.openbis.generic.server.business.bo;
 
+import java.util.List;
+
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
@@ -23,6 +25,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.api.IManagedProperty;
 import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentUpdatesDTO;
+import ch.systemsx.cisd.openbis.generic.shared.dto.InvalidationPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
 
 /**
@@ -75,10 +78,18 @@ public interface IExperimentBO extends IEntityBusinessObject
     /**
      * Deletes experiment for specified reason.
      * 
-     * @param experimentId experiment technical identifier
-     * @throws UserFailureException if experiment with given technical identifier is not found.
+     * @param experimentIds experiment technical identifiers
+     * @throws UserFailureException if one of the experiments can not be deleted.
+     */
+    void deleteByTechIds(List<TechId> experimentIds, String reason);
+
+    /**
+     * Invalidates experiment with given invalidation.
+     * 
+     * @param experimentIds experiment technical identifiers
+     * @throws UserFailureException if one of the experiments can not be invalidated.
      */
-    void deleteByTechId(TechId experimentId, String reason);
+    void invalidateByTechIds(List<TechId> experimentIds, InvalidationPE invalidation);
 
     /**
      * Changes the value of a managed property.
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/SampleTable.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/SampleTable.java
index b78e338a97c..0a444de23b3 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/SampleTable.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/SampleTable.java
@@ -34,6 +34,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleBatchUpdateDetail
 import ch.systemsx.cisd.openbis.generic.shared.dto.EntityTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.EntityTypePropertyTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.InvalidationPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ListSamplesByPropertyCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectPE;
@@ -347,7 +348,13 @@ public final class SampleTable extends AbstractSampleBusinessObject implements I
         {
             getSessionFactory().getCurrentSession().flush();
             getSessionFactory().getCurrentSession().clear();
-            getSampleDAO().invalidate(sampleIds, session.tryGetPerson(), reason);
+
+            InvalidationPE invalidation = new InvalidationPE();
+            invalidation.setReason(reason);
+            invalidation.setRegistrator(session.tryGetPerson());
+            getInvalidationDAO().create(invalidation);
+
+            getSampleDAO().invalidate(sampleIds, invalidation);
         } catch (final DataAccessException ex)
         {
             throwException(ex, "Sample", EntityKind.SAMPLE);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDAOFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDAOFactory.java
index 629dd5bc04c..a4d88470eba 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDAOFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDAOFactory.java
@@ -98,4 +98,7 @@ public interface IDAOFactory extends IAuthorizationDAOFactory
     /** Returns an implementation of {@link IAuthorizationGroupDAO}. */
     public IAuthorizationGroupDAO getAuthorizationGroupDAO();
 
+    /** Returns an implementation of {@link IInvalidationDAO}. */
+    public IInvalidationDAO getInvalidationDAO();
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IExperimentDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IExperimentDAO.java
index f691f6341f6..dea45408e3b 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IExperimentDAO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IExperimentDAO.java
@@ -22,8 +22,10 @@ import java.util.Set;
 
 import org.springframework.dao.DataAccessException;
 
+import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 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.InvalidationPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SpacePE;
 
@@ -105,4 +107,6 @@ public interface IExperimentDAO extends IGenericDAO<ExperimentPE>
      */
     public void createOrUpdateExperiments(List<ExperimentPE> experiments);
 
+    public void invalidate(List<TechId> experimentIds, InvalidationPE invalidation);
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IInvalidationDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IInvalidationDAO.java
new file mode 100644
index 00000000000..1eee382d8db
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IInvalidationDAO.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2008 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.dataaccess;
+
+import org.springframework.dao.DataAccessException;
+
+import ch.systemsx.cisd.openbis.generic.shared.dto.InvalidationPE;
+
+/**
+ * An interface that contains all data access operations on {@link InvalidationPE}s.
+ * 
+ * @author Piotr Buczek
+ */
+public interface IInvalidationDAO extends IGenericDAO<InvalidationPE>
+{
+    /**
+     * Inserts given {@link InvalidationPE} into the database.
+     */
+    void create(final InvalidationPE invalidation) throws DataAccessException;
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/ISampleDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/ISampleDAO.java
index ca4d43c8a9e..2df1d1e0c8d 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/ISampleDAO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/ISampleDAO.java
@@ -24,6 +24,7 @@ import org.springframework.dao.DataAccessException;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DatabaseInstancePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.InvalidationPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SpacePE;
@@ -123,8 +124,7 @@ public interface ISampleDAO extends IGenericDAO<SamplePE>
             throws DataAccessException;
 
     /**
-     * Invalidate samples with given ids by specified registrator with specified reason.
+     * Invalidate samples with given ids using specified invalidation.
      */
-    void invalidate(List<TechId> sampleIds, PersonPE registrator, String reason)
-            throws DataAccessException;
+    void invalidate(List<TechId> sampleIds, InvalidationPE invalidation) throws DataAccessException;
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DAOFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DAOFactory.java
index 31a8b8d3b1a..41de9abe438 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DAOFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DAOFactory.java
@@ -34,6 +34,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.IEntityTypeDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IEventDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IFileFormatTypeDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IHibernateSearchDAO;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IInvalidationDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.ILocatorTypeDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IMaterialDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IPropertyTypeDAO;
@@ -99,7 +100,9 @@ public final class DAOFactory extends AuthorizationDAOFactory implements IDAOFac
 
     private final IAuthorizationGroupDAO authorizationGroupDAO;
 
-    private IScriptDAO scriptDAO;
+    private final IScriptDAO scriptDAO;
+
+    private final IInvalidationDAO invalidationDAO;
 
     public DAOFactory(final DatabaseConfigurationContext context,
             final SessionFactory sessionFactory, HibernateSearchContext hibernateSearchContext,
@@ -127,6 +130,7 @@ public final class DAOFactory extends AuthorizationDAOFactory implements IDAOFac
         eventDAO = new EventDAO(sessionFactory, databaseInstance);
         authorizationGroupDAO = new AuthorizationGroupDAO(sessionFactory, databaseInstance);
         scriptDAO = new ScriptDAO(sessionFactory, databaseInstance);
+        invalidationDAO = new InvalidationDAO(sessionFactory, databaseInstance);
         final EntityKind[] entityKinds = EntityKind.values();
         for (final EntityKind entityKind : entityKinds)
         {
@@ -232,6 +236,11 @@ public final class DAOFactory extends AuthorizationDAOFactory implements IDAOFac
         return scriptDAO;
     }
 
+    public IInvalidationDAO getInvalidationDAO()
+    {
+        return invalidationDAO;
+    }
+
     public IDynamicPropertyEvaluationScheduler getDynamicPropertyEvaluationScheduler()
     {
         return dynamicPropertyEvaluationScheduler;
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ExperimentDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ExperimentDAO.java
index 2ab18131f7c..f0aea06842a 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ExperimentDAO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ExperimentDAO.java
@@ -36,10 +36,12 @@ import ch.systemsx.cisd.common.utilities.MethodUtils;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IExperimentDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.PersistencyResources;
 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.dto.DatabaseInstancePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPropertyPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentTypePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.InvalidationPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SpacePE;
 
@@ -298,4 +300,18 @@ public class ExperimentDAO extends AbstractGenericEntityWithPropertiesDAO<Experi
         }
     }
 
+    public void invalidate(final List<TechId> experimentIds, final InvalidationPE invalidation)
+            throws DataAccessException
+    {
+        // TODO 2011-06-16, Piotr Buczek: could be done faster with bulk update
+        for (TechId experimentId : experimentIds)
+        {
+            ExperimentPE experiment = loadByTechId(experimentId);
+            experiment.setInvalidation(invalidation);
+            getHibernateTemplate().update(experiment);
+        }
+
+        getHibernateTemplate().flush();
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/InvalidationDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/InvalidationDAO.java
new file mode 100644
index 00000000000..c227d12a442
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/InvalidationDAO.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2007 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.dataaccess.db;
+
+import org.apache.log4j.Logger;
+import org.hibernate.SessionFactory;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.support.JdbcAccessor;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IInvalidationDAO;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DatabaseInstancePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.InvalidationPE;
+
+/**
+ * <i>Data Access Object</i> implementation for {@link IInvalidationDAO}.
+ * 
+ * @author Christian Ribeaud
+ */
+final class InvalidationDAO extends AbstractGenericEntityDAO<InvalidationPE> implements
+        IInvalidationDAO
+{
+
+    /**
+     * This logger does not output any SQL statement. If you want to do so, you had better set an
+     * appropriate debugging level for class {@link JdbcAccessor}. </p>
+     */
+    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION,
+            InvalidationDAO.class);
+
+    InvalidationDAO(final SessionFactory sessionFactory, final DatabaseInstancePE databaseInstance)
+    {
+        super(sessionFactory, databaseInstance, InvalidationPE.class);
+    }
+
+    //
+    // IInvalidationDAO
+    //
+
+    public final void create(final InvalidationPE invalidation) throws DataAccessException
+    {
+        assert invalidation != null : "Unspecified invalidation";
+        validatePE(invalidation);
+
+        final HibernateTemplate template = getHibernateTemplate();
+        template.save(invalidation);
+        template.flush();
+        if (operationLog.isInfoEnabled())
+        {
+            operationLog.info(String.format("ADD: invalidation '%s'.", invalidation));
+        }
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/SampleDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/SampleDAO.java
index 9dcd63bf55e..35f280dcb7b 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/SampleDAO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/SampleDAO.java
@@ -440,15 +440,9 @@ public class SampleDAO extends AbstractGenericEntityWithPropertiesDAO<SamplePE>
         scheduleRemoveFromFullTextIndex(ids);
     }
 
-    public void invalidate(final List<TechId> sampleIds, final PersonPE registrator,
-            final String reason) throws DataAccessException
+    public void invalidate(final List<TechId> sampleIds, final InvalidationPE invalidation)
+            throws DataAccessException
     {
-        // TODO 2011-06-16, Piotr Buczek: move when cascade invalidation is implemented
-        InvalidationPE invalidation = new InvalidationPE();
-        invalidation.setReason(reason);
-        invalidation.setRegistrator(registrator);
-        getHibernateTemplate().save(invalidation);
-
         // TODO 2011-06-16, Piotr Buczek: could be done faster with bulk update
         for (TechId sampleId : sampleIds)
         {
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 520b457b666..22196590c5c 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
@@ -343,7 +343,8 @@ public interface ICommonServer extends IServer
      */
     @Transactional(readOnly = true)
     @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
-    public List<Experiment> listExperiments(final String sessionToken,
+    public List<Experiment> listExperiments(
+            final String sessionToken,
             @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class) Collection<ExperimentIdentifier> experimentIdentifiers);
 
     /**
@@ -749,7 +750,7 @@ public interface ICommonServer extends IServer
             String reason);
 
     /**
-     * Deletes specified samples.
+     * Deletes/Invalidates specified samples.
      */
     @Transactional
     @RolesAllowed(RoleWithHierarchy.SPACE_POWER_USER)
@@ -760,7 +761,7 @@ public interface ICommonServer extends IServer
             String reason, DeletionType type);
 
     /**
-     * Deletes specified experiments.
+     * Deletes/Invalidates specified experiments.
      */
     @Transactional
     @RolesAllowed(RoleWithHierarchy.SPACE_POWER_USER)
@@ -768,7 +769,7 @@ public interface ICommonServer extends IServer
     public void deleteExperiments(
             String sessionToken,
             @AuthorizationGuard(guardClass = ExperimentTechIdPredicate.class) List<TechId> experimentIds,
-            String reason);
+            String reason, DeletionType deletionType);
 
     /**
      * Deletes specified attachments (all versions with given file names) of specified experiment.
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/experiment/GenericExperimentViewer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/experiment/GenericExperimentViewer.java
index 41f4afde275..44c0c3511d1 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/experiment/GenericExperimentViewer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/experiment/GenericExperimentViewer.java
@@ -129,7 +129,7 @@ public class GenericExperimentViewer extends AbstractViewerWithVerticalSplit<Exp
                 {
                     new ExperimentListDeletionConfirmationDialog(
                             viewContext.getCommonViewContext(), createDeletionCallback(),
-                            getOriginalData()).show();
+                            createInvalidationCallback(), getOriginalData()).show();
                 }
             }));
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/sample/GenericSampleViewer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/sample/GenericSampleViewer.java
index 37a3b196ca1..9dc585ff479 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/sample/GenericSampleViewer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/sample/GenericSampleViewer.java
@@ -174,7 +174,7 @@ abstract public class GenericSampleViewer extends AbstractViewerWithVerticalSpli
                 {
                     new SampleListDeletionConfirmationDialog(viewContext.getCommonViewContext(),
                             getOriginalDataAsSingleton(), createDeletionCallback(),
-                            getOriginalData()).show();
+                            createInvalidationCallback(), getOriginalData()).show();
                 }
             }));
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/common-dictionary.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/common-dictionary.js
index bd3afded33a..1e5a23c8b14 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/public/common-dictionary.js
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/common-dictionary.js
@@ -63,12 +63,14 @@ var common = {
   
   invalidate: "Invalidate",
   invalidation: "Invalidation",
-  invalidation_template: "Invalidated by: {0}<br>date: {1}<br>reason: {2}",
+  invalidation_template: "<i>by:</i> {0}<br><i>date:</i> {1}<br><i>reason:</i> {2}",
   permanent: "Permanent",
   reason: "Reason",
   delete_confirmation_title: "Confirm Deletion",
   delete_confirmation_message: "Are you sure you want to delete [{0}]?",
-  delete_confirmation_message_with_reason: "You are {0} deleting {1}(s).<br><br>Please enter a reason or cancel deletion.",
+  delete_confirmation_message_with_reason_template: "You are {0} {1}(s).<br><br>Please enter a reason or cancel the operation.",
+  deleting_permanently: "<b>permanently</b> deleting",
+  invalidating: "invalidating",
   delete_progress_message: "Deleting {0}(s)...",
   delete_progress_message: "Invalidating {0}(s)...",
 	 
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBOTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBOTest.java
index dc344892b59..936b2da313d 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBOTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBOTest.java
@@ -102,7 +102,7 @@ public final class ExperimentBOTest extends AbstractBOTest
             {
                 {
                     one(propertiesConverter).updateProperties(oldProperties, entityType,
-                            newProperties, registrator, Collections.<String>emptySet());
+                            newProperties, registrator, Collections.<String> emptySet());
                     will(returnValue(new HashSet<ExperimentPropertyPE>(updated)));
 
                 }
@@ -622,7 +622,7 @@ public final class ExperimentBOTest extends AbstractBOTest
             });
 
         final ExperimentBO expBO = createExperimentBO();
-        expBO.deleteByTechId(experimentId, reason);
+        expBO.deleteByTechIds(Collections.singletonList(experimentId), reason);
         context.assertIsSatisfied();
     }
 
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java.expected b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java.expected
index 066f0ce4c48..22196590c5c 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java.expected
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java.expected
@@ -68,6 +68,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataStoreServiceKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseModificationKind.ObjectKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatastoreServiceDescription;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DeletionType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DynamicPropertyEvaluationInfo;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
@@ -342,7 +343,8 @@ public interface ICommonServer extends IServer
      */
     @Transactional(readOnly = true)
     @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
-    public List<Experiment> listExperiments(final String sessionToken,
+    public List<Experiment> listExperiments(
+            final String sessionToken,
             @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class) Collection<ExperimentIdentifier> experimentIdentifiers);
 
     /**
@@ -748,7 +750,7 @@ public interface ICommonServer extends IServer
             String reason);
 
     /**
-     * Deletes specified samples.
+     * Deletes/Invalidates specified samples.
      */
     @Transactional
     @RolesAllowed(RoleWithHierarchy.SPACE_POWER_USER)
@@ -756,10 +758,10 @@ public interface ICommonServer extends IServer
     public void deleteSamples(
             String sessionToken,
             @AuthorizationGuard(guardClass = SampleTechIdCollectionPredicate.class) List<TechId> sampleIds,
-            String reason);
+            String reason, DeletionType type);
 
     /**
-     * Deletes specified experiments.
+     * Deletes/Invalidates specified experiments.
      */
     @Transactional
     @RolesAllowed(RoleWithHierarchy.SPACE_POWER_USER)
@@ -767,7 +769,7 @@ public interface ICommonServer extends IServer
     public void deleteExperiments(
             String sessionToken,
             @AuthorizationGuard(guardClass = ExperimentTechIdPredicate.class) List<TechId> experimentIds,
-            String reason);
+            String reason, DeletionType deletionType);
 
     /**
      * Deletes specified attachments (all versions with given file names) of specified experiment.
-- 
GitLab