From 4e31be7c196d97e45023e21ca103dac31158ee87 Mon Sep 17 00:00:00 2001
From: pkupczyk <pkupczyk>
Date: Wed, 30 Nov 2016 20:28:47 +0000
Subject: [PATCH] SSDM-3768 : bugfixes: - missing Serializable for email
 notification - execution state didn't change from RUNNING to FAILED when an
 exception was thrown on a transaction commit

SVN: 37419
---
 .../AsynchronousOperationExecutor.java        |  28 +++-
 ...ynchronousOperationThreadPoolExecutor.java |  27 +--
 ...ynchronousOperationThreadPoolExecutor.java |   3 +-
 .../store/IOperationExecutionStore.java       |   2 +
 .../store/OperationExecutionDBStore.java      |  11 --
 .../store/OperationExecutionStore.java        | 158 +++++++++++-------
 openbis/source/java/service.properties        |   2 +-
 .../AsynchronousOperationExecutorTest.java    |   2 +-
 .../store/OperationExecutionStoreTest.java    |  17 +-
 .../IOperationExecutionNotification.java      |   4 +-
 .../OperationExecutionEmailNotification.java  |   2 +
 11 files changed, 151 insertions(+), 105 deletions(-)

diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/AsynchronousOperationExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/AsynchronousOperationExecutor.java
index 1c36cd2c05b..a531ac2d276 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/AsynchronousOperationExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/AsynchronousOperationExecutor.java
@@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperationResult;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.operation.AsynchronousOperationExecutionOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.operation.AsynchronousOperationExecutionResults;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.operation.OperationExecutionError;
@@ -108,10 +109,29 @@ public class AsynchronousOperationExecutor implements IAsynchronousOperationExec
                     @Override
                     public Void call() throws Exception
                     {
-                        // Transaction from the thread where the execution was scheduled is not available here,
-                        // therefore we need to call a bean with @Transactional annotation again.
-                        poolExecutor.execute(context, executionId, operations);
-                        return null;
+                        try
+                        {
+                            // Transaction from the thread where the execution was scheduled is not available here,
+                            // therefore we need to call beans with @Transactional annotation again. That's why we need the poolExecutor.
+
+                            // Execution store is annotated to always create its own transactions to be independent from the execution
+                            // (information about the execution must remain in operation_executions table even if the execution fails).
+
+                            // The try/catch block wraps around poolExecutor because at the end of its execute method
+                            // a transaction is flushed and any potential problems with the database commit
+                            // will be thrown from it.
+
+                            executionStore.executionRunning(context, executionId);
+                            List<IOperationResult> results = poolExecutor.execute(context, executionId, operations);
+                            executionStore.executionFinished(context, executionId, results);
+                            return null;
+
+                        } catch (Exception e)
+                        {
+                            log.error(e);
+                            executionStore.executionFailed(context, executionId, new OperationExecutionError(e));
+                            throw e;
+                        }
                     }
                 });
 
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/AsynchronousOperationThreadPoolExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/AsynchronousOperationThreadPoolExecutor.java
index 0f704e2c4b8..78cf540cb78 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/AsynchronousOperationThreadPoolExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/AsynchronousOperationThreadPoolExecutor.java
@@ -20,18 +20,13 @@ import java.util.List;
 
 import javax.transaction.Transactional;
 
