From 030154a811fae0c855e206e4522f6ecd61214aa0 Mon Sep 17 00:00:00 2001
From: brinn <brinn>
Date: Tue, 15 Jul 2008 07:04:10 +0000
Subject: [PATCH] add: error status reporting [DMV-31] fix: ensure the error
 marker files are removed when the error condition is finished

SVN: 7234
---
 .../ch/systemsx/cisd/common/Constants.java    |  8 +-
 ...HighwaterMarkDirectoryScanningHandler.java |  3 +-
 .../utilities/DirectoryScannedStore.java      | 20 +++-
 .../DirectoryScanningHandlerInterceptor.java  |  3 +-
 .../utilities/DirectoryScanningTimerTask.java | 99 +++++++++++++++----
 .../FaultyPathDirectoryScanningHandler.java   | 17 ++--
 .../utilities/IDirectoryScanningHandler.java  | 67 ++++++++++++-
 .../utilities/ITimerTaskStatusProvider.java   | 22 ++++-
 ...waterMarkDirectoryScanningHandlerTest.java | 33 +++++--
 .../DirectoryScanningTimerTaskTest.java       |  3 +-
 .../cisd/datamover/FileScannedStore.java      | 30 +++---
 .../cisd/datamover/IncomingProcessor.java     |  5 +-
 ...imerTaskListenerForMarkerFileProtocol.java | 36 +++++--
 .../cisd/datamover/common/MarkerFile.java     |  8 ++
 14 files changed, 282 insertions(+), 72 deletions(-)

diff --git a/common/source/java/ch/systemsx/cisd/common/Constants.java b/common/source/java/ch/systemsx/cisd/common/Constants.java
index a3df6c694a0..31b7c2cdec3 100644
--- a/common/source/java/ch/systemsx/cisd/common/Constants.java
+++ b/common/source/java/ch/systemsx/cisd/common/Constants.java
@@ -47,9 +47,13 @@ public final class Constants
     /** The number of milliseconds to sleep before retrying (<i>3s</i>). */
     public static final long MILLIS_TO_SLEEP_BEFORE_RETRYING = 3 * DateUtils.MILLIS_PER_SECOND;
 
-    /** The number of retries when a given process failed. */
-    public static final int MAXIMUM_RETRY_COUNT = 12;
+    /** The maximal number of invocations when a given process failed. */
+    public static final int MAXIMUM_INVOCATIONS_ON_FAILURE = 12;
 
     /** The prefix of marker files that indicate that a directory is currently being processed. */
     public static final String PROCESSING_PREFIX = MARKER_PREFIX + "processing_";
+
+    /** The file name of the file that contains file names which are known to be bad. */
+    public static final String FAULTY_PATH_FILENAME = ".faulty_paths";
+
 }
diff --git a/common/source/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkDirectoryScanningHandler.java b/common/source/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkDirectoryScanningHandler.java
index f252714d3f4..99dc94461ba 100644
--- a/common/source/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkDirectoryScanningHandler.java
+++ b/common/source/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkDirectoryScanningHandler.java
@@ -88,7 +88,8 @@ public final class HighwaterMarkDirectoryScanningHandler extends
     @Override
     public HandleInstruction mayHandle(final IScannedStore scannedStore, final StoreItem storeItem)
     {
-        return mayHandle() == false ? HandleInstruction.ERROR : super.mayHandle(
+        return (mayHandle() == false) ? HandleInstruction.createError(
+                "Not enough disk space on store '%s'.", scannedStore) : super.mayHandle(
                 scannedStore, storeItem);
     }
 
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScannedStore.java b/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScannedStore.java
index 349e05c7598..e87c0ebfad9 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScannedStore.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScannedStore.java
@@ -17,6 +17,8 @@ package ch.systemsx.cisd.common.utilities;
 
 import java.io.File;
 import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.List;
 
 import ch.systemsx.cisd.common.logging.ISimpleLogger;
 import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask.IScannedStore;
@@ -54,9 +56,9 @@ public final class DirectoryScannedStore implements IScannedStore
         return StoreItem.asFile(directory, item).exists();
     }
 
-    public final StoreItem[] tryListSortedReadyToProcess(final ISimpleLogger loggerOrNull)
+    public StoreItem[] tryListSorted(ISimpleLogger loggerOrNull)
     {
-        final File[] files = FileUtilities.tryListFiles(directory, filter, loggerOrNull);
+        final File[] files = FileUtilities.tryListFiles(directory, null, loggerOrNull);
         if (files != null)
         {
             FileUtilities.sortByLastModified(files);
@@ -67,6 +69,19 @@ public final class DirectoryScannedStore implements IScannedStore
         }
     }
 
+    public StoreItem[] filterReadyToProcess(StoreItem[] items)
+    {
+        final List<StoreItem> result = new ArrayList<StoreItem>(items.length);
+        for (StoreItem item : items)
+        {
+            if (filter.accept(new File(directory, item.getName())))
+            {
+                result.add(item);
+            }
+        }
+        return result.toArray(new StoreItem[result.size()]);
+    }
+
     //
     // Object
     //
@@ -76,4 +91,5 @@ public final class DirectoryScannedStore implements IScannedStore
     {
         return directory.toString();
     }
+
 }
\ No newline at end of file
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningHandlerInterceptor.java b/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningHandlerInterceptor.java
index 55205035ed2..0d93b38aa14 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningHandlerInterceptor.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningHandlerInterceptor.java
@@ -16,6 +16,7 @@
 
 package ch.systemsx.cisd.common.utilities;
 