-import org.apache.log4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperationResult;
-import ch.ethz.sis.openbis.generic.asapi.v3.dto.operation.OperationExecutionError;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.operation.id.OperationExecutionPermId;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext;
-import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.operation.store.IOperationExecutionStore;
-import ch.systemsx.cisd.common.logging.LogCategory;
-import ch.systemsx.cisd.common.logging.LogFactory;
 
 /**
  * @author pkupczyk
@@ -40,11 +35,6 @@ import ch.systemsx.cisd.common.logging.LogFactory;
 public class AsynchronousOperationThreadPoolExecutor implements IAsynchronousOperationThreadPoolExecutor
 {
 
-    private static final Logger log = LogFactory.getLogger(LogCategory.OPERATION, AsynchronousOperationThreadPoolExecutor.class);
-
-    @Autowired
-    private IOperationExecutionStore executionStore;
-
     @Autowired
     private IOperationsExecutor operationsExecutor;
 
@@ -52,27 +42,16 @@ public class AsynchronousOperationThreadPoolExecutor implements IAsynchronousOpe
     {
     }
 
-    AsynchronousOperationThreadPoolExecutor(IOperationExecutionStore executionStore, IOperationsExecutor operationsExecutor)
+    AsynchronousOperationThreadPoolExecutor(IOperationsExecutor operationsExecutor)
     {
-        this.executionStore = executionStore;
         this.operationsExecutor = operationsExecutor;
     }
 
     @Override
     @Transactional
-    public void execute(IOperationContext context, OperationExecutionPermId executionId, List<? extends IOperation> operations)
+    public List<IOperationResult> execute(IOperationContext context, OperationExecutionPermId executionId, List<? extends IOperation> operations)
     {
-        try
-        {
-            executionStore.executionRunning(context, executionId);
-            List<IOperationResult> results = operationsExecutor.execute(context, operations);
-            executionStore.executionFinished(context, executionId, results);
-        } catch (Exception e)
-        {
-            log.error(e);
-            executionStore.executionFailed(context, executionId, new OperationExecutionError(e));
-            throw e;
-        }
+        return operationsExecutor.execute(context, operations);
     }
 
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/IAsynchronousOperationThreadPoolExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/IAsynchronousOperationThreadPoolExecutor.java
index 422d0cf883e..74e06303de8 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/IAsynchronousOperationThreadPoolExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/IAsynchronousOperationThreadPoolExecutor.java
@@ -19,6 +19,7 @@ package ch.ethz.sis.openbis.generic.server.asapi.v3.executor.operation;
 import java.util.List;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperationResult;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.operation.id.OperationExecutionPermId;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext;
 
@@ -28,6 +29,6 @@ import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext;
 public interface IAsynchronousOperationThreadPoolExecutor
 {
 
-    void execute(IOperationContext context, OperationExecutionPermId executionId, List<? extends IOperation> operations);
+    List<IOperationResult> execute(IOperationContext context, OperationExecutionPermId executionId, List<? extends IOperation> operations);
 
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/IOperationExecutionStore.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/IOperationExecutionStore.java
index ba658dd0525..3dcb871508b 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/IOperationExecutionStore.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/IOperationExecutionStore.java
@@ -54,6 +54,8 @@ public interface IOperationExecutionStore
     public void executionDetailsAvailability(IOperationContext context, OperationExecutionPermId executionId,
             OperationExecutionAvailability availability);
 
+    public void synchronizeProgress();
+
     public OperationExecution getExecution(IOperationContext context, IOperationExecutionId executionId, OperationExecutionFetchOptions fetchOptions);
 
     public List<OperationExecution> getExecutions(IOperationContext context, OperationExecutionFetchOptions fetchOptions);
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/OperationExecutionDBStore.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/OperationExecutionDBStore.java
index 7cd6c513d05..138af3aae4c 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/OperationExecutionDBStore.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/OperationExecutionDBStore.java
@@ -22,8 +22,6 @@ import java.util.List;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.DataAccessException;
 import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Propagation;
-import org.springframework.transaction.annotation.Transactional;
 
 import ch.systemsx.cisd.openbis.generic.server.business.bo.DataAccessExceptionTranslator;
 import ch.systemsx.cisd.openbis.generic.shared.dto.OperationExecutionAvailability;
@@ -50,7 +48,6 @@ public class OperationExecutionDBStore implements IOperationExecutionDBStore
     }
 
     @Override
-    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionNew(String code, Long owner, String description, String notification, List<String> operations, long availabilityTime,
             long summaryAvailabilityTime, long detailsAvailabilityTime)
     {
@@ -81,7 +78,6 @@ public class OperationExecutionDBStore implements IOperationExecutionDBStore
     }
 
     @Override
-    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionScheduled(String code)
     {
         OperationExecutionPE executionPE = dao.findExecutionByCode(code);
@@ -89,7 +85,6 @@ public class OperationExecutionDBStore implements IOperationExecutionDBStore
     }
 
     @Override
-    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionRunning(String code)
     {
         OperationExecutionPE executionPE = dao.findExecutionByCode(code);
@@ -98,7 +93,6 @@ public class OperationExecutionDBStore implements IOperationExecutionDBStore
     }
 
     @Override
-    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionProgressed(String code, String progress)
     {
         // This method may be called when the execution state is already FINISHED (progress is reported with some delay by a different thread - other
@@ -109,7 +103,6 @@ public class OperationExecutionDBStore implements IOperationExecutionDBStore
     }
 
     @Override
-    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionFailed(String code, String error)
     {
         OperationExecutionPE executionPE = dao.findExecutionByCode(code);
@@ -119,7 +112,6 @@ public class OperationExecutionDBStore implements IOperationExecutionDBStore
     }
 
     @Override
-    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionFinished(String code, List<String> results)
     {
         OperationExecutionPE executionPE = dao.findExecutionByCode(code);
@@ -130,7 +122,6 @@ public class OperationExecutionDBStore implements IOperationExecutionDBStore
     }
 
     @Override
-    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionAvailability(String code, OperationExecutionAvailability availability)
     {
         OperationExecutionPE executionPE = dao.findExecutionByCode(code);
@@ -154,7 +145,6 @@ public class OperationExecutionDBStore implements IOperationExecutionDBStore
     }
 
     @Override
-    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionSummaryAvailability(String code, OperationExecutionAvailability summaryAvailability)
     {
         OperationExecutionPE executionPE = dao.findExecutionByCode(code);
@@ -175,7 +165,6 @@ public class OperationExecutionDBStore implements IOperationExecutionDBStore
     }
 
     @Override
-    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionDetailsAvailability(String code, OperationExecutionAvailability detailsAvailability)
     {
         OperationExecutionPE executionPE = dao.findExecutionByCode(code);
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/OperationExecutionStore.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/OperationExecutionStore.java
index f0d7c9524c9..30e70d6efa2 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/OperationExecutionStore.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/OperationExecutionStore.java
@@ -30,7 +30,11 @@ import javax.annotation.Resource;
 
 import org.apache.log4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 
@@ -69,11 +73,13 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.OperationExecutionState;
  * @author pkupczyk
  */
 @Component