+import ch.systemsx.cisd.common.exceptions.Status;
 import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask.IScannedStore;
 
 /**
@@ -49,7 +50,7 @@ public class DirectoryScanningHandlerInterceptor implements IDirectoryScanningHa
         return directoryScanningHandler.mayHandle(scannedStore, storeItem);
     }
 
-    public boolean finishItemHandle(final IScannedStore scannedStore, final StoreItem storeItem)
+    public Status finishItemHandle(final IScannedStore scannedStore, final StoreItem storeItem)
     {
         return directoryScanningHandler.finishItemHandle(scannedStore, storeItem);
     }
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java b/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java
index df5e3cf1574..4c1fe7f0af4 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java
@@ -18,18 +18,26 @@ package ch.systemsx.cisd.common.utilities;
 
 import java.io.File;
 import java.io.FileFilter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
 import java.util.TimerTask;
 
 import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 
 import ch.systemsx.cisd.common.collections.CollectionUtils;
+import ch.systemsx.cisd.common.exceptions.Status;
 import ch.systemsx.cisd.common.logging.ConditionalNotificationLogger;
 import ch.systemsx.cisd.common.logging.ISimpleLogger;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.common.utilities.IDirectoryScanningHandler.HandleInstruction;
+import ch.systemsx.cisd.common.utilities.IDirectoryScanningHandler.HandleInstructionFlag;
 
 /**
  * A {@link TimerTask} that scans a source directory for entries that are accepted by some
@@ -60,9 +68,11 @@ public final class DirectoryScanningTimerTask extends TimerTask implements ITime
 
     private final ConditionalNotificationLogger notificationLogger;
 
-    private int numberOfProcessedItems;
+    private final Map<StoreItem, String> errorLog;
+    
+    private boolean didSomeWork;
 
-    private int numberOfErrorItems;
+    private String threadNameOrNull;
 
     /**
      * Indicates that we should try to exit the {@link #run()} method as soon as possible.
@@ -139,6 +149,7 @@ public final class DirectoryScanningTimerTask extends TimerTask implements ITime
         this.notificationLogger =
                 new ConditionalNotificationLogger(operationLog, Level.WARN, notificationLog,
                         ignoredErrorCount);
+        this.errorLog = new LinkedHashMap<StoreItem, String>();
     }
 
     /**
@@ -172,7 +183,7 @@ public final class DirectoryScanningTimerTask extends TimerTask implements ITime
     private final StoreItem[] listStoreItems()
     {
         final StoreItem[] storeItems =
-                sourceDirectory.tryListSortedReadyToProcess(notificationLogger);
+                sourceDirectory.tryListSorted(notificationLogger);
         if (storeItems != null)
         {
             notificationLogger.reset(String.format("Directory '%s' is available again.",
@@ -203,15 +214,17 @@ public final class DirectoryScanningTimerTask extends TimerTask implements ITime
         {
             operationLog.trace(String.format("Start scanning directory '%s'.", sourceDirectory));
         }
+        threadNameOrNull = Thread.currentThread().getName();
         try
         {
-            numberOfProcessedItems = 0;
-            numberOfErrorItems = 0;
+            didSomeWork = false;
             int numberOfItemsProcessedInLastRound;
             do
             {
                 numberOfItemsProcessedInLastRound = 0;
-                final StoreItem[] storeItems = listStoreItems();
+                final StoreItem[] allStoreItems = listStoreItems();
+                cleanseErrorLog(allStoreItems);
+                final StoreItem[] storeItems = sourceDirectory.filterReadyToProcess(allStoreItems);
                 final int numberOfItems = storeItems.length;
                 directoryScanningHandler.beforeHandle();
                 for (int i = 0; i < numberOfItems; i++)
@@ -231,7 +244,7 @@ public final class DirectoryScanningTimerTask extends TimerTask implements ITime
                     }
                     final HandleInstruction instruction =
                             directoryScanningHandler.mayHandle(sourceDirectory, storeItem);
-                    if (HandleInstruction.PROCESS.equals(instruction))
+                    if (HandleInstructionFlag.PROCESS.equals(instruction.getFlag()))
                     {
                         try
                         {
@@ -239,31 +252,38 @@ public final class DirectoryScanningTimerTask extends TimerTask implements ITime
                             if (operationLog.isTraceEnabled())
                             {
                                 operationLog.trace(String.format(
-                                        "Following store item '%s' has been handled.", storeItem));
+                                        "Store item '%s' has been handled.", storeItem));
                             }
-                            ++numberOfProcessedItems;
+                            didSomeWork = true;
                             ++numberOfItemsProcessedInLastRound;
                         } catch (final Exception ex)
                         {
                             // Do not stop when processing of one file has failed,
                             // continue with other files.
-                            ++numberOfErrorItems;
+                            errorLog.put(storeItem, String.format(
+                                    "Exception when processing item '%s': %s (%s)", storeItem, ex
+                                            .getClass().getSimpleName(), StringUtils.defaultString(
+                                            ex.getMessage(), "no message")));
                             printNotification(ex);
                         } finally
                         {
-                            final boolean ok =
+                            final Status status =
                                     directoryScanningHandler.finishItemHandle(sourceDirectory,
                                             storeItem);
-                            if (ok == false)
+                            if (status.isError())
                             {
-                                ++numberOfErrorItems;
+                                final String msgOrNull = status.tryGetErrorMessage();
+                                errorLog.put(storeItem, StringUtils.defaultIfEmpty(msgOrNull,
+                                        getDefaultErrorMessage(storeItem)));
                             }
                         }
                     } else
                     {
-                        if (HandleInstruction.ERROR.equals(instruction))
+                        if (HandleInstructionFlag.ERROR.equals(instruction.getFlag()))
                         {
-                            ++numberOfErrorItems;
+                            final String msgOrNull = instruction.tryGetMessage();
+                            errorLog.put(storeItem, StringUtils.defaultIfEmpty(msgOrNull,
+                                    getDefaultErrorMessage(storeItem)));
                         }
                         if (operationLog.isTraceEnabled())
                         {
@@ -284,18 +304,47 @@ public final class DirectoryScanningTimerTask extends TimerTask implements ITime
         }
     }
 
+    private void cleanseErrorLog(StoreItem[] allStoreItems)
+    {
+        final Set<StoreItem> itemSet = new HashSet<StoreItem>(Arrays.asList(allStoreItems));
+        for (StoreItem errorItem : errorLog.keySet())
+        {
+            if (itemSet.contains(errorItem) == false)
+            {
+                errorLog.remove(errorItem);
+            }
+        }
+    }
+    
+    private String getDefaultErrorMessage(final StoreItem storeItem)
+    {
+        return String.format("Error processing item '%s'.", storeItem);
+    }
+
     //
     // ITimerTaskStatusProvider
     //
 
     public boolean hasErrors()
     {
-        return (numberOfErrorItems > 0);
+        return errorLog.size() > 0;
     }
 
     public boolean hasPerformedMeaningfulWork()
     {
-        return (numberOfProcessedItems > 0);
+        return didSomeWork;
+    }
+
+    public String tryGetErrorLog()
+    {
+        if (hasErrors())
+        {
+            return String.format("  [%s]\n  %s", StringUtils.defaultIfEmpty(threadNameOrNull,
+                    "UNKNOWN"), StringUtils.join(errorLog.values(), "\n  "));
+        } else
+        {
+            return null;
+        }
     }
 
     //
@@ -305,12 +354,24 @@ public final class DirectoryScanningTimerTask extends TimerTask implements ITime
     public static interface IScannedStore
     {
         /**
-         * List items in the scanned store in order in which they should be handled.
+         * List <i>all</i> items (not just the ones who are ready to be processed) in the scanned
+         * store in order in which they should be handled.
          * 
          * @return <code>null</code> if it was no able to access the items of this scanned store.
          */
-        StoreItem[] tryListSortedReadyToProcess(ISimpleLogger loggerOrNull);
+        StoreItem[] tryListSorted(ISimpleLogger loggerOrNull);
+
+        /**
+         * Performs a filtering on the items.
+         * 
+         * @returns Only those <var>items</var> which are ready to be processed right now.
+         */
+        StoreItem[] filterReadyToProcess(final StoreItem[] items);
 
+        /**
+         * Returns <code>true</code>, if the <var>item</var> either still exists or is in an
+         * error state.
+         */
         boolean existsOrError(StoreItem item);
 
         /**
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/FaultyPathDirectoryScanningHandler.java b/common/source/java/ch/systemsx/cisd/common/utilities/FaultyPathDirectoryScanningHandler.java
index d4c62e3586d..b7e6c5d78b0 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/FaultyPathDirectoryScanningHandler.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/FaultyPathDirectoryScanningHandler.java
@@ -22,8 +22,10 @@ import java.util.Set;
 
 import org.apache.log4j.Logger;
 
+import ch.systemsx.cisd.common.Constants;
 import ch.systemsx.cisd.common.collections.CollectionIO;
 import ch.systemsx.cisd.common.collections.CollectionUtils;
+import ch.systemsx.cisd.common.exceptions.Status;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask.IScannedStore;
@@ -49,12 +51,10 @@ public final class FaultyPathDirectoryScanningHandler implements IDirectoryScann
 
     private long faultyPathsLastChanged;
 
-    static final String FAULTY_PATH_FILENAME = ".faulty_paths";
-
     public FaultyPathDirectoryScanningHandler(final File faultyPathDirectory)
     {
         this.faultyPaths = new HashSet<String>();
-        this.faultyPathsFile = new File(faultyPathDirectory, FAULTY_PATH_FILENAME);
+        this.faultyPathsFile = new File(faultyPathDirectory, Constants.FAULTY_PATH_FILENAME);
     }
 
     private final void checkForFaultyPathsFileChanged()
@@ -129,31 +129,32 @@ public final class FaultyPathDirectoryScanningHandler implements IDirectoryScann
         checkForFaultyPathsFileChanged();
     }
 
-    public final HandleInstruction mayHandle(final IScannedStore scannedStore, final StoreItem storeItem)
+    public final HandleInstruction mayHandle(final IScannedStore scannedStore,
+            final StoreItem storeItem)
     {
         if (isFaultyPathsFile(scannedStore, storeItem))
         {
             return HandleInstruction.IGNORE;
         } else if (isFaultyPath(scannedStore, storeItem))
         {
-            return HandleInstruction.ERROR;
+            return HandleInstruction.createError("Known bad item '%s'.", storeItem);
         } else
         {
             return HandleInstruction.PROCESS;
         }
     }
 
-    public final boolean finishItemHandle(final IScannedStore scannedStore, final StoreItem storeItem)
+    public final Status finishItemHandle(final IScannedStore scannedStore, final StoreItem storeItem)
     {
         // If the item still exists, we assume that it has not been handled. So it
         // should be added to the faulty paths.
         if (scannedStore.existsOrError(storeItem))
         {
             addToFaultyPaths(scannedStore, storeItem);
-            return false;
+            return Status.createError("Failed to move item '%s'.", storeItem);
         } else
         {
-            return true;
+            return Status.OK;
         }
     }
 
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/IDirectoryScanningHandler.java b/common/source/java/ch/systemsx/cisd/common/utilities/IDirectoryScanningHandler.java
index 3c794f1a46a..54c1001499e 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/IDirectoryScanningHandler.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/IDirectoryScanningHandler.java
@@ -16,6 +16,7 @@
 
 package ch.systemsx.cisd.common.utilities;
 
+import ch.systemsx.cisd.common.exceptions.Status;
 import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask.IScannedStore;
 
 /**
@@ -29,13 +30,70 @@ public interface IDirectoryScanningHandler
 {
 
     /**
-     * The instruction of whether to process an item or not.
+     * The instruction flag of whether to process an item or not.
      */