-public class OperationExecutionStore implements IOperationExecutionStore, Runnable
+public class OperationExecutionStore implements IOperationExecutionStore, ApplicationContextAware, Runnable
 {
 
     private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, OperationExecutionStore.class);
 
+    private ApplicationContext applicationContext;
+
     @Resource(name = ObjectMapperResource.NAME)
     private ObjectMapper objectMapper;
 
@@ -109,11 +115,10 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
         this.dbStore = dbStore;
         this.fsStore = fsStore;
         this.notifier = notifier;
-        init();
     }
 
     @PostConstruct
-    private void init()
+    void init()
     {
         progressThread = new Thread(this);
         progressThread.setName(config.getProgressThreadName());
@@ -122,6 +127,7 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
     }
 
     @Override
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionNew(final IOperationContext context, final OperationExecutionPermId executionId, final List<? extends IOperation> operations,
             final IOperationExecutionOptions options)
     {
@@ -171,6 +177,7 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
     }
 
     @Override
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionScheduled(IOperationContext context, OperationExecutionPermId executionId)
     {
         checkContext(context);
@@ -187,6 +194,7 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
     }
 
     @Override
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionRunning(IOperationContext context, OperationExecutionPermId executionId)
     {
         checkContext(context);
@@ -202,17 +210,8 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
         operationLog.info("Execution " + executionId + " is running");
     }
 