-    public enum HandleInstruction
+    public enum HandleInstructionFlag
     {
         PROCESS, IGNORE, ERROR
     }
 
+    /**
+     * The instruction of whether to process an item or not, possibly including a message on why not
+     * to process it.
+     * 
+     * @author Bernd Rinn
+     */
+    public static class HandleInstruction
+    {
+        public static HandleInstruction PROCESS =
+                new HandleInstruction(HandleInstructionFlag.PROCESS, null);
+
+        public static HandleInstruction IGNORE =
+                new HandleInstruction(HandleInstructionFlag.IGNORE, null);
+
+        private final HandleInstructionFlag flag;
+
+        private final String messageOrNull;
+
+        public static HandleInstruction createError()
+        {
+            return new HandleInstruction(HandleInstructionFlag.ERROR, null);
+        }
+
+        public static HandleInstruction createError(String message)
+        {
+            assert message != null;
+
+            return new HandleInstruction(HandleInstructionFlag.ERROR, message);
+        }
+
+        public static HandleInstruction createError(String messageTemplate, Object... args)
+        {
+            assert messageTemplate != null;
+
+            return new HandleInstruction(HandleInstructionFlag.ERROR, String.format(
+                    messageTemplate, args));
+        }
+
+        private HandleInstruction(HandleInstructionFlag flag, String messageOrNull)
+        {
+            assert flag != null;
+
+            this.flag = flag;
+            this.messageOrNull = messageOrNull;
+        }
+
+        public final HandleInstructionFlag getFlag()
+        {
+            return flag;
+        }
+
+        public final String tryGetMessage()
+        {
+            return messageOrNull;
+        }
+    }
+
     /**
      * Is performed just before handling all the items contained in the store.
      */
@@ -50,8 +108,7 @@ public interface IDirectoryScanningHandler
     /**
      * Finishes and closes the handling of given <var>storeItem</var>.
      * 
-     * @returns <code>true</code>, if the item has been handled correctly and <code>false</code>
-     *          if an error occurred.
+     * @returns A status of handling the <var>storeItem</var>.
      */
-    public boolean finishItemHandle(IScannedStore scannedStore, StoreItem storeItem);
+    public Status finishItemHandle(IScannedStore scannedStore, StoreItem storeItem);
 }
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/ITimerTaskStatusProvider.java b/common/source/java/ch/systemsx/cisd/common/utilities/ITimerTaskStatusProvider.java
index 380baa0a096..768b9c4b026 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/ITimerTaskStatusProvider.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/ITimerTaskStatusProvider.java
@@ -17,15 +17,29 @@
 package ch.systemsx.cisd.common.utilities;
 
 /**
- * A provider for status information about the last run of a {@link java.util.TimerTask}. 
- *
+ * A provider for status information about the last run of a {@link java.util.TimerTask}.
+ * 
  * @author Bernd Rinn
  */
 public interface ITimerTaskStatusProvider
 {
-    
+
+    /**
+     * Returns <code>true</code> if the last execution of the timer task found some useful work to
+     * do.
+     */
     public boolean hasPerformedMeaningfulWork();
-    
+
+    /**
+     * Returns <code>true</code> if during the last execution of the timer task errors have
+     * occurred.
+     */
     public boolean hasErrors();
 
+    /**
+     * Returns the error log of the last execution, or <code>null</code>, if no errors have
+     * occurred.
+     */
+    public String tryGetErrorLog();
+
 }
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkDirectoryScanningHandlerTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkDirectoryScanningHandlerTest.java
index be0762458c5..340ccdcb83c 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkDirectoryScanningHandlerTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkDirectoryScanningHandlerTest.java
@@ -18,6 +18,7 @@ package ch.systemsx.cisd.common.highwatermark;
 
 import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertNull;
 
 import java.io.File;
 import java.io.IOException;
@@ -34,6 +35,7 @@ import ch.systemsx.cisd.common.utilities.IDirectoryScanningHandler;
 import ch.systemsx.cisd.common.utilities.StoreItem;
 import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask.IScannedStore;
 import ch.systemsx.cisd.common.utilities.IDirectoryScanningHandler.HandleInstruction;