-    private void executionProgressed(OperationExecutionPermId executionId, IProgress progress)
-    {
-        dbStore.executionProgressed(executionId.getPermId(), ProgressFormatter.format(progress));
-        fsStore.executionProgressed(executionId.getPermId(),
-                new OperationExecutionProgress(ProgressFormatter.format(progress), progress.getNumItemsProcessed(),
-                        progress.getTotalItemsToProcess()));
-
-        operationLog.info("Execution " + executionId + " progressed (" + ProgressFormatter.formatShort(progress) + ")");
-    }
-
     @Override
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionFailed(IOperationContext context, OperationExecutionPermId executionId, IOperationExecutionError error)
     {
         checkContext(context);
@@ -234,6 +233,7 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
     }
 
     @Override
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionFinished(IOperationContext context, OperationExecutionPermId executionId, List<? extends IOperationResult> results)
     {
         checkContext(context);
@@ -260,6 +260,7 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
     }
 
     @Override
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionAvailability(IOperationContext context, OperationExecutionPermId executionId, OperationExecutionAvailability availability)
     {
         checkContext(context);
@@ -278,6 +279,7 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
     }
 
     @Override
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionSummaryAvailability(IOperationContext context, OperationExecutionPermId executionId,
             OperationExecutionAvailability summaryAvailability)
     {
@@ -297,6 +299,7 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
     }
 
     @Override
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void executionDetailsAvailability(IOperationContext context, OperationExecutionPermId executionId,
             OperationExecutionAvailability detailsAvailability)
     {
@@ -316,6 +319,81 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
     }
 
     @Override
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public void synchronizeProgress()
+    {
+        Map<OperationExecutionPermId, IProgress> progressMapCopy = new HashMap<OperationExecutionPermId, IProgress>();
+
+        synchronized (progressMap)
+        {
+            progressMapCopy.putAll(progressMap);
+            progressMap.clear();
+        }
+
+        if (false == progressMapCopy.isEmpty())
+        {
+            operationLog.info("Progress synchronization with database and file system has been started (" + progressMapCopy.size()
+                    + " execution(s) to be synchronized).");
+
+            int successCount = 0;
+            int failureCount = 0;
+
+            for (Map.Entry<OperationExecutionPermId, IProgress> progressEntry : progressMapCopy.entrySet())
+            {
+                OperationExecutionPermId executionId = progressEntry.getKey();
+                IProgress progress = progressEntry.getValue();
+
+                try
+                {
+                    dbStore.executionProgressed(executionId.getPermId(), ProgressFormatter.format(progress));
+                    fsStore.executionProgressed(executionId.getPermId(),
+                            new OperationExecutionProgress(ProgressFormatter.format(progress), progress.getNumItemsProcessed(),
+                                    progress.getTotalItemsToProcess()));
+
+                    operationLog.info("Execution " + executionId + " progressed (" + ProgressFormatter.formatShort(progress) + ")");
+
+                    successCount++;
+
+                } catch (Throwable t)
+                {
+                    operationLog.error("Couldn't synchronize progress for execution with id " + executionId, t);
+                    failureCount++;
+                }
+            }
+
+            operationLog.info("Progress synchronization with database and file system has been finished (" + successCount
+                    + " execution(s) has been successfully synchronized, synchronization of " + failureCount + " execution(s) has failed).");
+        }
+    }
+
+    @Override
+    public void run()
+    {
+        while (false == Thread.currentThread().isInterrupted())
+        {
+            // Transaction is not available in this thread. For a transaction to be created we need to make an "external" call
+            // to OperationExecutionStore bean. Only then the AOP magic gets executed. To make such call we need to
+            // fetch the bean from the context. If we made a call on OperationExecutionStore.this,
+            // then it would not go trough the AOP, any @Transactional annotations would be ignored and a transaction wouldn't be created.
+
+            if (false == progressMap.isEmpty())
+            {
+                IOperationExecutionStore store = applicationContext.getBean(IOperationExecutionStore.class);
+                store.synchronizeProgress();
+            }
+
+            try
+            {
+                Thread.sleep(config.getProgressInterval() * 1000);
+            } catch (InterruptedException ex)
+            {
+                return;
+            }
+        }
+    }
+
+    @Override
+    @Transactional
     public OperationExecution getExecution(IOperationContext context, IOperationExecutionId executionId, OperationExecutionFetchOptions fo)
     {
         checkContext(context);
@@ -333,6 +411,7 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
     }
 
     @Override