+import ch.systemsx.cisd.common.utilities.IDirectoryScanningHandler.HandleInstructionFlag;
 
 /**
  * Test cases for the {@link HighwaterMarkDirectoryScanningHandler}.
@@ -62,6 +64,7 @@ public final class HighwaterMarkDirectoryScanningHandlerTest
         context = new Mockery();
         directoryScanningHandler = context.mock(IDirectoryScanningHandler.class);
         scannedStore = context.mock(IScannedStore.class);
+        
         freeSpaceProvider = context.mock(IFreeSpaceProvider.class);
         highwaterMarkWatcher = new HighwaterMarkWatcher(HIGHWATER_MARK, freeSpaceProvider);
     }
@@ -135,9 +138,18 @@ public final class HighwaterMarkDirectoryScanningHandlerTest
                     }
                 }
             });
-        boolean mayHandle =
-                HandleInstruction.PROCESS.equals(scanningHandler.mayHandle(scannedStore, storeItem));
-        assertEquals(freeSpace > HIGHWATER_MARK, mayHandle);
+        final HandleInstruction instruction = scanningHandler.mayHandle(scannedStore, storeItem);
+        boolean mayHandleExpected = (freeSpace > HIGHWATER_MARK);
+        boolean mayHandleObserved = HandleInstructionFlag.PROCESS.equals(instruction.getFlag());
+        assertEquals(mayHandleExpected, mayHandleObserved);
+        if (mayHandleExpected)
+        {
+            assertNull(instruction.tryGetMessage());
+        } else
+        {
+            assertEquals("Not enough disk space on store 'iScannedStore'.", instruction
+                    .tryGetMessage());
+        }
         context.assertIsSatisfied();
     }
 
@@ -165,9 +177,18 @@ public final class HighwaterMarkDirectoryScanningHandlerTest
                     }
                 }
             });
-        boolean mayHandle =
-                HandleInstruction.PROCESS.equals(scanningHandler.mayHandle(scannedStore, storeItem));
-        assertEquals(freeSpace > HIGHWATER_MARK, mayHandle);
+        final HandleInstruction instruction = scanningHandler.mayHandle(scannedStore, storeItem);
+        boolean mayHandleExpected = (freeSpace > HIGHWATER_MARK);
+        boolean mayHandleObserved = HandleInstructionFlag.PROCESS.equals(instruction.getFlag());
+        assertEquals(mayHandleExpected, mayHandleObserved);
+        if (mayHandleExpected)
+        {
+            assertNull(instruction.tryGetMessage());
+        } else
+        {
+            assertEquals("Not enough disk space on store 'iScannedStore'.", instruction
+                    .tryGetMessage());
+        }
         context.assertIsSatisfied();
     }
 }
\ No newline at end of file
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTaskTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTaskTest.java
index e5ffbfe3a98..60b8dabecbb 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTaskTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTaskTest.java
@@ -30,6 +30,7 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import ch.systemsx.cisd.common.Constants;
 import ch.systemsx.cisd.common.collections.CollectionIO;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogInitializer;
@@ -113,7 +114,7 @@ public class DirectoryScanningTimerTaskTest
 
     private final static File getFaultyPathFile()
     {
-        return new File(workingDirectory, FaultyPathDirectoryScanningHandler.FAULTY_PATH_FILENAME);
+        return new File(workingDirectory, Constants.FAULTY_PATH_FILENAME);
     }
 
     private final static void createNewFile(final File someFile) throws IOException
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/FileScannedStore.java b/datamover/source/java/ch/systemsx/cisd/datamover/FileScannedStore.java
index 9dc1a4602f5..cace1e6369d 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/FileScannedStore.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/FileScannedStore.java
@@ -44,19 +44,6 @@ final class FileScannedStore implements IScannedStore
         this.storeItemFilter = storeItemFilter;
     }
 
-    private final StoreItem[] filterReadyToProcess(final StoreItem[] items)
-    {
-        final Vector<StoreItem> result = new Vector<StoreItem>();
-        for (final StoreItem item : items)
-        {
-            if (isReadyToProcess(item))
-            {
-                result.add(item);
-            }
-        }
-        return result.toArray(StoreItem.EMPTY_ARRAY);
-    }
-
     private final boolean isReadyToProcess(final StoreItem item)
     {
         if (item.getName().startsWith(Constants.DELETION_IN_PROGRESS_PREFIX))
@@ -81,7 +68,7 @@ final class FileScannedStore implements IScannedStore
         return fileStore.getLocationDescription(item);
     }
 
-    public final StoreItem[] tryListSortedReadyToProcess(final ISimpleLogger loggerOrNull)
+    public StoreItem[] tryListSorted(ISimpleLogger loggerOrNull)
     {
         // Older items will be handled before newer items. This becomes important when doing online
         // quality control of measurements.
@@ -90,7 +77,20 @@ final class FileScannedStore implements IScannedStore
         {
             return null;
         }
-        return filterReadyToProcess(items);
+        return items;
+    }
+
+    public final StoreItem[] filterReadyToProcess(final StoreItem[] items)
+    {
+        final Vector<StoreItem> result = new Vector<StoreItem>();
+        for (final StoreItem item : items)
+        {
+            if (isReadyToProcess(item))
+            {
+                result.add(item);
+            }
+        }
+        return result.toArray(StoreItem.EMPTY_ARRAY);
     }
 
     //
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/IncomingProcessor.java b/datamover/source/java/ch/systemsx/cisd/datamover/IncomingProcessor.java
index 28519602540..b572a34de52 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/IncomingProcessor.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/IncomingProcessor.java
@@ -84,7 +84,7 @@ public class IncomingProcessor implements IRecoverableTimerTaskFactory
     private final IStoreItemFilter storeItemFilter;
 
     private final String markerFileName;
-    
+
     private final String errorMarkerFileName;
 
     private final String successorMarkerFileName;
@@ -284,7 +284,8 @@ public class IncomingProcessor implements IRecoverableTimerTaskFactory
 
             for (final File file : files)
             {
-                if (MarkerFile.isDeletionInProgressMarker(file))
+                if (MarkerFile.isDeletionInProgressMarker(file)
+                        || MarkerFile.isFaultyPathsFile(file))
                 {
                     continue;
                 }
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/TimerTaskListenerForMarkerFileProtocol.java b/datamover/source/java/ch/systemsx/cisd/datamover/TimerTaskListenerForMarkerFileProtocol.java
index 5f1e838f7e4..f0f35891045 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/TimerTaskListenerForMarkerFileProtocol.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/TimerTaskListenerForMarkerFileProtocol.java
@@ -20,6 +20,7 @@ import java.io.File;
 import java.io.IOException;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
 
 import ch.systemsx.cisd.common.concurrent.DummyTimerTaskListener;
 import ch.systemsx.cisd.common.concurrent.ITimerTaskListener;
@@ -89,7 +90,7 @@ public class TimerTaskListenerForMarkerFileProtocol extends DummyTimerTaskListen
     @Override
     public void startRunning()
     {
-        touch(markerFile);
+        touch(markerFile, StringUtils.EMPTY);
     }
 
     /**
@@ -100,11 +101,17 @@ public class TimerTaskListenerForMarkerFileProtocol extends DummyTimerTaskListen
     {
         if (successorMarkerFileOrNull != null && hasPerformedMeaningfullWork(statusProviderOrNull))
         {
-            touch(successorMarkerFileOrNull);
+            touch(successorMarkerFileOrNull, StringUtils.EMPTY);
         }
-        if (errorMarkerFileOrNull != null && hasErrors(statusProviderOrNull))
+        if (errorMarkerFileOrNull != null)
         {
-            touch(errorMarkerFileOrNull);
+            if (hasErrors(statusProviderOrNull))
+            {
+                touch(errorMarkerFileOrNull, getErrorLog(statusProviderOrNull));
+            } else
+            {
+                errorMarkerFileOrNull.delete();
+            }
         }
         // Avoid deleting the marker file when it is used as error marker file, too, and an error
         // occurred.
@@ -125,6 +132,22 @@ public class TimerTaskListenerForMarkerFileProtocol extends DummyTimerTaskListen
         return (statusProviderOrNull != null) && statusProviderOrNull.hasErrors();
     }
 
+    private String getErrorLog(ITimerTaskStatusProvider statusProviderOrNull)
+    {
+        if (statusProviderOrNull == null)
+        {
+            return StringUtils.EMPTY;
+        }
+        final String errorLogOrNull = statusProviderOrNull.tryGetErrorLog();
+        if (errorLogOrNull == null)
+        {
+            return StringUtils.EMPTY;
+        } else
+        {
+            return errorLogOrNull;
+        }
+    }
+
     private static void failIfDirectory(File markerFile)
     {
         if (markerFile.isDirectory())
@@ -134,11 +157,12 @@ public class TimerTaskListenerForMarkerFileProtocol extends DummyTimerTaskListen
         }
     }
 
-    private static void touch(final File markerFile) throws EnvironmentFailureException
+    private static void touch(final File markerFile, String message)
+            throws EnvironmentFailureException
     {
         try
         {
-            FileUtils.touch(markerFile);
+            FileUtils.writeStringToFile(markerFile, message);
         } catch (IOException ex)
         {
             throw new EnvironmentFailureException("Can not create marker file '"
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/common/MarkerFile.java b/datamover/source/java/ch/systemsx/cisd/datamover/common/MarkerFile.java
index 3258cf92a05..b53fdea9df5 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/common/MarkerFile.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/common/MarkerFile.java
@@ -55,6 +55,13 @@ public class MarkerFile
         return file.getName().startsWith(Constants.DELETION_IN_PROGRESS_PREFIX);
     }
 
+    public static boolean isFaultyPathsFile(File file)
+    {
+        assert file != null;
+        
+        return Constants.FAULTY_PATH_FILENAME.equals(file.getName());
+    }
+
     public static File extractOriginalFromCopyFinishedMarker(File markerFile)
     {
         assert isCopyFinishedMarker(markerFile);
@@ -80,4 +87,5 @@ public class MarkerFile
     {
         return new StoreItem(".requiresDeletionBeforeCreation");
     }
+    
 }
-- 
GitLab