+    @Transactional
     public List<OperationExecution> getExecutions(IOperationContext context, OperationExecutionFetchOptions fetchOptions)
     {
         checkContext(context);
@@ -343,6 +422,7 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
     }
 
     @Override
+    @Transactional
     public List<OperationExecution> getExecutionsToBeTimeOutPending(IOperationContext context, OperationExecutionFetchOptions fetchOptions)
     {
         checkContext(context);
@@ -353,6 +433,7 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
     }
 
     @Override
+    @Transactional
     public List<OperationExecution> getExecutionsToBeTimedOut(IOperationContext context, OperationExecutionFetchOptions fetchOptions)
     {
         checkContext(context);
@@ -363,6 +444,7 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
     }
 
     @Override
+    @Transactional
     public List<OperationExecution> getExecutionsToBeDeleted(IOperationContext context, OperationExecutionFetchOptions fetchOptions)
     {
         checkContext(context);
@@ -724,55 +806,9 @@ public class OperationExecutionStore implements IOperationExecutionStore, Runnab
     }
 
     @Override
-    public void run()
+    public void setApplicationContext(ApplicationContext applicationContext)
     {
-        while (false == Thread.currentThread().isInterrupted())
-        {
-            Map<OperationExecutionPermId, IProgress> progressMapCopy = new HashMap<OperationExecutionPermId, IProgress>();
-
-            synchronized (progressMap)
-            {
-                progressMapCopy.putAll(progressMap);
-                progressMap.clear();
-            }
-
-            if (false == progressMapCopy.isEmpty())
-            {
-                operationLog.info("Progress synchronization with database and file system has been started (" + progressMapCopy.size()
-                        + " execution(s) to be synchronized).");
-
-                int successCount = 0;
-                int failureCount = 0;
-
-                for (Map.Entry<OperationExecutionPermId, IProgress> progressEntry : progressMapCopy.entrySet())
-                {
-                    OperationExecutionPermId executionId = progressEntry.getKey();
-                    IProgress progress = progressEntry.getValue();
-
-                    try
-                    {
-                        executionProgressed(executionId, progress);
-
-                        successCount++;
-
-                    } catch (Throwable t)
-                    {
-                        operationLog.error("Couldn't synchronize progress for execution with id " + executionId, t);
-                        failureCount++;
-                    }
-                }
-
-                operationLog.info("Progress synchronization with database and file system has been finished (" + successCount
-                        + " execution(s) has been successfully synchronized, synchronization of " + failureCount + " execution(s) has failed).");
-            }
-            try
-            {
-                Thread.sleep(config.getProgressInterval() * 1000);
-            } catch (InterruptedException ex)
-            {
-                return;
-            }
-        }
+        this.applicationContext = applicationContext;
     }
 
     public void shutdown()
diff --git a/openbis/source/java/service.properties b/openbis/source/java/service.properties
index e0b410fc55d..9a34068188c 100644
--- a/openbis/source/java/service.properties
+++ b/openbis/source/java/service.properties
@@ -202,7 +202,7 @@ mail.smtp.password =
 #
   api.v3.operation-execution.store.path = targets/operation-execution-store
 #
-# A thread pool that is used for executing all asynchronous operations and synchronous operations with defined execution id.  
+# A thread pool that is used for executing all asynchronous operations.  
 #
 # api.v3.operation-execution.thread-pool.name = operation-execution-pool
 # api.v3.operation-execution.thread-pool.core-size = 10
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/AsynchronousOperationExecutorTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/AsynchronousOperationExecutorTest.java
index ee4708726d3..ae773123011 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/AsynchronousOperationExecutorTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/AsynchronousOperationExecutorTest.java
@@ -537,7 +537,7 @@ public class AsynchronousOperationExecutorTest
 
     private AsynchronousOperationExecutor createExecutor()
     {
-        AsynchronousOperationThreadPoolExecutor poolExecutor = new AsynchronousOperationThreadPoolExecutor(executionStore, operationsExecutor);
+        AsynchronousOperationThreadPoolExecutor poolExecutor = new AsynchronousOperationThreadPoolExecutor(operationsExecutor);
         AsynchronousOperationExecutor executor = new AsynchronousOperationExecutor(executionConfig, executionIdFactory, executionStore, poolExecutor);
         executors.add(executor);
         return executor;
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/OperationExecutionStoreTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/OperationExecutionStoreTest.java
index efd4302bfe1..e8badcdfa1e 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/OperationExecutionStoreTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/store/OperationExecutionStoreTest.java
@@ -25,6 +25,7 @@ import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.jmock.api.Invocation;
 import org.jmock.lib.action.CustomAction;
+import org.springframework.context.ApplicationContext;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -66,6 +67,8 @@ public class OperationExecutionStoreTest
 
     private Mockery mockery;
 
+    private ApplicationContext applicationContext;
+
     private IOperationExecutionConfig executionConfig;
 
     private IOperationExecutionAuthorizationExecutor executionAuthorization;
@@ -98,6 +101,7 @@ public class OperationExecutionStoreTest
 
         mockery = new Mockery();
 
+        applicationContext = mockery.mock(ApplicationContext.class);
         executionConfig = mockery.mock(IOperationExecutionConfig.class);
         executionAuthorization = mockery.mock(IOperationExecutionAuthorizationExecutor.class);
         executionDAO = mockery.mock(IOperationExecutionDBStoreDAO.class);
@@ -488,7 +492,18 @@ public class OperationExecutionStoreTest
         OperationExecutionFSStore fsStore = new OperationExecutionFSStore(executionConfig);
         OperationExecutionDBStore dbStore = new OperationExecutionDBStore(executionDAO);
         OperationExecutionNotifier notifier = new OperationExecutionNotifier();
-        OperationExecutionStore store = new OperationExecutionStore(executionConfig, executionAuthorization, dbStore, fsStore, notifier);
+
+        final OperationExecutionStore store = new OperationExecutionStore(executionConfig, executionAuthorization, dbStore, fsStore, notifier);
+        store.setApplicationContext(applicationContext);
+        mockery.checking(new Expectations()
+            {
+                {
+                    allowing(applicationContext).getBean(IOperationExecutionStore.class);
+                    will(returnValue(store));
+                }
+            });
+        store.init();
+        
         stores.add(store);
         return store;
     }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/operation/IOperationExecutionNotification.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/operation/IOperationExecutionNotification.java
index d42259f8f38..f65c8481223 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/operation/IOperationExecutionNotification.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/operation/IOperationExecutionNotification.java
@@ -16,13 +16,15 @@
 
 package ch.ethz.sis.openbis.generic.asapi.v3.dto.operation;
 
+import java.io.Serializable;
+
 import ch.systemsx.cisd.base.annotation.JsonObject;
 
 /**
  * @author pkupczyk
  */
 @JsonObject("as.dto.operation.IOperationExecutionNotification")
-public interface IOperationExecutionNotification
+public interface IOperationExecutionNotification extends Serializable
 {
 
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/operation/OperationExecutionEmailNotification.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/operation/OperationExecutionEmailNotification.java
index 22910582f26..af9dc1f47b5 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/operation/OperationExecutionEmailNotification.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/operation/OperationExecutionEmailNotification.java
@@ -28,6 +28,8 @@ import ch.systemsx.cisd.base.annotation.JsonObject;
 public class OperationExecutionEmailNotification implements IOperationExecutionNotification
 {
 
+    private static final long serialVersionUID = 1L;
+
     private List<String> emails;
 
     @SuppressWarnings("unused")
-- 
GitLab