diff --git a/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/LinkMakerProvider.java b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/LinkMakerProvider.java
index 296e9cee8d49e1e45e872ca6eb9a63e09cdf3732..9944210aa2f3d5b025168cda5a3f8edb017cb748 100644
--- a/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/LinkMakerProvider.java
+++ b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/LinkMakerProvider.java
@@ -16,7 +16,7 @@
 
 package ch.systemsx.cisd.bds.storage.filesystem;
 
-import ch.systemsx.cisd.common.Constants;
+import ch.systemsx.cisd.common.TimingParameters;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
 import ch.systemsx.cisd.common.utilities.FastRecursiveHardLinkMaker;
 import ch.systemsx.cisd.common.utilities.IImmutableCopier;
@@ -31,8 +31,6 @@ public final class LinkMakerProvider
 
     private static final String NO_HARD_LINK_EXECUTABLE = "No hard link executable has been found.";
 
-    private static final int MAX_COPY_RETRIES = 7;
-
     private static IImmutableCopier hardLinkMaker;
 
     private LinkMakerProvider()
@@ -43,8 +41,7 @@ public final class LinkMakerProvider
     private final static IImmutableCopier tryCreateHardLinkMaker()
     {
         final IImmutableCopier copier =
-                FastRecursiveHardLinkMaker.tryCreate(Constants.MILLIS_TO_WAIT_BEFORE_TIMEOUT,
-                        MAX_COPY_RETRIES, Constants.MILLIS_TO_SLEEP_BEFORE_RETRYING);
+            FastRecursiveHardLinkMaker.tryCreate(TimingParameters.getDefaultParameters());
         if (copier != null)
         {
             return copier;
diff --git a/common/source/java/ch/systemsx/cisd/common/Constants.java b/common/source/java/ch/systemsx/cisd/common/Constants.java
index 31b7c2cdec3e98cea17e0ab8480003b94346ceac..a3ec8610c744c0830b910eb7e2c013893b9f885a 100644
--- a/common/source/java/ch/systemsx/cisd/common/Constants.java
+++ b/common/source/java/ch/systemsx/cisd/common/Constants.java
@@ -41,19 +41,31 @@ public final class Constants
     public static final String DELETION_IN_PROGRESS_PREFIX =
             MARKER_PREFIX + "deletion_in_progress_";
 
-    /** The number of milliseconds to wait before considering timeout (<i>10s</i>). */
-    public static final long MILLIS_TO_WAIT_BEFORE_TIMEOUT = 10 * DateUtils.MILLIS_PER_SECOND;
-
-    /** 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 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";
 
+    //
+    // Timing
+    //
+    
+    /** Time in seconds to wait before a timeout occurs. */
+    public static final int TIMEOUT_SECONDS = 60;
+
+    /** Time in milli-seconds to wait before a timeout occurs.. */
+    public static final long MILLIS_TO_WAIT_BEFORE_TIMEOUT =
+            TIMEOUT_SECONDS * DateUtils.MILLIS_PER_SECOND;
+
+    /** Time interval in seconds to wait after an operation failed before retrying it. */
+    public static final int INTERVAL_TO_WAIT_AFTER_FAILURE_SECONDS = 10;
+
+    /** Time interval in milli-seconds to wait after an operation failed before retrying it. */
+    public static final long MILLIS_TO_SLEEP_BEFORE_RETRYING =
+            INTERVAL_TO_WAIT_AFTER_FAILURE_SECONDS * DateUtils.MILLIS_PER_SECOND;
+
+    /** Maximal number of retries when an operation fails. */
+    public static final int MAXIMUM_RETRY_COUNT = 11;
+
 }
diff --git a/common/source/java/ch/systemsx/cisd/common/TimingParameters.java b/common/source/java/ch/systemsx/cisd/common/TimingParameters.java
new file mode 100644
index 0000000000000000000000000000000000000000..18935b6cde40c21274049886991fd09fee3361bd
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/TimingParameters.java
@@ -0,0 +1,258 @@
+/*
+ * 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.common;
+
+import java.util.Properties;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.time.DateUtils;
+
+import ch.systemsx.cisd.common.utilities.PropertyUtils;
+
+/**
+ * A class that provides timing parameters for operations that can timeout or otherwise fail.
+ * 
+ * @author Bernd Rinn
+ */
+public class TimingParameters
+{
+
+    private final static TimingParameters standardParameters =
+            new TimingParameters(Constants.MILLIS_TO_WAIT_BEFORE_TIMEOUT,
+                    Constants.MAXIMUM_RETRY_COUNT, Constants.MILLIS_TO_SLEEP_BEFORE_RETRYING);
+
+    private final static TimingParameters defaultParameters =
+            new TimingParameters(standardParameters);
+
+    private final static TimingParameters noTimeoutNoRetriesParameters =
+            new TimingParameters(Long.MAX_VALUE, 0, 0L);
+
+    /**
+     * Returns the standard timing parameters (may not be overwritten).
+     */
+    public static final TimingParameters getStandardParameters()
+    {
+        return standardParameters;
+    }
+
+    /**
+     * Returns the default timing parameters.
+     */
+    public static final TimingParameters getDefaultParameters()
+    {
+        return defaultParameters;
+    }
+
+    /**
+     * Returns timing parameters that corresponds to 'no timeout', 'no retries'.
+     */
+    public static final TimingParameters getNoTimeoutNoRetriesParameters()
+    {
+        return noTimeoutNoRetriesParameters;
+    }
+
+    public static final String TIMEOUT_PROPERTY = "timeout";
+
+    public static final String MAX_RETRY_PROPERTY = "max-retries";
+
+    public static final String FAILURE_INTERVAL = "failure-interval";
+
+    private volatile long timeoutMillis;
+
+    private volatile int maxRetriesOnFailure;
+
+    private volatile long intervalToWaitAfterFailureMillis;
+
+    public static TimingParameters create(final Properties properties)
+    {
+        return new TimingParameters(properties);
+    }
+
+    /**
+     * Returns <code>true</code> if the <var>properties</var> contain timing parameters and
+     * <code>false</code> otherwise.
+     */
+    public static boolean hasTimingParameters(Properties properties)
+    {
+        return properties.containsKey(TIMEOUT_PROPERTY)
+                || properties.containsKey(MAX_RETRY_PROPERTY)
+                || properties.containsKey(FAILURE_INTERVAL);
+    }
+
+    /**
+     * Sets the new default.
+     */
+    public static void setDefault(final long timeoutMillis, final int maxRetriesOnFailure,
+            final long intervalToWaitAfterFailureMillis)
+    {
+        defaultParameters.timeoutMillis = timeoutMillis;
+        defaultParameters.maxRetriesOnFailure = maxRetriesOnFailure;
+        defaultParameters.intervalToWaitAfterFailureMillis = intervalToWaitAfterFailureMillis;
+    }
+
+    /**
+     * Sets the new default.
+     */
+    public static void setDefault(final TimingParameters timingParameters)
+    {
+        defaultParameters.timeoutMillis = timingParameters.timeoutMillis;
+        defaultParameters.maxRetriesOnFailure = timingParameters.maxRetriesOnFailure;
+        defaultParameters.intervalToWaitAfterFailureMillis =
+                timingParameters.intervalToWaitAfterFailureMillis;
+    }
+
+    /**
+     * Sets the new default from the properties (or fall back to standard values, if
+     * <var>properties</var> do not contain values).
+     */
+    public static void setDefault(Properties properties)
+    {
+        defaultParameters.timeoutMillis = getTimeoutMillis(properties);
+        defaultParameters.maxRetriesOnFailure = getMaxRetriesOnFailure(properties);
+        defaultParameters.intervalToWaitAfterFailureMillis =
+                getIntervalToWaitAfterFailureMillis(properties);
+    }
+
+    private static long getIntervalToWaitAfterFailureMillis(Properties properties)
+    {
+        return PropertyUtils.getInt(properties, FAILURE_INTERVAL,
+                Constants.INTERVAL_TO_WAIT_AFTER_FAILURE_SECONDS)
+                * DateUtils.MILLIS_PER_SECOND;
+    }
+
+    private static int getMaxRetriesOnFailure(Properties properties)
+    {
+        return PropertyUtils.getInt(properties, MAX_RETRY_PROPERTY, Constants.MAXIMUM_RETRY_COUNT);
+    }
+
+    private static long getTimeoutMillis(Properties properties)
+    {
+        final long timeoutSpecified =
+                PropertyUtils.getInt(properties, TIMEOUT_PROPERTY, Constants.TIMEOUT_SECONDS)
+                        * DateUtils.MILLIS_PER_SECOND;
+        return (timeoutSpecified == 0) ? Long.MAX_VALUE : timeoutSpecified;
+    }
+
+    public static TimingParameters create(final long timeoutMillis, final int maxRetriesOnFailure,
+            final long intervalToWaitAfterFailureMillis)
+    {
+        return new TimingParameters(timeoutMillis, maxRetriesOnFailure,
+                intervalToWaitAfterFailureMillis);
+    }
+
+    public static TimingParameters create(final long timeoutMillis)
+    {
+        return new TimingParameters(timeoutMillis, Constants.MAXIMUM_RETRY_COUNT,
+                Constants.MILLIS_TO_SLEEP_BEFORE_RETRYING);
+    }
+
+    public static TimingParameters createNoRetries(final long timeoutMillis)
+    {
+        return new TimingParameters(timeoutMillis, 0, 0L);
+    }
+
+    private TimingParameters(TimingParameters parameters)
+    {
+        this.timeoutMillis = parameters.timeoutMillis;
+        this.maxRetriesOnFailure = parameters.maxRetriesOnFailure;
+        this.intervalToWaitAfterFailureMillis = parameters.intervalToWaitAfterFailureMillis;
+    }
+
+    private TimingParameters(final long timeoutMillis, final int maxRetriesOnFailure,
+            final long intervalToWaitAfterFailureMillis)
+    {
+        this.timeoutMillis = timeoutMillis;
+        this.maxRetriesOnFailure = maxRetriesOnFailure;
+        this.intervalToWaitAfterFailureMillis = intervalToWaitAfterFailureMillis;
+    }
+
+    private TimingParameters(Properties properties)
+    {
+        this.timeoutMillis = getTimeoutMillis(properties);
+        this.maxRetriesOnFailure = getMaxRetriesOnFailure(properties);
+        this.intervalToWaitAfterFailureMillis = getIntervalToWaitAfterFailureMillis(properties);
+    }
+
+    /**
+     * Returns the timeout (in milli-seconds).
+     */
+    public long getTimeoutMillis()
+    {
+        return timeoutMillis;
+    }
+
+    /**
+     * Returns the maximal number of retries when an operation fails.
+     */
+    public int getMaxRetriesOnFailure()
+    {
+        return maxRetriesOnFailure;
+    }
+
+    /**
+     * Returns the time (in milli-seconds) to wait after a failure has occurred and before retrying
+     * the operation.
+     */
+    public long getIntervalToWaitAfterFailureMillis()
+    {
+        return intervalToWaitAfterFailureMillis;
+    }
+
+    //
+    // Object
+    //
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (obj == this)
+        {
+            return true;
+        }
+        if (obj instanceof TimingParameters == false)
+        {
+            return false;
+        }
+        final TimingParameters that = (TimingParameters) obj;
+        final EqualsBuilder builder = new EqualsBuilder();
+        builder.append(this.timeoutMillis, that.timeoutMillis);
+        builder.append(this.maxRetriesOnFailure, that.maxRetriesOnFailure);
+        builder
+                .append(this.intervalToWaitAfterFailureMillis,
+                        that.intervalToWaitAfterFailureMillis);
+        return builder.isEquals();
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final HashCodeBuilder builder = new HashCodeBuilder();
+        builder.append(timeoutMillis);
+        builder.append(maxRetriesOnFailure);
+        builder.append(intervalToWaitAfterFailureMillis);
+        return builder.toHashCode();
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("Timing: timeout: %d s, maximal retries: %d, sleep on failure: %d s",
+                timeoutMillis / 1000, maxRetriesOnFailure, intervalToWaitAfterFailureMillis / 1000);
+    }
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/concurrent/ConcurrencyUtilities.java b/common/source/java/ch/systemsx/cisd/common/concurrent/ConcurrencyUtilities.java
index 464b8e3e8512b4305237d3b857d7a41ed53903ba..659f8093390f0ffd12770d24b9275ae10f991c1a 100644
--- a/common/source/java/ch/systemsx/cisd/common/concurrent/ConcurrencyUtilities.java
+++ b/common/source/java/ch/systemsx/cisd/common/concurrent/ConcurrencyUtilities.java
@@ -43,6 +43,27 @@ public final class ConcurrencyUtilities
     /** Corresponds to an immediate timeout when waiting for the future. */
     public static final long IMMEDIATE_TIMEOUT = 0L;
 
+    /**
+     * Provides settings for error logging.
+     */
+    public interface ILogSettings
+    {
+        /**
+         * Returns the logger to be used for errors.
+         */
+        ISimpleLogger getLogger();
+
+        /**
+         * Returns the operation name to be used in error logging.
+         */
+        String getOperationName();
+
+        /**
+         * Returns the log level to be used in error logging.
+         */
+        LogLevel getLogLevelForError();
+    }
+
     /**
      * Tries to get the result of a <var>future</var>, maximally waiting <var>timeoutMillis</var>
      * for the result to become available. Any {@link ExecutionException} that might occur in the
@@ -57,7 +78,7 @@ public final class ConcurrencyUtilities
      */
     public static <T> T tryGetResult(Future<T> future, long timeoutMillis) throws StopException
     {
-        return tryGetResult(future, timeoutMillis, null, null, true);
+        return tryGetResult(future, timeoutMillis, null, true);
     }
 
     /**
@@ -76,7 +97,7 @@ public final class ConcurrencyUtilities
     public static <T> T tryGetResult(Future<T> future, long timeoutMillis, boolean stopOnInterrupt)
             throws StopException
     {
-        return tryGetResult(future, timeoutMillis, null, null, stopOnInterrupt);
+        return tryGetResult(future, timeoutMillis, null, stopOnInterrupt);
     }
 
     /**
@@ -88,12 +109,10 @@ public final class ConcurrencyUtilities
      * @param future The future representing the execution to wait for.
      * @param timeoutMillis The time-out (in milliseconds) to wait for the execution to finish. If
      *            it is smaller than 0, no time-out will apply.
-     * @param loggerOrNull The logger to use for logging note-worthy information, or
-     *            <code>null</code>, if nothing should be logged.
-     * @param operationNameOrNull The name of the operation performed, for log messages, or
-     *            <code>null</code>, if it is not known or deemed unimportant.
-     * @param stopOnInterrupt If <code>true</code>, throw a {@link StopException} if the thread
-     *            gets interrupted while waiting on the future.
+     * @param logSettingsOrNull The settings for error logging, or <code>null</code>, if error
+     *            conditions should not be logged.
+     * @param stopOnInterrupt If <code>true</code>, throw a {@link StopException} if the thread gets
+     *            interrupted while waiting on the future.
      * @return The result of the future, or <code>null</code>, if the result does not become
      *         available within <var>timeoutMillis</var> ms or if the waiting thread gets
      *         interrupted.
@@ -101,11 +120,9 @@ public final class ConcurrencyUtilities
      *             <code>true</code>.
      */
     public static <T> T tryGetResult(Future<T> future, long timeoutMillis,
-            ISimpleLogger loggerOrNull, String operationNameOrNull, boolean stopOnInterrupt)
-            throws StopException
+            ILogSettings logSettingsOrNull, boolean stopOnInterrupt) throws StopException
     {
-        final ExecutionResult<T> result =
-                getResult(future, timeoutMillis, loggerOrNull, operationNameOrNull);
+        final ExecutionResult<T> result = getResult(future, timeoutMillis, logSettingsOrNull);
         return tryDealWithResult(result, stopOnInterrupt);
     }
 
@@ -129,12 +146,11 @@ public final class ConcurrencyUtilities
      * with the deviant cases yourself, then call this method to deal with the rest.
      * 
      * @param result A
-     * @param stopOnInterrupt If <code>true</code>, throw a {@link StopException} if the thread
-     *            gets interrupted while waiting on the future.
-     * @return The value of the <var>result</var> of the future, or <code>null</code>, if the
-     *         result status is {@link ExecutionStatus#TIMED_OUT} or
-     *         {@link ExecutionStatus#INTERRUPTED} and <var>stopOnInterrupt</var> is
-     *         <code>false</code>.
+     * @param stopOnInterrupt If <code>true</code>, throw a {@link StopException} if the thread gets
+     *            interrupted while waiting on the future.
+     * @return The value of the <var>result</var> of the future, or <code>null</code>, if the result
+     *         status is {@link ExecutionStatus#TIMED_OUT} or {@link ExecutionStatus#INTERRUPTED}
+     *         and <var>stopOnInterrupt</var> is <code>false</code>.
      * @throws StopException If the thread got interrupted and <var>stopOnInterrupt</var> is
      *             <code>true</code>.
      * @throws RuntimeException If the result status is {@link ExecutionStatus#EXCEPTION} and the
@@ -180,17 +196,16 @@ public final class ConcurrencyUtilities
     }
 
     /**
-     * Returns the result of a <var>future</var>, maximally waiting <var>timeoutMillis</var> for
-     * the result to become available. The return value is never <code>null</code>, but always a
+     * Returns the result of a <var>future</var>, maximally waiting <var>timeoutMillis</var> for the
+     * result to become available. The return value is never <code>null</code>, but always a
      * {@link ExecutionResult} that describes the outcome of the execution. The possible outcomes
      * are:
      * <ul>
      * <li> {@link ExecutionStatus#COMPLETE}: The execution has been performed correctly and a
-     * result is available, if provided.</li>
-     * <li> {@link ExecutionStatus#EXCEPTION}: The execution has been terminated by an exception.</li>
-     * <li> {@link ExecutionStatus#TIMED_OUT}: The execution timed out.</li>
-     * <li> {@link ExecutionStatus#INTERRUPTED}: The thread of the execution was interrupted (see
-     * {@link Thread#interrupt()}).</li>
+     * result is available, if provided.</li> <li> {@link ExecutionStatus#EXCEPTION}: The execution
+     * has been terminated by an exception.</li> <li> {@link ExecutionStatus#TIMED_OUT}: The
+     * execution timed out.</li> <li> {@link ExecutionStatus#INTERRUPTED}: The thread of the
+     * execution was interrupted (see {@link Thread#interrupt()}).</li>
      * </ul>
      * 
      * @param future The future representing the execution to wait for.
@@ -201,51 +216,81 @@ public final class ConcurrencyUtilities
      */
     public static <T> ExecutionResult<T> getResult(Future<T> future, long timeoutMillis)
     {
-        return getResult(future, timeoutMillis, null, null);
+        return getResult(future, timeoutMillis, null);
+    }
+
+    /**
+     * Returns the result of a <var>future</var>, maximally waiting <var>timeoutMillis</var> for the
+     * result to become available. The return value is never <code>null</code>, but always a
+     * {@link ExecutionResult} that describes the outcome of the execution. The possible outcomes
+     * are:
+     * <ul>
+     * <li> {@link ExecutionStatus#COMPLETE}: The execution has been performed correctly and a
+     * result is available, if provided.</li> <li> {@link ExecutionStatus#EXCEPTION}: The execution
+     * has been terminated by an exception.</li> <li> {@link ExecutionStatus#TIMED_OUT}: The
+     * execution timed out.</li> <li> {@link ExecutionStatus#INTERRUPTED}: The thread of the
+     * execution was interrupted (see {@link Thread#interrupt()}).</li>
+     * </ul>
+     * 
+     * @param future The future representing the execution to wait for.
+     * @param timeoutMillis The time-out (in milliseconds) to wait for the execution to finish. If
+     *            it is smaller than 0, no time-out will apply.
+     * @param logSettingsOrNull The settings for error logging, or <code>null</code>, if error
+     *            conditions should not be logged.
+     * @return The {@link ExecutionResult} of the <var>future</var>. May correspond to each one of
+     *         the {@link ExecutionStatus} values.
+     */
+    public static <T> ExecutionResult<T> getResult(Future<T> future, long timeoutMillis,
+            ILogSettings logSettingsOrNull)
+    {
+        return getResult(future, timeoutMillis, true, logSettingsOrNull);
     }
 
     /**
-     * Returns the result of a <var>future</var>, maximally waiting <var>timeoutMillis</var> for
-     * the result to become available. The return value is never <code>null</code>, but always a
+     * Returns the result of a <var>future</var>, at most waiting <var>timeoutMillis</var> for the
+     * result to become available. The return value is never <code>null</code>, but always a
      * {@link ExecutionResult} that describes the outcome of the execution. The possible outcomes
      * are:
      * <ul>
      * <li> {@link ExecutionStatus#COMPLETE}: The execution has been performed correctly and a
-     * result is available, if provided.</li>
-     * <li> {@link ExecutionStatus#EXCEPTION}: The execution has been terminated by an exception.</li>
-     * <li> {@link ExecutionStatus#TIMED_OUT}: The execution timed out.</li>
-     * <li> {@link ExecutionStatus#INTERRUPTED}: The thread of the execution was interrupted (see
-     * {@link Thread#interrupt()}).</li>
+     * result is available, if provided.</li> <li> {@link ExecutionStatus#EXCEPTION}: The execution
+     * has been terminated by an exception.</li> <li> {@link ExecutionStatus#TIMED_OUT}: The
+     * execution timed out.</li> <li> {@link ExecutionStatus#INTERRUPTED}: The thread of the
+     * execution was interrupted (see {@link Thread#interrupt()}).</li>
      * </ul>
      * 
      * @param future The future representing the execution to wait for.
      * @param timeoutMillis The time-out (in milliseconds) to wait for the execution to finish. If
      *            it is smaller than 0, no time-out will apply.
-     * @param loggerOrNull The logger to use for logging note-worthy information, or
-     *            <code>null</code>, if nothing should be logged.
-     * @param operationNameOrNull The name of the operation performed, for log messages, or
-     *            <code>null</code>, if it is not known or deemed unimportant.
+     * @param cancelOnTimeout If <code>true</code>, the <var>future</var> will be canceled on
+     *            time-out.
+     * @param logSettingsOrNull The settings for error logging, or <code>null</code>, if error
+     *            conditions should not be logged.
      * @return The {@link ExecutionResult} of the <var>future</var>. May correspond to each one of
      *         the {@link ExecutionStatus} values.
      */
     public static <T> ExecutionResult<T> getResult(Future<T> future, long timeoutMillis,
-            ISimpleLogger loggerOrNull, String operationNameOrNull)
+            boolean cancelOnTimeout, ILogSettings logSettingsOrNull)
+    {
+        return getResult(future, timeoutMillis, cancelOnTimeout, logSettingsOrNull, null);
+    }
+
+    private static boolean isActive(IActivitySensor sensorOrNull, long timeoutMillis)
     {
-        return getResult(future, timeoutMillis, true, loggerOrNull, operationNameOrNull);
+        return (sensorOrNull != null) && sensorOrNull.hasActivityMoreRecentThan(timeoutMillis);
     }
 
     /**
-     * Returns the result of a <var>future</var>, maximally waiting <var>timeoutMillis</var> for
-     * the result to become available. The return value is never <code>null</code>, but always a
+     * Returns the result of a <var>future</var>, at most waiting <var>timeoutMillis</var> for the
+     * result to become available. The return value is never <code>null</code>, but always a
      * {@link ExecutionResult} that describes the outcome of the execution. The possible outcomes
      * are:
      * <ul>
      * <li> {@link ExecutionStatus#COMPLETE}: The execution has been performed correctly and a
-     * result is available, if provided.</li>
-     * <li> {@link ExecutionStatus#EXCEPTION}: The execution has been terminated by an exception.</li>
-     * <li> {@link ExecutionStatus#TIMED_OUT}: The execution timed out.</li>
-     * <li> {@link ExecutionStatus#INTERRUPTED}: The thread of the execution was interrupted (see
-     * {@link Thread#interrupt()}).</li>
+     * result is available, if provided.</li> <li> {@link ExecutionStatus#EXCEPTION}: The execution
+     * has been terminated by an exception.</li> <li> {@link ExecutionStatus#TIMED_OUT}: The
+     * execution timed out.</li> <li> {@link ExecutionStatus#INTERRUPTED}: The thread of the
+     * execution was interrupted (see {@link Thread#interrupt()}).</li>
      * </ul>
      * 
      * @param future The future representing the execution to wait for.
@@ -253,65 +298,98 @@ public final class ConcurrencyUtilities
      *            it is smaller than 0, no time-out will apply.
      * @param cancelOnTimeout If <code>true</code>, the <var>future</var> will be canceled on
      *            time-out.
-     * @param loggerOrNull The logger to use for logging note-worthy information, or
-     *            <code>null</code>, if nothing should be logged.
-     * @param operationNameOrNull The name of the operation performed, for log messages, or
-     *            <code>null</code>, if it is not known or deemed unimportant.
+     * @param logSettingsOrNull The settings for error logging, or <code>null</code>, if error
+     *            conditions should not be logged.
+     * @param sensorOrNull A sensor that can prevent the method from timing out by showing activity.
      * @return The {@link ExecutionResult} of the <var>future</var>. May correspond to each one of
      *         the {@link ExecutionStatus} values.
      */
     public static <T> ExecutionResult<T> getResult(Future<T> future, long timeoutMillis,
-            boolean cancelOnTimeout, ISimpleLogger loggerOrNull, String operationNameOrNull)
+            boolean cancelOnTimeout, ILogSettings logSettingsOrNull, IActivitySensor sensorOrNull)
     {
-        final String operationName =
-                (operationNameOrNull == null) ? "UNKNOWN" : operationNameOrNull;
         try
         {
-            return ExecutionResult.create(future.get(transform(timeoutMillis),
-                    TimeUnit.MILLISECONDS));
-        } catch (TimeoutException ex)
+            ExecutionResult<T> result = null;
+            do
+            {
+                try
+                {
+                    result = ExecutionResult.create(future.get(transform(timeoutMillis),
+                                    TimeUnit.MILLISECONDS));
+                } catch (TimeoutException ex)
+                {
+                    // result is still null
+                }
+            } while (result == null && isActive(sensorOrNull, timeoutMillis));
+            if (result == null)
+            {
+                if (cancelOnTimeout)
+                {
+                    future.cancel(true);
+                }
+                if (logSettingsOrNull != null)
+                {
+                    logSettingsOrNull.getLogger().log(
+                            logSettingsOrNull.getLogLevelForError(),
+                            String.format("%s: timeout of %.2f s exceeded%s.", logSettingsOrNull
+                                    .getOperationName(), timeoutMillis / 1000f,
+                                    cancelOnTimeout ? ", cancelled" : ""));
+                }
+                return ExecutionResult.createTimedOut();
+            } else
+            {
+                return result;
+            }
+        } catch (InterruptedException ex)
         {
-            if (cancelOnTimeout)
+            future.cancel(true);
+            if (logSettingsOrNull != null)
             {
-                future.cancel(true);
+                logSettingsOrNull.getLogger().log(logSettingsOrNull.getLogLevelForError(),
+                        String.format("%s: interrupted.", logSettingsOrNull.getOperationName()));
             }
-            if (loggerOrNull != null)
+            return ExecutionResult.createInterrupted();
+        } catch (StopException ex)
+        {
+            // Happens when Thread.stop(new StopException()) is called.
+            future.cancel(true);
+            if (logSettingsOrNull != null)
             {
-                loggerOrNull.log(LogLevel.DEBUG, String.format(
-                        "%s took longer than %f s, cancelled.", operationName,
-                        timeoutMillis / 1000f));
+                logSettingsOrNull.getLogger().log(logSettingsOrNull.getLogLevelForError(),
+                        String.format("%s: stopped.", logSettingsOrNull.getOperationName()));
             }
-            return ExecutionResult.createTimedOut();
-        } catch (InterruptedException ex)
+            return ExecutionResult.createInterrupted();
+        } catch (ThreadDeath ex)
         {
             future.cancel(true);
-            if (loggerOrNull != null)
+            if (logSettingsOrNull != null)
             {
-                loggerOrNull.log(LogLevel.DEBUG, String
-                        .format("%s got interrupted.", operationName));
+                logSettingsOrNull.getLogger().log(logSettingsOrNull.getLogLevelForError(),
+                        String.format("%s: stopped.", logSettingsOrNull.getOperationName()));
             }
             return ExecutionResult.createInterrupted();
         } catch (CancellationException ex)
         {
-            if (loggerOrNull != null)
+            if (logSettingsOrNull != null)
             {
-                loggerOrNull.log(LogLevel.DEBUG, String
-                        .format("%s got cancelled.", operationName));
+                logSettingsOrNull.getLogger().log(logSettingsOrNull.getLogLevelForError(),
+                        String.format("%s: cancelled.", logSettingsOrNull.getOperationName()));
             }
-            // We treat cancelled the same as interrupted.
             return ExecutionResult.createInterrupted();
         } catch (ExecutionException ex)
         {
             final Throwable cause = ex.getCause();
-            if (loggerOrNull != null)
+            if (logSettingsOrNull != null)
             {
                 final String message =
                         (cause == null || cause.getMessage() == null) ? "<no message>" : cause
                                 .getMessage();
                 final String className =
                         (cause == null) ? "<unknown class>" : cause.getClass().getSimpleName();
-                loggerOrNull.log(LogLevel.ERROR, String.format(
-                        "%s has caused an exception: %s [%s]", operationName, message, className));
+                logSettingsOrNull.getLogger().log(
+                        logSettingsOrNull.getLogLevelForError(),
+                        String.format("%s: exception: %s [%s].", logSettingsOrNull
+                                .getOperationName(), message, className));
             }
             return ExecutionResult.createExceptional(cause == null ? ex : cause);
         }
diff --git a/common/source/java/ch/systemsx/cisd/common/concurrent/IActivityObserver.java b/common/source/java/ch/systemsx/cisd/common/concurrent/IActivityObserver.java
new file mode 100644
index 0000000000000000000000000000000000000000..3713fa5820deb6740a82c9f12fb30b7ff014405a
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/concurrent/IActivityObserver.java
@@ -0,0 +1,32 @@
+/*
+ * 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.common.concurrent;
+
+import java.util.Observer;
+
+/**
+ * A simple role that can observe activity. It is a simple version of {@link Observer} that can only
+ * observes the fact that the {@link #update()} method has been called.
+ * 
+ * @author Bernd Rinn
+ */
+public interface IActivityObserver
+{
+    /**
+     * Called when activity occurred.
+     */
+    public void update();
+}
\ No newline at end of file
diff --git a/common/source/java/ch/systemsx/cisd/common/concurrent/IActivitySensor.java b/common/source/java/ch/systemsx/cisd/common/concurrent/IActivitySensor.java
new file mode 100644
index 0000000000000000000000000000000000000000..ca32fe5a0c1cbbb1bfa2140d3f6f3c4f16877fee
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/concurrent/IActivitySensor.java
@@ -0,0 +1,47 @@
+/*
+ * 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.common.concurrent;
+
+/**
+ * A role that can provide activity information.
+ * 
+ * @author Bernd Rinn
+ */
+public interface IActivitySensor
+{
+    /**
+     * Returns the time of last activity on this sensor in milli-seconds since start of the epoch.
+     * <p>
+     * Callers should expect that a call of this method may be expensive and thus only call it when
+     * necessary. However, the caller can expect the information returned by is to be up-to-date.
+     * 
+     * @param thresholdMillis The time threshold for activity (in milli-seconds) that qualifies as
+     *            "recent enough" to terminate the search for even more recent activity. An
+     *            implementation can safely ignore this, as it is just meant for the sensor to
+     *            optimize its search. A value <code>thresholdMillis=0</code> translates into
+     *            "The absolutely most recent activity".
+     */
+    long getLastActivityMillisMoreRecentThan(long thresholdMillis);
+
+    /**
+     * Returns <code>true</code>, if this sensor has had activity more recent than
+     * <var>thresholdMillis</var> milli-seconds ago.
+     * 
+     * @param thresholdMillis The time threshold for activity (in milli-seconds) that qualifies as
+     *            "recent enough" to terminate the search for even more recent activity.
+     */
+    boolean hasActivityMoreRecentThan(long thresholdMillis);
+}
\ No newline at end of file
diff --git a/common/source/java/ch/systemsx/cisd/common/concurrent/InactivityMonitor.java b/common/source/java/ch/systemsx/cisd/common/concurrent/InactivityMonitor.java
index 194245ae4a7ba07daa54efa03bf85ed6ca5e8a9f..95863da93980275e8e86caf16735c947bd6bf0e9 100644
--- a/common/source/java/ch/systemsx/cisd/common/concurrent/InactivityMonitor.java
+++ b/common/source/java/ch/systemsx/cisd/common/concurrent/InactivityMonitor.java
@@ -40,26 +40,13 @@ public class InactivityMonitor
     /**
      * The sensor to get activity information from.
      */
-    public interface IActivitySensor
+    public interface IDescribingActivitySensor extends IActivitySensor
     {
-        /**
-         * Returns the time of last activity in milli-seconds since start of the epoch.
-         * <p>
-         * The monitor assumes that a call of this method may be expensive and thus only calls it
-         * when necessary. However, the information returned is expected to be up-to-date.
-         * 
-         * @param thresholdMillis The time threshold for activity (in milli-seconds) that qualifies
-         *            as "recent enough" to terminate the search for even more recent activity. An
-         *            implementation can safely ignore this, as it is just meant for the sensor to
-         *            optimize its search.
-         */
-        long getTimeOfLastActivityMoreRecentThan(long thresholdMillis);
-
         /**
          * Returns a string that describes the kind (and possibly reason) of recent inactivity.
          * <p>
          * Used for log messages. It can generally be assumed that this method is called after
-         * {@link #getTimeOfLastActivityMoreRecentThan(long)}.
+         * {@link #getLastActivityMillisMoreRecentThan(long)}.
          * 
          * @param now The current time as it should be used in the description.
          */
@@ -83,7 +70,7 @@ public class InactivityMonitor
         void update(long inactiveSinceMillis, String descriptionOfInactivity);
     }
 
-    private final IActivitySensor sensor;
+    private final IDescribingActivitySensor sensor;
 
     private final IInactivityObserver observer;
 
@@ -102,11 +89,11 @@ public class InactivityMonitor
      * @param observer The observer to inform when the inactivity threshold has been exceeded.
      * @param inactivityThresholdMillis The threshold of a period of inactivity that needs to be
      *            exceeded before the inactivity observer gets informed.
-     * @param stopAfterFirstEvent If <code>true</code>, the monitor will stop itself after the
-     *            first event of exceeded inactivity threshold has happened, otherwise, the monitor
-     *            will continue to look for such events.
+     * @param stopAfterFirstEvent If <code>true</code>, the monitor will stop itself after the first
+     *            event of exceeded inactivity threshold has happened, otherwise, the monitor will
+     *            continue to look for such events.
      */
-    public InactivityMonitor(IActivitySensor sensor, IInactivityObserver observer,
+    public InactivityMonitor(IDescribingActivitySensor sensor, IInactivityObserver observer,
             long inactivityThresholdMillis, boolean stopAfterFirstEvent)
     {
         assert sensor != null;
@@ -162,7 +149,7 @@ public class InactivityMonitor
         private void updateTimeOfActivity()
         {
             timeOfLastActivity =
-                    sensor.getTimeOfLastActivityMoreRecentThan(inactivityThresholdMillis);
+                    sensor.getLastActivityMillisMoreRecentThan(inactivityThresholdMillis);
         }
 
         private boolean isInactivityThresholdExceeded(final long now)
diff --git a/common/source/java/ch/systemsx/cisd/common/concurrent/MonitoringProxy.java b/common/source/java/ch/systemsx/cisd/common/concurrent/MonitoringProxy.java
index 8135299e2e7d28b9bece1c04f9b1dedc9e6489db..e93916cf6af48f4569d13ab61426bb9156b897e6 100644
--- a/common/source/java/ch/systemsx/cisd/common/concurrent/MonitoringProxy.java
+++ b/common/source/java/ch/systemsx/cisd/common/concurrent/MonitoringProxy.java
@@ -20,22 +20,38 @@ import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 
+import ch.systemsx.cisd.common.TimingParameters;
+import ch.systemsx.cisd.common.concurrent.ConcurrencyUtilities.ILogSettings;
 import ch.systemsx.cisd.common.exceptions.StopException;
 import ch.systemsx.cisd.common.exceptions.TimeoutException;
+import ch.systemsx.cisd.common.logging.ISimpleLogger;
+import ch.systemsx.cisd.common.logging.LogLevel;
 
 /**
  * A class that can provide a dynamic {@link Proxy} for an interface that delegates the method
  * invocations to an implementation of this interface, monitoring for calls to
- * {@link Thread#interrupt()} and for timeouts. (Note that by default no timeout is set.)
+ * {@link Thread#interrupt()} and for timeouts and allowing the invocation to be retried if it
+ * failed. (Note that by default no timeout is set and no retrying of failed operations is
+ * performed.)
  * <p>
  * On calls to {@link Thread#interrupt()} the proxy will throw a {@link StopException}, on timeouts
  * a {@link TimeoutException}.
  * <p>
+ * Retrying failed invocations is enabled by calling {@link #timing(TimingParameters)} with a retry
+ * parameter greater than 0. You need to carefully consider whether the methods in the interface are
+ * suitable for retrying or not. Note that there is no retrying on thread interruption, but only on
+ * timeout. You can configure a 'white-list' of exception classes that are suitable for retrying the
+ * operation. By default this white-list is empty, thus method invocations failing with an exception
+ * will not be retried.
+ * <p>
  * The proxy can be configured by chaining. If all options have been set, the actual proxy can be
  * obtained by calling {@link #get()}. In order to e.g. set the timeout to 10s, the following call
  * chain can be used:
@@ -44,9 +60,9 @@ import ch.systemsx.cisd.common.exceptions.TimeoutException;
  * If proxy = MonitoringProxy.create(If.class, someInstance).timeoutMillis(10000L).get();
  * </pre>
  * 
- * Instead of throwing a exception, the proxy can also be configured to provide special error values
- * on error conditions. This configuration is done by {@link #errorValueOnInterrupt()} for thread
- * interrupts and {@link #errorValueOnTimeout()} for timeouts.
+ * Instead of throwing an exception, the proxy can also be configured to provide special error
+ * values on error conditions. This configuration is done by {@link #errorValueOnInterrupt()} for
+ * thread interrupts and {@link #errorValueOnTimeout()} for timeouts.
  * <p>
  * The error return values can be set, either for the type of the return value of a method, or by
  * the method itself. If present, the specific method-value mapping will take precedence of the
@@ -58,14 +74,25 @@ import ch.systemsx.cisd.common.exceptions.TimeoutException;
  *         MonitoringProxy.create(If.class, someInstance).errorValueOnInterrupt()
  *                 .errorTypeValueMapping(String.class, &quot;ERROR&quot;).get();
  * </pre>
+ * <p>
+ * <i>Note:</i> A MonitoringProxy object can only be used safely from more than one thread if
+ * <ol>
+ * <li>The proxied object is thread-safe
+ * <li>
+ * <li>No observer / sensor pattern is used to detect activity (can produce "false negatives" on
+ * hanging method calls)
+ * <li>
+ * </ol>
  * 
  * @author Bernd Rinn
  */
 public class MonitoringProxy<T>
 {
+    private final static int NUMBER_OF_CORE_THREADS = 10;
 
     private final static ExecutorService executor =
-            new NamingThreadPoolExecutor("Monitoring Proxy").daemonize();
+            new NamingThreadPoolExecutor("Monitoring Proxy").corePoolSize(NUMBER_OF_CORE_THREADS)
+                    .daemonize();
 
     private final DelegatingInvocationHandler<T> delegate;
 
@@ -73,11 +100,13 @@ public class MonitoringProxy<T>
 
     private final Map<Method, Object> errorMethodValueMap;
 
+    private final Set<Class<? extends Exception>> exceptionClassesSuitableForRetrying;
+
     private final MonitoringInvocationHandler handler;
 
     private final T proxy;
 
-    private long timeoutMillis;
+    private TimingParameters timingParameters;
 
     private boolean errorValueOnTimeout;
 
@@ -85,9 +114,12 @@ public class MonitoringProxy<T>
 
     private String nameOrNull;
 
+    private ISimpleLogger loggerOrNull;
+
+    private IActivitySensor sensorOrNull;
+
     private static class DelegatingInvocationHandler<T> implements InvocationHandler
     {
-
         private final T objectToProxyFor;
 
         private DelegatingInvocationHandler(T objectToProxyFor)
@@ -96,10 +128,18 @@ public class MonitoringProxy<T>
         }
 
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+
         {
             try
             {
-                return method.invoke(objectToProxyFor, args);
+                try
+                {
+                    return method.invoke(objectToProxyFor, args);
+                } catch (IllegalAccessException ex)
+                {
+                    method.setAccessible(true);
+                    return method.invoke(objectToProxyFor, args);
+                }
             } catch (InvocationTargetException ex)
             {
                 throw ex.getTargetException();
@@ -153,6 +193,64 @@ public class MonitoringProxy<T>
 
         public Object invoke(final Object myProxy, final Method method, final Object[] args)
                 throws Throwable
+        {
+            final ExecutionResult<Object> result = retryingExecuteInThread(myProxy, method, args);
+            if (result.getStatus() == ExecutionStatus.TIMED_OUT)
+            {
+                if (errorValueOnTimeout == false)
+                {
+                    throw new TimeoutException(describe(method) + " timed out (timeout="
+                            + timingParameters.getTimeoutMillis() + "ms).");
+                }
+                return getErrorValue(method);
+            }
+            if (result.getStatus() == ExecutionStatus.INTERRUPTED && errorValueOnInterrupt)
+            {
+                return getErrorValue(method);
+            }
+            return ConcurrencyUtilities.tryDealWithResult(result);
+        }
+
+        private ExecutionResult<Object> retryingExecuteInThread(final Object myProxy,
+                final Method method, final Object[] args)
+        {
+            int counter = 0;
+            ExecutionResult<Object> result = null;
+            do
+            {
+                result = executeInThread(myProxy, method, args);
+                if (result.getStatus() == ExecutionStatus.COMPLETE
+                        || result.getStatus() == ExecutionStatus.INTERRUPTED)
+                {
+                    break;
+                }
+                if (exceptionStatusUnsuitableForRetry(result))
+                {
+                    break;
+                }
+                if (counter > 0 && timingParameters.getIntervalToWaitAfterFailureMillis() > 0)
+                {
+                    try
+                    {
+                        Thread.sleep(timingParameters.getIntervalToWaitAfterFailureMillis());
+                    } catch (InterruptedException ex)
+                    {
+                        return ExecutionResult.createInterrupted();
+                    }
+                }
+            } while (counter++ < timingParameters.getMaxRetriesOnFailure());
+            return result;
+        }
+
+        private boolean exceptionStatusUnsuitableForRetry(ExecutionResult<Object> result)
+        {
+            return (result.getStatus() == ExecutionStatus.EXCEPTION)
+                    && exceptionClassesSuitableForRetrying.contains(result.tryGetException()
+                            .getClass()) == false;
+        }
+
+        private ExecutionResult<Object> executeInThread(final Object myProxy, final Method method,
+                final Object[] args)
         {
             final String callingThreadName = Thread.currentThread().getName();
             final Future<Object> future = executor.submit(new NamedCallable<Object>()
@@ -185,22 +283,32 @@ public class MonitoringProxy<T>
                         }
                     }
                 });
-            final ExecutionResult<Object> result =
-                    ConcurrencyUtilities.getResult(future, timeoutMillis);
-            if (result.getStatus() == ExecutionStatus.TIMED_OUT)
-            {
-                if (errorValueOnTimeout == false)
-                {
-                    throw new TimeoutException(describe(method) + " timed out (timeout="
-                            + timeoutMillis + "ms).");
-                }
-                return getErrorValue(method);
-            }
-            if (result.getStatus() == ExecutionStatus.INTERRUPTED && errorValueOnInterrupt)
-            {
-                return getErrorValue(method);
-            }
-            return ConcurrencyUtilities.tryDealWithResult(result);
+            final ILogSettings logSettingsOrNull =
+                    (loggerOrNull == null) ? null : new ILogSettings()
+                        {
+                            public LogLevel getLogLevelForError()
+                            {
+                                return LogLevel.ERROR;
+                            }
+
+                            public ISimpleLogger getLogger()
+                            {
+                                return loggerOrNull;
+                            }
+
+                            public String getOperationName()
+                            {
+                                if (nameOrNull != null)
+                                {
+                                    return describe(method) + "[" + nameOrNull + "]";
+                                } else
+                                {
+                                    return describe(method);
+                                }
+                            }
+                        };
+            return ConcurrencyUtilities.getResult(future, timingParameters.getTimeoutMillis(),
+                    true, logSettingsOrNull, sensorOrNull);
         }
 
         private Object getErrorValue(final Method method)
@@ -223,7 +331,8 @@ public class MonitoringProxy<T>
 
         this.errorTypeValueMap = createDefaultErrorTypeValueMap();
         this.errorMethodValueMap = new HashMap<Method, Object>();
-        this.timeoutMillis = ConcurrencyUtilities.NO_TIMEOUT;
+        this.exceptionClassesSuitableForRetrying = new HashSet<Class<? extends Exception>>();
+        this.timingParameters = TimingParameters.getNoTimeoutNoRetriesParameters();
         this.delegate = new DelegatingInvocationHandler<T>(objectToProxyFor);
         this.handler = new MonitoringInvocationHandler();
         this.proxy = createProxy(interfaceClass, objectToProxyFor);
@@ -255,13 +364,14 @@ public class MonitoringProxy<T>
     }
 
     /**
-     * Sets the timeout to <var>newTimeoutMillis</var>.
+     * Sets the timing parameters to <var>newParameters</var>.
      * 
      * @return This object (for chaining).
      */
-    public MonitoringProxy<T> timeoutMillis(long newTimeoutMillis)
+    public MonitoringProxy<T> timing(TimingParameters newParameters)
     {
-        this.timeoutMillis = newTimeoutMillis;
+        assert newParameters != null;
+        this.timingParameters = newParameters;
         return this;
     }
 
@@ -298,6 +408,17 @@ public class MonitoringProxy<T>
         return this;
     }
 
+    /**
+     * Sets the logger to be used for error logging to <var>newLogger</var>.
+     * 
+     * @return This object (for chaining).
+     */
+    public MonitoringProxy<T> errorLog(ISimpleLogger newLogger)
+    {
+        this.loggerOrNull = newLogger;
+        return this;
+    }
+
     /**
      * Sets an error return <var>value</var> for the type <var>clazz</var>.
      * <p>
@@ -323,6 +444,35 @@ public class MonitoringProxy<T>
         return this;
     }
 
+    /**
+     * Add an {@link Exception} class that is suitable for retrying the operation.
+     */
+    public MonitoringProxy<T> exceptionClassSuitableForRetrying(Class<? extends Exception> clazz)
+    {
+        exceptionClassesSuitableForRetrying.add(clazz);
+        return this;
+    }
+
+    /**
+     * Add a list of {@link Exception} classes that are suitable for retrying the operation.
+     */
+    public MonitoringProxy<T> exceptionClassesSuitableForRetrying(
+            Collection<Class<? extends Exception>> classes)
+    {
+        exceptionClassesSuitableForRetrying.addAll(classes);
+        return this;
+    }
+
+    /**
+     * Sets an sensor of fine-grained activity. Activity on this sensor can prevent a method
+     * invocation from timing out.
+     */
+    public MonitoringProxy<T> sensor(IActivitySensor sensor)
+    {
+        this.sensorOrNull = sensor;
+        return this;
+    }
+
     /**
      * Returns the actual proxy.
      */
diff --git a/common/source/java/ch/systemsx/cisd/common/concurrent/RecordingActivityObserverSensor.java b/common/source/java/ch/systemsx/cisd/common/concurrent/RecordingActivityObserverSensor.java
new file mode 100644
index 0000000000000000000000000000000000000000..3840aac57765c55673ab8679f7433bcac30fd478
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/concurrent/RecordingActivityObserverSensor.java
@@ -0,0 +1,62 @@
+/*
+ * 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.common.concurrent;
+
+/**
+ * An implementation of {@link IActivityObserver} that just records the last event.
+ * 
+ * @author Bernd Rinn
+ */
+public class RecordingActivityObserverSensor implements IActivityObserver, IActivitySensor
+{
+
+    private volatile long lastActivityMillis;
+
+    public RecordingActivityObserverSensor()
+    {
+        update(); // Avoid initial 'false alarm'.
+    }
+
+    protected long getLastActivityMillis()
+    {
+        return lastActivityMillis;
+    }
+    
+    //
+    // IActivityObserver
+    //
+
+    public void update()
+    {
+        lastActivityMillis = System.currentTimeMillis();
+    }
+
+    //
+    // IActivitySensor
+    //
+    
+    public long getLastActivityMillisMoreRecentThan(long thresholdMillis)
+    {
+        return lastActivityMillis;
+    }
+
+    public boolean hasActivityMoreRecentThan(long thresholdMillis)
+    {
+        return (System.currentTimeMillis() - lastActivityMillis) < thresholdMillis;
+    }
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/exceptions/InvalidExternalDataException.java b/common/source/java/ch/systemsx/cisd/common/exceptions/InvalidExternalDataException.java
new file mode 100644
index 0000000000000000000000000000000000000000..19f9e611497ee42e8ff1f57a8a6363571b4d5749
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/exceptions/InvalidExternalDataException.java
@@ -0,0 +1,57 @@
+/*
+ * 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.common.exceptions;
+
+/**
+ * Data provided to the system from another system is invalid. This class is supposed to be used in
+ * cases where the data cannot be attributed to any user, but rather another system.
+ * 
+ * @author Bernd Rinn
+ */
+public class InvalidExternalDataException extends HighLevelException
+{
+
+    private static final long serialVersionUID = 1L;
+
+    public InvalidExternalDataException(String message)
+    {
+        super(message);
+    }
+
+    public InvalidExternalDataException(String message, Throwable cause)
+    {
+        super(message, cause);
+    }
+
+    /**
+     * Creates an {@link InvalidExternalDataException} using a {@link java.util.Formatter}.
+     */
+    public static InvalidExternalDataException fromTemplate(String messageTemplate, Object... args)
+    {
+        return new InvalidExternalDataException(String.format(messageTemplate, args));
+    }
+
+    /**
+     * Creates an {@link InvalidExternalDataException} using a {@link java.util.Formatter}.
+     */
+    public static InvalidExternalDataException fromTemplate(Throwable cause, String messageTemplate,
+            Object... args)
+    {
+        return new InvalidExternalDataException(String.format(messageTemplate, args), cause);
+    }
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/filesystem/FileLinkUtilities.java b/common/source/java/ch/systemsx/cisd/common/filesystem/FileLinkUtilities.java
index 3d9eae3c8994c34d379fb83592b780dea06dfa93..f23d6d7e7002e291d987eb950e9b5c4ba0c77c12 100644
--- a/common/source/java/ch/systemsx/cisd/common/filesystem/FileLinkUtilities.java
+++ b/common/source/java/ch/systemsx/cisd/common/filesystem/FileLinkUtilities.java
@@ -16,6 +16,9 @@
 
 package ch.systemsx.cisd.common.filesystem;
 
+import java.io.IOException;
+
+import ch.systemsx.cisd.common.exceptions.WrappedIOException;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
 
 /**
@@ -28,28 +31,17 @@ public class FileLinkUtilities
 
     private final static boolean operational = FileUtilities.loadNativeLibraryFromResource("jlink");
 
-    /** An exception that indicates that creating a link failed. */
-    public static final class FileLinkException extends RuntimeException
+    private static void throwException(String type, String source, String target,
+            String errorMessage)
     {
-        private static final long serialVersionUID = 1L;
-
-        private FileLinkException(String type, String source, String target, String errorMessage)
-        {
-            super(String.format("Creating %s link '%s' -> '%s': %s", type, source, target,
-                    errorMessage));
-        }
+        throw new WrappedIOException(new IOException(String.format(
+                "Creating %s link '%s' -> '%s': %s", type, source, target, errorMessage)));
     }
 
-    /** An exception that indicates that obtaining information about a link failed. */
-    public static final class FileLinkInfoException extends RuntimeException
+    private static void throwException(String filename, String errorMessage)
     {
-        private static final long serialVersionUID = 1L;
-
-        private FileLinkInfoException(String filename, String errorMessage)
-        {
-            super(String.format("Cannot obtain inode info for file '%s': %s", filename,
-                    errorMessage));
-        }
+        throw new WrappedIOException(new IOException(String.format(
+                "Cannot obtain inode info for file '%s': %s", filename, errorMessage)));
     }
 
     /** A class that provides information about a link. */
@@ -131,54 +123,55 @@ public class FileLinkUtilities
     /**
      * Creates a hard link from <var>filename</var> to <var>linkname</var>.
      * 
-     * @throws FileLinkException If the underlying system call fails, e.g. because <var>filename</var>
-     *             does not exist or because <var>linkname</var> already exists.
+     * @throws WrappedIOException If the underlying system call fails, e.g. because
+     *             <var>filename</var> does not exist or because <var>linkname</var> already exists.
      */
     public static final void createHardLink(String filename, String linkname)
-            throws FileLinkException
+            throws WrappedIOException
     {
         if (filename == null || linkname == null)
         {
-            throw new FileLinkException("hard", filename, linkname, "null is not allowed");
+            throwException("hard", filename, linkname, "null is not allowed");
         }
         final int result = hardlink(filename, linkname);
         if (result < 0)
         {
-            throw new FileLinkException("hard", filename, linkname, strerror(result));
+            throwException("hard", filename, linkname, strerror(result));
         }
     }
 
     /**
      * Creates a symbolic link from <var>filename</var> to <var>linkname</var>.
      * 
-     * @throws FileLinkException If the underlying system call fails, e.g. because <var>filename</var>
-     *             does not exist or because <var>linktarget</var> already exists.
+     * @throws WrappedIOException If the underlying system call fails, e.g. because
+     *             <var>filename</var> does not exist or because <var>linktarget</var> already
+     *             exists.
      */
     public static final void createSymbolicLink(String filename, String linkname)
-            throws FileLinkException
+            throws WrappedIOException
     {
         if (filename == null || linkname == null)
         {
-            throw new FileLinkException("symbolic", filename, linkname, "null is not allowed");
+            throwException("symbolic", filename, linkname, "null is not allowed");
         }
         final int result = symlink(filename, linkname);
         if (result < 0)
         {
-            throw new FileLinkException("symbolic", filename, linkname, strerror(result));
+            throwException("symbolic", filename, linkname, strerror(result));
         }
     }
 
-    private static int[] getLinkInfoArray(String linkname) throws FileLinkInfoException
+    private static int[] getLinkInfoArray(String linkname) throws WrappedIOException
     {
         if (linkname == null)
         {
-            throw new FileLinkInfoException(linkname, "null is not allowed");
+            throwException(linkname, "null is not allowed");
         }
         final int[] inodeInfo = new int[4];
         final int result = linkinfo(linkname, inodeInfo);
         if (result < 0)
         {
-            throw new FileLinkInfoException(linkname, strerror(result));
+            throwException(linkname, strerror(result));
         }
         return inodeInfo;
     }
@@ -186,10 +179,10 @@ public class FileLinkUtilities
     /**
      * Returns the inode for the <var>filename</var>.
      * 
-     * @throws FileLinkInfoException If the information could not be obtained, e.g. because the link
+     * @throws WrappedIOException If the information could not be obtained, e.g. because the link
      *             does not exist.
      */
-    public static final int getInode(String filename) throws FileLinkInfoException
+    public static final int getInode(String filename) throws WrappedIOException
     {
         return getLinkInfoArray(filename)[0];
     }
@@ -197,10 +190,10 @@ public class FileLinkUtilities
     /**
      * Returns the inode for the <var>filename</var>.
      * 
-     * @throws FileLinkInfoException If the information could not be obtained, e.g. because the link
+     * @throws WrappedIOException If the information could not be obtained, e.g. because the link
      *             does not exist.
      */
-    public static final int getHardLinkCount(String filename) throws FileLinkInfoException
+    public static final int getHardLinkCount(String filename) throws WrappedIOException
     {
         return getLinkInfoArray(filename)[1];
     }
@@ -209,10 +202,10 @@ public class FileLinkUtilities
      * Returns <code>true</code> if <var>filename</var> is a symbolic link and <code>false</code>
      * otherwise.
      * 
-     * @throws FileLinkInfoException If the information could not be obtained, e.g. because the link
+     * @throws WrappedIOException If the information could not be obtained, e.g. because the link
      *             does not exist.
      */
-    public static final boolean isSymbolicLink(String filename) throws FileLinkInfoException
+    public static final boolean isSymbolicLink(String filename) throws WrappedIOException
     {
         return getLinkInfoArray(filename)[2] != 0;
     }
@@ -221,10 +214,10 @@ public class FileLinkUtilities
      * Returns the value of the symbolik link <var>filename</var>, or <code>null</code>, if
      * <var>filename</var> is not a symbolic link.
      * 
-     * @throws FileLinkInfoException If the information could not be obtained, e.g. because the link
+     * @throws WrappedIOException If the information could not be obtained, e.g. because the link
      *             does not exist.
      */
-    public static final String tryReadSymbolicLink(String linkname) throws FileLinkInfoException
+    public static final String tryReadSymbolicLink(String linkname) throws WrappedIOException
     {
         final int[] info = getLinkInfoArray(linkname);
         return (info[2] != 0) ? readlink(linkname, info[3]) : null;
@@ -233,10 +226,10 @@ public class FileLinkUtilities
     /**
      * Returns the information about <var>linkname</var>.
      * 
-     * @throws FileLinkInfoException If the information could not be obtained, e.g. because the link
+     * @throws WrappedIOException If the information could not be obtained, e.g. because the link
      *             does not exist.
      */
-    public static final LinkInfo getLinkInfo(String linkname) throws FileLinkInfoException
+    public static final LinkInfo getLinkInfo(String linkname) throws WrappedIOException
     {
         final int[] info = getLinkInfoArray(linkname);
         final String symbolicLinkOrNull = (info[2] != 0) ? readlink(linkname, info[3]) : null;
diff --git a/common/source/java/ch/systemsx/cisd/common/filesystem/FileOperations.java b/common/source/java/ch/systemsx/cisd/common/filesystem/FileOperations.java
new file mode 100644
index 0000000000000000000000000000000000000000..4a1b31da285d0c2f32316683adc28fc82eacb6a1
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/filesystem/FileOperations.java
@@ -0,0 +1,497 @@
+/*
+ * 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.common.filesystem;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.apache.commons.io.FileCopyUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.Logger;
+
+import ch.systemsx.cisd.common.TimingParameters;
+import ch.systemsx.cisd.common.concurrent.IActivityObserver;
+import ch.systemsx.cisd.common.concurrent.MonitoringProxy;
+import ch.systemsx.cisd.common.concurrent.RecordingActivityObserverSensor;
+import ch.systemsx.cisd.common.exceptions.CheckedExceptionTunnel;
+import ch.systemsx.cisd.common.exceptions.WrappedIOException;
+import ch.systemsx.cisd.common.logging.Log4jSimpleLogger;
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
+import ch.systemsx.cisd.common.utilities.FileUtilities;
+
+/**
+ * Monitoring for (potentially hanging) file operations. Supposed to be used on remote file systems.
+ * 
+ * @author Bernd Rinn
+ */
+public class FileOperations implements IFileOperations
+{
+
+    // @Private
+    final static Logger operationLog =
+            LogFactory.getLogger(LogCategory.OPERATION, IFileOperations.class);
+
+    private final static IFileOperations instance = new FileOperations(null, null);
+
+    private final static ThreadLocal<IFileOperations> monitoredInstanceForThread =
+            new ThreadLocal<IFileOperations>();
+
+    /**
+     * Returns the straight-forward implementation of {@link IFileOperations}.
+     */
+    public static IFileOperations getInstance()
+    {
+        return instance;
+    }
+
+    /**
+     * Returns the monitored implementation of {@link IFileRemover} for the current thread with the
+     * default {@link TimingParameters} (i.e. with {@link TimingParameters#getDefaultParameters()}).
+     * <p>
+     * Note that you should only ever cache the return value of this method if you can ensure that
+     * the cached value will only be used in the same thread as the one where this method has been
+     * called. The monitored instance is <i>not</i> thread-safe with respect to the recursive list,
+     * remove and copy methods.
+     */
+    public static IFileOperations getMonitoredInstanceForCurrentThread()
+    {
+        IFileOperations monitoredInstance = monitoredInstanceForThread.get();
+        if (monitoredInstance == null)
+        {
+            monitoredInstance =
+                    internalCreateMonitored(TimingParameters.getDefaultParameters(),
+                            new RecordingActivityObserverSensor());
+            monitoredInstanceForThread.set(monitoredInstance);
+        }
+
+        return monitoredInstance;
+    }
+
+    /**
+     * Creates a monitored implementation of {@link IFileRemover} with the given
+     * <var>observerSensor</var>.
+     */
+    public static IFileOperations createMonitoredInstance(
+            RecordingActivityObserverSensor observerSensor)
+    {
+        return internalCreateMonitored(TimingParameters.getDefaultParameters(), observerSensor);
+    }
+
+    /**
+     * Creates a monitored implementation of {@link IFileRemover} with the given
+     * <var>parameters</var>.
+     */
+    public static IFileOperations createMonitoredInstance(TimingParameters parameters)
+    {
+        // One of the rare cases where '==' is exactly what we mean. Note that TimingParameters are
+        // <i>not</i> immutable.
+        if (parameters == TimingParameters.getDefaultParameters())
+        {
+            return getMonitoredInstanceForCurrentThread();
+        } else
+        {
+            return internalCreateMonitored(parameters, new RecordingActivityObserverSensor());
+        }
+    }
+
+    private static IFileOperations internalCreateMonitored(TimingParameters parameters,
+            RecordingActivityObserverSensor observerSensor)
+    {
+        return MonitoringProxy.create(IFileOperations.class,
+                new FileOperations(parameters, observerSensor)).timing(parameters).sensor(
+                observerSensor).errorLog(new Log4jSimpleLogger(operationLog)).name(
+                "remote file operations").get();
+    }
+
+    private final TimingParameters timingParametersOrNull;
+
+    private final IActivityObserver observerOrNull;
+
+    // @Private
+    FileOperations(TimingParameters timingParametersOrNull, IActivityObserver observerOrNull)
+    {
+        this.timingParametersOrNull = timingParametersOrNull;
+        this.observerOrNull = observerOrNull;
+    }
+
+    //
+    // IFileOperations
+    //
+
+    // java.io.File
+
+    public boolean exists(File file)
+    {
+        return file.exists();
+    }
+
+    public boolean delete(File file)
+    {
+        return file.delete();
+    }
+
+    public boolean rename(File source, File destination)
+    {
+        return source.renameTo(destination);
+    }
+
+    public boolean canRead(File file)
+    {
+        return file.canRead();
+    }
+
+    public boolean canWrite(File file)
+    {
+        return file.canWrite();
+    }
+
+    public boolean createNewFile(File file) throws WrappedIOException
+    {
+        try
+        {
+            return file.createNewFile();
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public File createTempFile(String prefix, String suffix, File directory)
+            throws WrappedIOException
+    {
+        try
+        {
+            return File.createTempFile(prefix, suffix, directory);
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public File createTempFile(String prefix, String suffix) throws WrappedIOException
+    {
+        try
+        {
+            return File.createTempFile(prefix, suffix);
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public File getCanonicalFile(File file) throws WrappedIOException
+    {
+        try
+        {
+            return file.getCanonicalFile();
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public String getCanonicalPath(File file) throws WrappedIOException
+    {
+        try
+        {
+            return file.getCanonicalPath();
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public boolean isDirectory(File file)
+    {
+        return file.isDirectory();
+    }
+
+    public boolean isFile(File file)
+    {
+        return file.isFile();
+    }
+
+    public boolean isHidden(File file)
+    {
+        return file.isHidden();
+    }
+
+    public long lastModified(File file)
+    {
+        return file.lastModified();
+    }
+
+    public long length(File file)
+    {
+        return file.length();
+    }
+
+    public String[] list(File file)
+    {
+        return file.list();
+    }
+
+    public String[] list(File file, FilenameFilter filter)
+    {
+        return file.list(filter);
+    }
+
+    public File[] listFiles(File file)
+    {
+        return file.listFiles();
+    }
+
+    public File[] listFiles(File file, FilenameFilter filter)
+    {
+        return file.listFiles(filter);
+    }
+
+    public File[] listFiles(File file, FileFilter filter)
+    {
+        return file.listFiles(filter);
+    }
+
+    public boolean mkdir(File file)
+    {
+        return file.mkdir();
+    }
+
+    public boolean mkdirs(File file)
+    {
+        return file.mkdirs();
+    }
+
+    public boolean setLastModified(File file, long time)
+    {
+        return file.setLastModified(time);
+    }
+
+    public boolean setReadOnly(File file)
+    {
+        return file.setReadOnly();
+    }
+
+    // Advanced
+
+    public List<File> listDirectories(File directory, boolean recursive)
+    {
+        return FileUtilities.listDirectories(directory, recursive, observerOrNull);
+    }
+
+    public List<File> listFiles(File directory, String[] extensionsOrNull, boolean recursive)
+    {
+        return FileUtilities.listFiles(directory, extensionsOrNull, recursive, observerOrNull);
+    }
+
+    public List<File> listFilesAndDirectories(File directory, boolean recursive)
+    {
+        return FileUtilities.listFilesAndDirectories(directory, recursive, observerOrNull);
+    }
+
+    public void touch(File file) throws WrappedIOException
+    {
+        try
+        {
+            FileUtils.touch(file);
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public void deleteRecursively(File fileToRemove) throws WrappedIOException
+    {
+        final boolean deleteOK =
+                FileUtilities.deleteRecursively(fileToRemove, null, observerOrNull);
+        if (deleteOK == false)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(new IOException("Recursive deletion of '"
+                    + fileToRemove + "' failed."));
+        }
+    }
+
+    public boolean removeRecursively(File fileToRemove)
+    {
+        return FileUtilities.deleteRecursively(fileToRemove, null, observerOrNull);
+    }
+
+    public void move(File source, File destination) throws WrappedIOException
+    {
+        if (destination.isDirectory())
+        {
+            moveToDirectory(source, destination);
+        } else
+        {
+            final boolean renameOK = rename(source, destination);
+            if (renameOK == false)
+            {
+                throw CheckedExceptionTunnel.wrapIfNecessary(new IOException("Moving '"
+                        + source.getAbsolutePath() + "' into directory '"
+                        + destination.getAbsolutePath() + "' failed."));
+            }
+        }
+    }
+
+    public void moveToDirectory(File source, File destinationDirectory) throws WrappedIOException
+    {
+        final File target = new File(destinationDirectory, source.getName());
+        final boolean moveOK = source.renameTo(target);
+        if (moveOK == false)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(new IOException("Moving '"
+                    + source.getAbsolutePath() + "' into directory '"
+                    + destinationDirectory.getAbsolutePath() + "' failed."));
+        }
+    }
+
+    public void copyDirectory(File srcDir, File destDir)
+    {
+        try
+        {
+            FileCopyUtils.copyDirectory(srcDir, destDir, observerOrNull);
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public void copyDirectoryToDirectory(File srcDir, File destDir) throws WrappedIOException
+    {
+        try
+        {
+            FileCopyUtils.copyDirectoryToDirectory(srcDir, destDir, observerOrNull);
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public void copyFile(File srcFile, File destFile) throws WrappedIOException
+    {
+        try
+        {
+            FileCopyUtils.copyFile(srcFile, destFile);
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public void copyFileToDirectory(File srcFile, File destDir) throws WrappedIOException
+    {
+        try
+        {
+            FileCopyUtils.copyFileToDirectory(srcFile, destDir);
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public void copy(File source, File destination) throws WrappedIOException
+    {
+        try
+        {
+            FileCopyUtils.copy(source, destination);
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public void copyToDirectory(File source, File destDir) throws WrappedIOException
+    {
+        try
+        {
+            FileCopyUtils.copyToDirectory(source, destDir);
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public byte[] getContentAsByteArray(File file) throws WrappedIOException
+    {
+        java.io.InputStream inputStream = null;
+        try
+        {
+            inputStream = new FileInputStream(file);
+            return IOUtils.toByteArray(inputStream);
+        } catch (IOException ex)
+        {
+            throw new WrappedIOException(ex);
+        } finally
+        {
+            IOUtils.closeQuietly(inputStream);
+        }
+    }
+
+    public String getContentAsString(File file) throws WrappedIOException
+    {
+        return FileUtilities.loadToString(file);
+    }
+
+    public List<String> getContentAsStringList(File file) throws WrappedIOException
+    {
+        return FileUtilities.loadToStringList(file);
+    }
+
+    public InputStream getInputStream(File file) throws WrappedIOException
+    {
+        return new IInputStreamAdapter(getIInputStream(file));
+    }
+
+    public IInputStream getIInputStream(File file) throws WrappedIOException
+    {
+        try
+        {
+            final IInputStream is = internalGetIInputStream(file);
+            if (timingParametersOrNull != null)
+            {
+                return MonitoringProxy.create(IInputStream.class, is).timing(
+                        timingParametersOrNull).name(
+                        "input stream <" + file.getAbsolutePath() + ">").get();
+            } else
+            {
+                return is;
+            }
+        } catch (IOException ex)
+        {
+            throw new WrappedIOException(ex);
+        }
+    }
+
+    // @Private
+    IInputStream internalGetIInputStream(File file) throws FileNotFoundException
+    {
+        return new InputStreamAdapter(new FileInputStream(file));
+    }
+
+    public void writeToFile(File file, String content) throws WrappedIOException
+    {
+        FileUtilities.writeToFile(file, content);
+    }
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/filesystem/IFileOperations.java b/common/source/java/ch/systemsx/cisd/common/filesystem/IFileOperations.java
new file mode 100644
index 0000000000000000000000000000000000000000..717e6cca23e2fa0f33fc3b8a67c1bdfde759b301
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/filesystem/IFileOperations.java
@@ -0,0 +1,379 @@
+/*
+ * 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.common.filesystem;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FilenameFilter;
+import java.io.InputStream;
+import java.util.List;
+
+import ch.systemsx.cisd.common.exceptions.WrappedIOException;
+
+/**
+ * Operations on {@link File} that are good to monitor when being performed on remote file systems
+ * because they can hang when a remote resource goes down unexpectedly.
+ * 
+ * @author Bernd Rinn
+ */
+public interface IFileOperations extends IFileRemover
+{
+
+    //
+    // java.io.File
+    //
+
+    /**
+     * @see File#getCanonicalPath()
+     */
+    public String getCanonicalPath(File file) throws WrappedIOException;
+
+    /**
+     * @see File#getCanonicalFile()
+     */
+    public File getCanonicalFile(File file) throws WrappedIOException;
+
+    /**
+     * @see File#canRead()
+     */
+    public boolean canRead(File file);
+
+    /**
+     * @see File#canWrite()
+     */
+    public boolean canWrite(File file);
+
+    /**
+     * @see File#exists()
+     */
+    public boolean exists(File file);
+
+    /**
+     * @see File#isDirectory()
+     */
+    public boolean isDirectory(File file);
+
+    /**
+     * @see File#isFile()
+     */
+    public boolean isFile(File file);
+
+    /**
+     * @see File#isHidden()
+     */
+    public boolean isHidden(File file);
+
+    /**
+     * @see File#lastModified()
+     */
+    public long lastModified(File file);
+
+    /**
+     * @see File#length()
+     */
+    public long length(File file);
+
+    public boolean createNewFile(File file) throws WrappedIOException;
+
+    /**
+     * @see File#delete()
+     */
+    public boolean delete(File file);
+
+    /**
+     * @see File#list()
+     */
+    public String[] list(File file);
+
+    /**
+     * @see File#list(FilenameFilter)
+     */
+    public String[] list(File file, FilenameFilter filter);
+
+    /**
+     * @see File#listFiles()
+     */
+    public File[] listFiles(File file);
+
+    /**
+     * @see File#listFiles(FilenameFilter)
+     */
+    public File[] listFiles(File file, FilenameFilter filter);
+
+    /**
+     * @see File#listFiles(FileFilter)
+     */
+    public File[] listFiles(File file, FileFilter filter);
+
+    /**
+     * @see File#mkdir()
+     */
+    public boolean mkdir(File file);
+
+    /**
+     * @see File#mkdirs()
+     */
+    public boolean mkdirs(File file);
+
+    /**
+     * @see File#renameTo(File)
+     */
+    public boolean rename(File source, File destination);
+
+    public boolean setLastModified(File file, long time);
+
+    public boolean setReadOnly(File file);
+
+    public File createTempFile(String prefix, String suffix, File directory)
+            throws WrappedIOException;
+
+    public File createTempFile(String prefix, String suffix) throws WrappedIOException;
+
+    //
+    // Advanced
+    //
+
+    /**
+     * Finds files within a given directory (and optionally its subdirectories) which match an array
+     * of extensions.
+     * <p>
+     * This call is suitable for large or huge directories on slow file systems as it is able to
+     * notify the monitor of progress.
+     * 
+     * @param directory The directory to search in.
+     * @param extensionsOrNull An array of extensions, ex. {"java","xml"}. If this parameter is
+     *            <code>null</code>, all files are returned.
+     * @param recursive If true all subdirectories are searched as well.
+     * @return A list of java.io.File (all files) with the matching files, or an empty list, if
+     *         <var>directory</var> ist not a directory.
+     */
+    public List<File> listFiles(File directory, String[] extensionsOrNull, boolean recursive);
+
+    /**
+     * Finds directories within a given directory (and optionally its subdirectories).
+     * <p>
+     * This call is suitable for large or huge directories on slow file systems as it is able to
+     * notify the monitor of progress.
+     * 
+     * @param directory The directory to search in.
+     * @param recursive If true all subdirectories are searched as well.
+     * @return A list of java.io.File (all directories), or an empty list, if <var>directory</var>
+     *         ist not a directory.
+     */
+    public List<File> listDirectories(File directory, boolean recursive);
+
+    /**
+     * Finds files or directories within a given directory (and optionally its subdirectories).
+     * <p>
+     * This call is suitable for large or huge directories on slow file systems as it is able to
+     * notify the monitor of progress.
+     * 
+     * @param directory The directory to search in.
+     * @param recursive If true all subdirectories are searched as well.
+     * @return A list of java.io.File (all directories), or an empty list, if <var>directory</var>
+     *         ist not a directory.
+     */
+    public List<File> listFilesAndDirectories(File directory, boolean recursive);
+
+    /**
+     * Sets the file's last modification time to the current time without changing the content. If
+     * the file does not exist, create it empty.
+     * 
+     * @throws WrappedIOException If the file cannot be created or the last modification time cannot
+     *             be changed.
+     */
+    public void touch(File file) throws WrappedIOException;
+
+    /**
+     * Removes the given <var>fileToRemove</var>, if necessary recursively.
+     * 
+     * @param fileToRemove File or directory to remove. If it is a directory, it will be removed
+     *            recursively.
+     * @throws WrappedIOException If the file or directory cannot be removed.
+     */
+    public void deleteRecursively(File fileToRemove) throws WrappedIOException;
+
+    /**
+     * Move <var>source</var> to <var>destinationDirectory</var>.
+     * 
+     * @param source File or directory to move. Must exist when this method is called.
+     * @param destinationDirectory The directory to move <var>source</var> to. Has to be an existing
+     *            directory.
+     * @throws WrappedIOException If <var>source</var> cannot be moved into
+     *             <var>destinationDirectory</var>.
+     */
+    public void moveToDirectory(File source, File destinationDirectory) throws WrappedIOException;
+
+    /**
+     * Rename <var>source</var> to <var>destination</var>, or move <var>source</var> to
+     * <var>destination</var> if it is an existing directory. Combines {@link #rename(File, File)}
+     * and {@link #moveToDirectory(File, File)} in move method, choosing the right method depending
+     * on the <var>destination</var>.
+     * 
+     * @param source File or directory to move. Must exist when this method is called.
+     * @param destination If it does not exist, this is the new name for <var>source</var>. If it is
+     *            an existing directory, <var>source</var> will be moved to this directory.
+     * @throws WrappedIOException If <var>source</var> cannot be moved to <var>destination</var>.
+     */
+    public void move(File source, File destination) throws WrappedIOException;
+
+    /**
+     * Copies a file or a whole directory to a new location preserving the file dates.
+     * <p>
+     * Calls {@link #copyFile(File, File)} if <var>source</var> is a file and
+     * {@link #copyDirectory(File, File)} if it is a directory.
+     * 
+     * @param source an existing file or directory to copy, must not be <code>null</code>
+     * @param destination the new file or directory, must not be <code>null</code>
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws WrappedIOException if source or destination is invalid
+     * @throws WrappedIOException if an IO error occurs during copying
+     * @see #copyFile(File, File)
+     * @see #copyDirectory(File, File)
+     */
+    public void copy(File source, File destination) throws WrappedIOException;
+
+    /**
+     * Copies a file or a whole directory to a new location preserving the file dates.
+     * <p>
+     * Calls {@link #copyFileToDirectory(File, File)} if <var>source</var> is a file and
+     * {@link #copyDirectoryToDirectory(File, File)} if it is a directory.
+     * 
+     * @param source an existing file or directory to copy, must not be <code>null</code>
+     * @param destDir the directory to place the copy in, must not be <code>null</code>
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws WrappedIOException if source or destination is invalid
+     * @throws WrappedIOException if an IO error occurs during copying
+     * @see #copyFileToDirectory(File, File)
+     * @see #copyDirectoryToDirectory(File, File)
+     */
+    public void copyToDirectory(File source, File destDir) throws WrappedIOException;
+
+    /**
+     * Copies a whole directory to a new location preserving the file dates.
+     * <p>
+     * This method copies the specified directory and all its child directories and files to the
+     * specified destination. The destination is the new location and name of the directory.
+     * <p>
+     * The destination directory is created if it does not exist. If the destination directory did
+     * exist, then this method merges the source with the destination, with the source taking
+     * precedence.
+     * 
+     * @param srcDir an existing directory to copy, must not be <code>null</code>
+     * @param destDir the new directory, must not be <code>null</code>
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws WrappedIOException if source or destination is invalid
+     * @throws WrappedIOException if an IO error occurs during copying
+     */
+    public void copyDirectory(File srcDir, File destDir) throws WrappedIOException;
+
+    /**
+     * Copies a file to a new location preserving the file date.
+     * <p>
+     * This method copies the contents of the specified source file to the specified destination
+     * file. The directory holding the destination file is created if it does not exist. If the
+     * destination file exists, then this method will overwrite it.
+     * 
+     * @param srcFile an existing file to copy, must not be <code>null</code>
+     * @param destFile the new file, must not be <code>null</code>
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws WrappedIOException if source or destination is invalid
+     * @throws WrappedIOException if an IO error occurs during copying
+     * @see #copyFileToDirectory(File, File)
+     */
+    public void copyFile(File srcFile, File destFile) throws WrappedIOException;
+
+    /**
+     * Copies a file to a directory preserving the file date.
+     * <p>
+     * This method copies the contents of the specified source file to a file of the same name in
+     * the specified destination directory. The destination directory is created if it does not
+     * exist. If the destination file exists, then this method will overwrite it.
+     * 
+     * @param srcFile an existing file to copy, must not be <code>null</code>
+     * @param destDir the directory to place the copy in, must not be <code>null</code>
+     * @throws NullPointerException if source or destination is null
+     * @throws WrappedIOException if source or destination is invalid
+     * @throws WrappedIOException if an IO error occurs during copying
+     */
+    public void copyFileToDirectory(File srcFile, File destDir) throws WrappedIOException;
+
+    /**
+     * Copies a directory to within another directory preserving the file dates.
+     * <p>
+     * This method copies the source directory and all its contents to a directory of the same name
+     * in the specified destination directory.
+     * <p>
+     * The destination directory is created if it does not exist. If the destination directory did
+     * exist, then this method merges the source with the destination, with the source taking
+     * precedence.
+     * 
+     * @param srcDir an existing directory to copy, must not be <code>null</code>
+     * @param destDir the directory to place the copy in, must not be <code>null</code>
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws WrappedIOException if source or destination is invalid
+     * @throws WrappedIOException if an IO error occurs during copying
+     */
+    public void copyDirectoryToDirectory(File srcDir, File destDir) throws WrappedIOException;
+
+    //
+    // Content
+    //
+
+    /**
+     * Returns the content of <var>file</var> as a byte array.
+     * 
+     * @throws WrappedIOException if an IO error occurs during reading the file
+     */
+    public byte[] getContentAsByteArray(File file) throws WrappedIOException;
+
+    /**
+     * Returns the content of <var>file</var> as a String.
+     * 
+     * @throws WrappedIOException if an IO error occurs during reading the file
+     */
+    public String getContentAsString(File file) throws WrappedIOException;
+
+    /**
+     * Returns the content of <var>file</var> as a list of Strings.
+     * 
+     * @throws WrappedIOException if an IO error occurs during reading the file
+     */
+    public List<String> getContentAsStringList(File file) throws WrappedIOException;
+
+    /**
+     * Returns a monitored {@link InputStream} of <var>file</var>.
+     * 
+     * @throws WrappedIOException if an IO error occurs during opening the stream
+     */
+    public InputStream getInputStream(File file) throws WrappedIOException;
+
+    /**
+     * Returns a monitored {@link IInputStream} of <var>file</var>.
+     * 
+     * @throws WrappedIOException if an IO error occurs during opening the stream
+     */
+    public IInputStream getIInputStream(File file) throws WrappedIOException;
+
+    /**
+     * Writes <var>content</var> to <var>file</var>. If <var>file</var> already exists, it will be
+     * overwritten.
+     * 
+     * @throws WrappedIOException if an IO error occurs during writing
+     */
+    public void writeToFile(File file, String content) throws WrappedIOException;
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/filesystem/IFileRemover.java b/common/source/java/ch/systemsx/cisd/common/filesystem/IFileRemover.java
new file mode 100644
index 0000000000000000000000000000000000000000..529a4b76b5afa9255ef0ea81c320bc1e96608fa2
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/filesystem/IFileRemover.java
@@ -0,0 +1,37 @@
+/*
+ * 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.common.filesystem;
+
+import java.io.File;
+
+/**
+ * A role that can remove a file or directory.
+ * 
+ * @author Bernd Rinn
+ */
+public interface IFileRemover
+{
+    /**
+     * Removes the given <var>fileToRemove</var>.
+     * 
+     * @param fileToRemove File or directory to remove. If it is a directory, it will be removed
+     *            recursively.
+     * @return <code>true</code> if the file or directory was removed successfully and
+     *         <code>false</code> otherwise.
+     */
+    public boolean removeRecursively(File fileToRemove);
+}
\ No newline at end of file
diff --git a/common/source/java/ch/systemsx/cisd/common/filesystem/IInputStream.java b/common/source/java/ch/systemsx/cisd/common/filesystem/IInputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1985d6601330a2a895c0bdc78df3ead22b07425
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/filesystem/IInputStream.java
@@ -0,0 +1,74 @@
+/*
+ * 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.common.filesystem;
+
+import ch.systemsx.cisd.common.exceptions.WrappedIOException;
+
+/**
+ * In interface for {@link java.io.InputStream}.
+ *
+ * @author Bernd Rinn
+ */
+public interface IInputStream
+{
+
+    /**
+     * @see java.io.InputStream#read()
+     */
+    public int read() throws WrappedIOException;
+    
+    /**
+     * @see java.io.InputStream#read(byte[])
+     */
+    public int read(byte b[]) throws WrappedIOException;
+    
+    /**
+     * @see java.io.InputStream#read(byte[], int, int)
+     */
+    public int read(byte b[], int off, int len) throws WrappedIOException;
+    
+    /**
+     * @see java.io.InputStream#skip(long)
+     */
+    public long skip(long n) throws WrappedIOException;
+    
+    /**
+     * @see java.io.InputStream#available()
+     */
+    public int available() throws WrappedIOException;
+
+    /**
+     * @see java.io.InputStream#close()
+     */
+    public void close() throws WrappedIOException;
+    
+    /**
+     * @see java.io.InputStream#mark(int)
+     */
+    public void mark(int readlimit);
+    
+    /**
+     * @see java.io.InputStream#reset()
+     */
+    public void reset() throws WrappedIOException;
+
+    /**
+     * @see java.io.InputStream#markSupported()
+     */
+    public boolean markSupported();
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/filesystem/IInputStreamAdapter.java b/common/source/java/ch/systemsx/cisd/common/filesystem/IInputStreamAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..d4cab963c7bdffd50ca1d8eaf62255236782e3c3
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/filesystem/IInputStreamAdapter.java
@@ -0,0 +1,135 @@
+/*
+ * 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.common.filesystem;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import ch.systemsx.cisd.common.exceptions.WrappedIOException;
+
+/**
+ * An adapter for {@link IInputStream} that extends {@link java.io.InputStream}.
+ *
+ * @author Bernd Rinn
+ */
+public class IInputStreamAdapter extends InputStream
+{
+
+    private final IInputStream delegate;
+    
+    public IInputStreamAdapter(IInputStream delegate)
+    {
+        this.delegate = delegate;
+    }
+    
+    @Override
+    public int available() throws IOException
+    {
+        try
+        {
+            return delegate.available();
+        } catch (WrappedIOException ex)
+        {
+            throw (IOException) ex.getCause();
+        }
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        try
+        {
+            delegate.close();
+        } catch (WrappedIOException ex)
+        {
+            throw (IOException) ex.getCause();
+        }
+    }
+
+    @Override
+    public synchronized void mark(int readlimit)
+    {
+        delegate.mark(readlimit);
+    }
+
+    @Override
+    public boolean markSupported()
+    {
+        return delegate.markSupported();
+    }
+
+    @Override
+    public int read() throws IOException
+    {
+        try
+        {
+            return delegate.read();
+        } catch (WrappedIOException ex)
+        {
+            throw (IOException) ex.getCause();
+        }
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException
+    {
+        try
+        {
+            return delegate.read(b, off, len);
+        } catch (WrappedIOException ex)
+        {
+            throw (IOException) ex.getCause();
+        }
+    }
+
+    @Override
+    public int read(byte[] b) throws IOException
+    {
+        try
+        {
+            return delegate.read(b);
+        } catch (WrappedIOException ex)
+        {
+            throw (IOException) ex.getCause();
+        }
+    }
+
+    @Override
+    public synchronized void reset() throws IOException
+    {
+        try
+        {
+            delegate.reset();
+        } catch (WrappedIOException ex)
+        {
+            throw (IOException) ex.getCause();
+        }
+    }
+
+    @Override
+    public long skip(long n) throws IOException
+    {
+        try
+        {
+            return delegate.skip(n);
+        } catch (WrappedIOException ex)
+        {
+            throw (IOException) ex.getCause();
+        }
+    }
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/filesystem/InputStreamAdapter.java b/common/source/java/ch/systemsx/cisd/common/filesystem/InputStreamAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..15e6d36fb2fff09049d7debeab295dcfa2b3aec5
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/filesystem/InputStreamAdapter.java
@@ -0,0 +1,148 @@
+/*
+ * 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.common.filesystem;
+
+import java.io.IOException;
+
+import ch.systemsx.cisd.common.exceptions.CheckedExceptionTunnel;
+import ch.systemsx.cisd.common.exceptions.WrappedIOException;
+
+/**
+ * An adapter for {@link java.io.InputStream} that implements {@link IInputStream}.
+ *
+ * @author Bernd Rinn
+ */
+public class InputStreamAdapter implements IInputStream
+{
+    
+    private final java.io.InputStream delegate;
+    
+    public InputStreamAdapter(java.io.InputStream delegate)
+    {
+        this.delegate = delegate;
+    }
+
+    public int available() throws WrappedIOException
+    {
+        try
+        {
+            return delegate.available();
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public void close() throws WrappedIOException
+    {
+        try
+        {
+            delegate.close();
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public void mark(int readlimit)
+    {
+        delegate.mark(readlimit);
+    }
+
+    public boolean markSupported()
+    {
+        return delegate.markSupported();
+    }
+
+    public int read() throws WrappedIOException
+    {
+        try
+        {
+            return delegate.read();
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public int read(byte[] b, int off, int len) throws WrappedIOException
+    {
+        try
+        {
+            return delegate.read(b, off, len);
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public int read(byte[] b) throws WrappedIOException
+    {
+        try
+        {
+            return delegate.read(b);
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public void reset() throws WrappedIOException
+    {
+        try
+        {
+            delegate.reset();
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    public long skip(long n) throws WrappedIOException
+    {
+        try
+        {
+            return delegate.skip(n);
+        } catch (IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+    }
+
+    //
+    // Object
+    //
+    
+    @Override
+    public String toString()
+    {
+        return delegate.toString();
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        return delegate.equals(obj);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return delegate.hashCode();
+    }
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/filesystem/LoggingPathRemoverDecorator.java b/common/source/java/ch/systemsx/cisd/common/filesystem/LoggingPathRemoverDecorator.java
new file mode 100644
index 0000000000000000000000000000000000000000..f4cf734e0516eb0d8abcd4d91cd733e23bc40bb9
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/filesystem/LoggingPathRemoverDecorator.java
@@ -0,0 +1,66 @@
+/*
+ * 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.common.filesystem;
+
+import java.io.File;
+
+import ch.systemsx.cisd.common.logging.ISimpleLogger;
+import ch.systemsx.cisd.common.logging.LogLevel;
+
+/**
+ * An {@link IFileRemover} decorator that logs path removals.
+ * 
+ * @author Bernd Rinn
+ */
+public class LoggingPathRemoverDecorator implements IFileRemover
+{
+    private final IFileRemover delegate;
+
+    private final ISimpleLogger logger;
+
+    private final boolean failuresOnly;
+
+    public LoggingPathRemoverDecorator(IFileRemover delegate, ISimpleLogger logger,
+            boolean failuresOnly)
+    {
+        this.delegate = delegate;
+        this.logger = logger;
+        this.failuresOnly = failuresOnly;
+    }
+
+    public boolean removeRecursively(File fileToRemove)
+    {
+        final boolean ok = delegate.removeRecursively(fileToRemove);
+        if (shouldLog(ok))
+        {
+            logger.log(ok ? LogLevel.INFO : LogLevel.ERROR, String.format("Deleting %s '%s': %s.",
+                    getType(fileToRemove), fileToRemove.getPath(), ok ? "OK" : "FAILED"));
+        }
+        return ok;
+    }
+
+    private boolean shouldLog(final boolean ok)
+    {
+        return (ok && failuresOnly) == false;
+    }
+
+    private String getType(File fileToRemove)
+    {
+        return fileToRemove.isDirectory() ? "directory" : "file";
+    }
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/filesystem/QueueingPathRemoverService.java b/common/source/java/ch/systemsx/cisd/common/filesystem/QueueingPathRemoverService.java
new file mode 100644
index 0000000000000000000000000000000000000000..d6626ce9aa0f09ede234b74912954497d09d0562
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/filesystem/QueueingPathRemoverService.java
@@ -0,0 +1,229 @@
+/*
+ * 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.common.filesystem;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.log4j.Logger;
+
+import ch.systemsx.cisd.common.TimingParameters;
+import ch.systemsx.cisd.common.collections.ExtendedBlockingQueueFactory;
+import ch.systemsx.cisd.common.collections.ExtendedLinkedBlockingQueue;
+import ch.systemsx.cisd.common.collections.IExtendedBlockingQueue;
+import ch.systemsx.cisd.common.collections.PersistentExtendedBlockingQueueDecorator;
+import ch.systemsx.cisd.common.collections.RecordBasedQueuePersister;
+import ch.systemsx.cisd.common.concurrent.MonitoringProxy;
+import ch.systemsx.cisd.common.exceptions.StopException;
+import ch.systemsx.cisd.common.logging.ISimpleLogger;
+import ch.systemsx.cisd.common.logging.Log4jSimpleLogger;
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
+
+/**
+ * A service for removing (deep) paths. It provides a {@link IFileRemover} that marks {@link File}s
+ * for deletion and queues them up, using a separate thread to actually delete them.
+ * <p>
+ * Note that the service needs to be started via {@link #start(File, TimingParameters)}.
+ * <p>
+ * A file can be specified that keeps track of all the items that are to be deleted in order to
+ * persist program restart.
+ * 
+ * @author Bernd Rinn
+ */
+public class QueueingPathRemoverService implements IFileRemover
+{
+
+    private final static Logger operationLog =
+            LogFactory.getLogger(LogCategory.OPERATION, QueueingPathRemoverService.class);
+
+    private static final int INITIAL_RECORD_SIZE = 128;
+
+    // @Private
+    final static String SHREDDER_PREFIX = ".SHREDDER_";
+
+    private static final AtomicInteger counter = new AtomicInteger();
+
+    private static IExtendedBlockingQueue<File> queue = null;
+
+    private static ICloseable queueCloseableOrNull = null;
+
+    private static Thread thread = null;
+
+    private static IFileRemover deepRemover = null;
+
+    private static IFileRemover queueingRemover = null;
+
+    private static QueueingPathRemoverService instance = new QueueingPathRemoverService();
+
+    /**
+     * Returns the instance of the shredder.
+     */
+    public static final QueueingPathRemoverService getInstance()
+    {
+        return instance;
+    }
+
+    /**
+     * Initializes the shredder thread. Will not persist over program restart. <i>Needs to be called
+     * before this class is constructed for the first time.</i>
+     */
+    public static final void start()
+    {
+        start(null);
+    }
+
+    /**
+     * Initializes the shredder thread. <i>Needs to be called before this class is constructed for
+     * the first time.</i>
+     * 
+     * @param queueFileOrNull If not <code>null</code>, the file will be used to persist the items
+     *            to be deleted over program restart.
+     */
+    public static final void start(File queueFileOrNull)
+    {
+        start(queueFileOrNull, TimingParameters.getDefaultParameters());
+    }
+
+    /**
+     * Initializes the shredder thread. <i>Needs to be called before this class is constructed for
+     * the first time.</i>
+     * 
+     * @param queueFileOrNull If not <code>null</code>, the file will be used to persist the items
+     *            to be deleted over program restart.
+     */
+    public static final void start(final File queueFileOrNull, TimingParameters parameters)
+    {
+        final ISimpleLogger logger = new Log4jSimpleLogger(operationLog);
+        final IFileRemover monitoringProxy = FileOperations.createMonitoredInstance(parameters);
+        deepRemover = new LoggingPathRemoverDecorator(monitoringProxy, logger, false);
+        queueingRemover = MonitoringProxy.create(IFileRemover.class, new IFileRemover()
+            {
+                public boolean removeRecursively(File fileToRemove)
+                {
+                    if (fileToRemove.isFile())
+                    {
+                        return fileToRemove.delete();
+                    } else
+                    {
+                        final String name =
+                                SHREDDER_PREFIX + System.currentTimeMillis() + "-"
+                                        + counter.incrementAndGet() + "-" + fileToRemove.getName();
+                        final File shredderFile = new File(fileToRemove.getParentFile(), name);
+                        final boolean ok = fileToRemove.renameTo(shredderFile);
+                        if (ok)
+                        {
+                            queue.add(shredderFile);
+                        }
+                        return ok;
+                    }
+                }
+            }).timing(parameters).errorLog(logger).get();
+        if (queueFileOrNull != null)
+        {
+            final PersistentExtendedBlockingQueueDecorator<File> persistentQueue =
+                    ExtendedBlockingQueueFactory.createPersistRecordBased(queueFileOrNull,
+                            INITIAL_RECORD_SIZE);
+            queue = persistentQueue;
+            queueCloseableOrNull = persistentQueue;
+        } else
+        {
+            queue = new ExtendedLinkedBlockingQueue<File>();
+        }
+        thread = new Thread(new Runnable()
+            {
+                public void run()
+                {
+                    try
+                    {
+                        while (true)
+                        {
+                            final File fileToRemove = queue.peekWait();
+                            deepRemover.removeRecursively(fileToRemove);
+                            // Note: this is the only consumer of this queue.
+                            queue.take();
+                        }
+                    } catch (InterruptedException ex)
+                    {
+                        // Exit thread.
+                    } catch (StopException ex)
+                    {
+                        // Exit thread.
+                    }
+                }
+            }, "Shredder Queue");
+        thread.setDaemon(true);
+        thread.start();
+    }
+
+    private static final void close()
+    {
+        if (queueCloseableOrNull != null)
+        {
+            queueCloseableOrNull.close();
+        }
+    }
+
+    public static final void stop()
+    {
+        thread.interrupt();
+        close();
+        thread = null;
+        queue = null;
+        queueCloseableOrNull = null;
+        deepRemover = null;
+    }
+
+    public static final boolean stopAndWait(long timeoutMillis)
+    {
+        thread.interrupt();
+        try
+        {
+            thread.join(timeoutMillis);
+        } catch (InterruptedException ex)
+        {
+        }
+        final boolean ok = (thread.isAlive() == false);
+        close();
+        return ok;
+    }
+
+    public static final boolean isRunning()
+    {
+        return queueingRemover != null;
+    }
+    
+    /**
+     * Return list of shredder items.
+     */
+    public static final List<File> listShredderItems(File queueFile)
+    {
+        return RecordBasedQueuePersister.list(File.class, queueFile);
+    }
+
+    private QueueingPathRemoverService()
+    {
+        // Cannot be instantiated.
+    }
+
+    public boolean removeRecursively(File fileToRemove)
+    {
+        return queueingRemover.removeRecursively(fileToRemove);
+    }
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/filesystem/rsync/RsyncCopier.java b/common/source/java/ch/systemsx/cisd/common/filesystem/rsync/RsyncCopier.java
index a29bd4ac0cc1be9eebb311f708e5099bdc831376..452122c5650c0c0fea7960887ad0edaff8f1439d 100644
--- a/common/source/java/ch/systemsx/cisd/common/filesystem/rsync/RsyncCopier.java
+++ b/common/source/java/ch/systemsx/cisd/common/filesystem/rsync/RsyncCopier.java
@@ -429,7 +429,7 @@ public final class RsyncCopier implements IPathCopier, IDirectoryImmutableCopier
         assert sourcePath != null;
         assert sourceHostOrNull != null || sourcePath.exists() : logNonExistent(sourcePath);
         assert destinationDirectory != null;
-        assert destinationHostOrNull != null || destinationDirectory.isDirectory() : logNonExistent(sourcePath);
+        assert destinationHostOrNull != null || destinationDirectory.isDirectory() : logNonExistent(destinationDirectory);
         // Only one side can be remote
         assert sourceHostOrNull == null || destinationHostOrNull == null;
         final List<String> commandLine =
diff --git a/common/source/java/ch/systemsx/cisd/common/process/CallableExecutor.java b/common/source/java/ch/systemsx/cisd/common/process/CallableExecutor.java
index 65d6cdda658bb3575540c1369c389ea8de7fa6d2..86a4aa18c0ee8914559ac9c2b0b80f98128db661 100644
--- a/common/source/java/ch/systemsx/cisd/common/process/CallableExecutor.java
+++ b/common/source/java/ch/systemsx/cisd/common/process/CallableExecutor.java
@@ -29,20 +29,20 @@ import ch.systemsx.cisd.common.exceptions.StopException;
  */
 public final class CallableExecutor
 {
-    private final int maxInvocationsOnFailure;
+    private final int maxRetriesOnFailure;
 
     private final long millisToSleepOnFailure;
 
     public CallableExecutor()
     {
-        this(Constants.MAXIMUM_INVOCATIONS_ON_FAILURE, Constants.MILLIS_TO_SLEEP_BEFORE_RETRYING);
+        this(Constants.MAXIMUM_RETRY_COUNT, Constants.MILLIS_TO_SLEEP_BEFORE_RETRYING);
     }
 
-    public CallableExecutor(final int maxInvocationsOnFailure, final long millisToSleepOnFailure)
+    public CallableExecutor(final int maxRetriesOnFailure, final long millisToSleepOnFailure)
     {
         assert millisToSleepOnFailure > -1 : "Negative value";
-        assert maxInvocationsOnFailure > -1 : "Negative value";
-        this.maxInvocationsOnFailure = maxInvocationsOnFailure;
+        assert maxRetriesOnFailure > -1 : "Negative value";
+        this.maxRetriesOnFailure = maxRetriesOnFailure;
         this.millisToSleepOnFailure = millisToSleepOnFailure;
     }
 
@@ -63,12 +63,12 @@ public final class CallableExecutor
                 if (result == null)
                 {
                     ++counter;
-                    if (counter < maxInvocationsOnFailure && millisToSleepOnFailure > 0)
+                    if (counter < maxRetriesOnFailure && millisToSleepOnFailure > 0)
                     {
                         Thread.sleep(millisToSleepOnFailure);
                     }
                 }
-            } while (result == null && counter < maxInvocationsOnFailure);
+            } while (result == null && counter < maxRetriesOnFailure);
         } catch (final Exception ex)
         {
             throw CheckedExceptionTunnel.wrapIfNecessary(ex);
diff --git a/common/source/java/ch/systemsx/cisd/common/process/ProcessExecutionHelper.java b/common/source/java/ch/systemsx/cisd/common/process/ProcessExecutionHelper.java
index 23d8c03c34fe52f7071a0780709b54fa70abebad..9eeebac027bf4a055228c83086a6d5b8d8a1cff3 100644
--- a/common/source/java/ch/systemsx/cisd/common/process/ProcessExecutionHelper.java
+++ b/common/source/java/ch/systemsx/cisd/common/process/ProcessExecutionHelper.java
@@ -352,7 +352,10 @@ public final class ProcessExecutionHelper
                 }
             } catch (final Exception ex)
             {
-                machineLog.error("Exception when launching: " + ex.getMessage());
+                machineLog.error(String
+                        .format("Exception when launching: [%s, %s]",
+                                ex.getClass().getSimpleName(), StringUtils.defaultIfEmpty(ex
+                                        .getMessage(), "NO MESSAGE")));
                 throw ex;
             }
         }
@@ -524,8 +527,7 @@ public final class ProcessExecutionHelper
     private ExecutionResult<ProcessResult> getExecutionResult(
             final Future<ProcessResult> runnerFuture)
     {
-        return ConcurrencyUtilities.getResult(runnerFuture, millisToWaitForCompletion, false, null,
-                null);
+        return ConcurrencyUtilities.getResult(runnerFuture, millisToWaitForCompletion, false, null);
     }
 
     private Future<ProcessResult> launchProcessExecutor()
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/AbstractCopyActivitySensor.java b/common/source/java/ch/systemsx/cisd/common/utilities/AbstractCopyActivitySensor.java
index b1caa579be806ae443b6d3f99172ee8315fc97d2..4d4ff64712d8454e1c72043bec255d5d77e29d2e 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/AbstractCopyActivitySensor.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/AbstractCopyActivitySensor.java
@@ -19,17 +19,17 @@ package ch.systemsx.cisd.common.utilities;
 import org.apache.commons.lang.time.DurationFormatUtils;
 import org.apache.log4j.Logger;
 
-import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IActivitySensor;
+import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IDescribingActivitySensor;
 import ch.systemsx.cisd.common.exceptions.StatusFlag;
 import ch.systemsx.cisd.common.exceptions.StatusWithResult;
 
 /**
- * A super class for {@link IActivitySensor}s that sense changes in some sort of copy operation to
- * a "target".
+ * A super class for {@link IDescribingActivitySensor}s that sense changes in some sort of copy
+ * operation to a "target".
  * 
  * @author Bernd Rinn
  */
-public abstract class AbstractCopyActivitySensor implements IActivitySensor
+public abstract class AbstractCopyActivitySensor implements IDescribingActivitySensor
 {
     protected static final int DEFAULT_MAX_ERRORS_TO_IGNORE = 3;
 
@@ -72,7 +72,7 @@ public abstract class AbstractCopyActivitySensor implements IActivitySensor
      * Returns a textual description of the target.
      */
     protected abstract String getTargetDescription();
-    
+
     /**
      * Returns the machine log for the concrete implementation.
      */
@@ -82,7 +82,7 @@ public abstract class AbstractCopyActivitySensor implements IActivitySensor
     // IActivitySensor
     //
 
-    public long getTimeOfLastActivityMoreRecentThan(long thresholdMillis)
+    public long getLastActivityMillisMoreRecentThan(long thresholdMillis)
     {
         currentResult = getTargetTimeOfLastActivityMoreRecentThan(thresholdMillis);
         final long now = System.currentTimeMillis();
@@ -92,13 +92,16 @@ public abstract class AbstractCopyActivitySensor implements IActivitySensor
             if (errorCount <= maxErrorsToIgnore)
             {
                 timeOfLastReportedActivity = now;
-                getMachineLog().error(describeInactivity(now)
-                        + String.format(" (error count: %d <= %d, goes unreported)", errorCount,
-                                maxErrorsToIgnore));
+                getMachineLog().error(
+                        describeInactivity(now)
+                                + String.format(" (error count: %d <= %d, goes unreported)",
+                                        errorCount, maxErrorsToIgnore));
             } else
             {
-                getMachineLog().error(describeInactivity(now)
-                        + String.format(" (error count: %s, reported to monitor)", errorCount));
+                getMachineLog().error(
+                        describeInactivity(now)
+                                + String.format(" (error count: %s, reported to monitor)",
+                                        errorCount));
             }
         } else
         {
@@ -122,6 +125,16 @@ public abstract class AbstractCopyActivitySensor implements IActivitySensor
         return timeOfLastReportedActivity;
     }
 
+    public boolean hasActivityMoreRecentThan(long thresholdMillis)
+    {
+        final long now = System.currentTimeMillis();
+        return (now - getLastActivityMillisMoreRecentThan(thresholdMillis)) < thresholdMillis;
+    }
+
+    //
+    // IDescribingActivitySensor
+    //
+    
     public String describeInactivity(long now)
     {
         if (currentResult.isError())
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 e10f0ff37f6d7e87e108d2ecacaf162143bf1b35..8bc2c79c7e9d7018341fd83d3bf1fded86a0f134 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java
@@ -32,6 +32,7 @@ import org.apache.log4j.Logger;
 
 import ch.systemsx.cisd.common.collections.CollectionUtils;
 import ch.systemsx.cisd.common.exceptions.Status;
+import ch.systemsx.cisd.common.exceptions.StopException;
 import ch.systemsx.cisd.common.logging.ConditionalNotificationLogger;
 import ch.systemsx.cisd.common.logging.ISimpleLogger;
 import ch.systemsx.cisd.common.logging.LogCategory;
@@ -166,7 +167,7 @@ public final class DirectoryScanningTimerTask extends TimerTask implements ITime
             final IPathHandler pathHandler, final int ignoredErrorCount)
     {
         this(asScannedStore(sourceDirectory, fileFilter), new FaultyPathDirectoryScanningHandler(
-                sourceDirectory), PathHandlerAdapter
+                sourceDirectory, pathHandler), PathHandlerAdapter
                 .asScanningHandler(sourceDirectory, pathHandler), ignoredErrorCount);
     }
 
@@ -234,7 +235,7 @@ public final class DirectoryScanningTimerTask extends TimerTask implements ITime
                 for (int i = 0; i < numberOfItems; i++)
                 {
                     final StoreItem storeItem = storeItems[i];
-                    if (stopRun)
+                    if (stopRun || Thread.interrupted() || storeHandler.isStopped())
                     {
                         if (operationLog.isDebugEnabled())
                         {
@@ -262,6 +263,10 @@ public final class DirectoryScanningTimerTask extends TimerTask implements ITime
                             ++numberOfItemsProcessedInLastRound;
                         } catch (final Exception ex)
                         {
+                            if (ex instanceof StopException)
+                            {
+                                break;
+                            }
                             // Do not stop when processing of one file has failed,
                             // continue with other files.
                             errorLog.put(storeItem, String.format(
@@ -300,7 +305,10 @@ public final class DirectoryScanningTimerTask extends TimerTask implements ITime
             } while (numberOfItemsProcessedInLastRound > 0);
         } catch (final Exception ex)
         {
-            printNotification(ex);
+            if (ex instanceof StopException == false)
+            {
+                printNotification(ex);
+            }
         }
         if (operationLog.isTraceEnabled())
         {
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/FastHardLinkMaker.java b/common/source/java/ch/systemsx/cisd/common/utilities/FastHardLinkMaker.java
index 969a88462cf6275195b71a0ee1bb0c3e11578c53..87a4b12d04419149e38c9c57d8959b02476fc2a2 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/FastHardLinkMaker.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/FastHardLinkMaker.java
@@ -17,12 +17,11 @@
 package ch.systemsx.cisd.common.utilities;
 
 import java.io.File;
-import java.util.concurrent.Callable;
 
+import ch.systemsx.cisd.common.TimingParameters;
 import ch.systemsx.cisd.common.concurrent.MonitoringProxy;
+import ch.systemsx.cisd.common.exceptions.WrappedIOException;
 import ch.systemsx.cisd.common.filesystem.FileLinkUtilities;
-import ch.systemsx.cisd.common.filesystem.FileLinkUtilities.FileLinkException;
-import ch.systemsx.cisd.common.process.CallableExecutor;
 
 /**
  * A {@link IFileImmutableCopier} that uses a native method to create hard links.
@@ -45,7 +44,7 @@ public class FastHardLinkMaker implements IFileImmutableCopier
                     FileLinkUtilities.createHardLink(source.getAbsolutePath(), destination
                             .getAbsolutePath());
                     return true;
-                } catch (FileLinkException ex)
+                } catch (WrappedIOException ex)
                 {
                     return false;
                 }
@@ -53,8 +52,8 @@ public class FastHardLinkMaker implements IFileImmutableCopier
         };
 
     /**
-     * Returns <code>true</code>, if the native library could be initialized successfully and
-     * thus this clase is operational, or <code>false</code> otherwise.
+     * Returns <code>true</code>, if the native library could be initialized successfully and thus
+     * this class is operational, or <code>false</code> otherwise.
      */
     public final static boolean isOperational()
     {
@@ -64,51 +63,51 @@ public class FastHardLinkMaker implements IFileImmutableCopier
     /**
      * Creates an {@link IFileImmutableCopier}.
      * 
-     * @param millisToWaitForCompletion The time out in milli-seconds to wait for one link creation
-     *            to finish.
-     * @param maxRetryOnFailure Maximal number of retries in case of failure.
-     * @param millisToSleepOnFailure Time in milli-seconds to sleep after a failure has occured and
-     *            before trying it again.
+     * @param timingParameters The timing parameters used to monitor and potentially retry the hard
+     *            link creation.
      * @return The copier, if the native library could be initialized successfully, or
      *         <code>null</code> otherwise.
      */
-    public final static IFileImmutableCopier tryCreate(final long millisToWaitForCompletion,
-            final int maxRetryOnFailure, final long millisToSleepOnFailure)
+    public final static IFileImmutableCopier tryCreate(final TimingParameters timingParameters)
     {
         if (FileLinkUtilities.isOperational() == false)
         {
             return null;
         }
-        return new FastHardLinkMaker(millisToWaitForCompletion, maxRetryOnFailure,
-                millisToSleepOnFailure);
+        return new FastHardLinkMaker(timingParameters);
 
     }
 
-    private final CallableExecutor retryingExecutor;
+    /**
+     * Creates an {@link IFileImmutableCopier} with default timing pameters (uses
+     * {@link TimingParameters#getDefaultParameters()}.
+     * 
+     * @return The copier, if the native library could be initialized successfully, or
+     *         <code>null</code> otherwise.
+     */
+    public final static IFileImmutableCopier tryCreate()
+    {
+        if (FileLinkUtilities.isOperational() == false)
+        {
+            return null;
+        }
+        return new FastHardLinkMaker(TimingParameters.getDefaultParameters());
+
+    }
 
     private final IFileImmutableCopier monitoringProxy;
 
-    private FastHardLinkMaker(final long millisToWaitForCompletion, final int maxRetryOnFailure,
-            final long millisToSleepOnFailure)
+    private FastHardLinkMaker(final TimingParameters timingParameters)
     {
-        retryingExecutor = new CallableExecutor(maxRetryOnFailure, millisToSleepOnFailure);
         monitoringProxy =
-                MonitoringProxy.create(IFileImmutableCopier.class, nativeCopier).timeoutMillis(
-                        millisToWaitForCompletion).get();
+                MonitoringProxy.create(IFileImmutableCopier.class, nativeCopier).timing(
+                        timingParameters).get();
     }
 
     public boolean copyFileImmutably(final File file, final File destinationDirectory,
             final String nameOrNull)
     {
-        final Callable<Boolean> creatorCallable = new Callable<Boolean>()
-            {
-                public Boolean call() throws Exception
-                {
-                    return monitoringProxy
-                            .copyFileImmutably(file, destinationDirectory, nameOrNull);
-                }
-            };
-        return retryingExecutor.executeCallable(creatorCallable);
+        return monitoringProxy.copyFileImmutably(file, destinationDirectory, nameOrNull);
     }
 
 }
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/FastRecursiveHardLinkMaker.java b/common/source/java/ch/systemsx/cisd/common/utilities/FastRecursiveHardLinkMaker.java
index 8c7a0f56703ab09b50b893851ffbd5cbe11886aa..af972a9093918bb71c94782a629352cc5a19bf07 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/FastRecursiveHardLinkMaker.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/FastRecursiveHardLinkMaker.java
@@ -20,6 +20,7 @@ import java.io.File;
 
 import org.apache.log4j.Logger;
 
+import ch.systemsx.cisd.common.TimingParameters;
 import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
@@ -38,80 +39,59 @@ public class FastRecursiveHardLinkMaker implements IImmutableCopier
 
     private static final String RSYNC_EXEC = "rsync";
 
-    private final static long MILLIS = 1000L;
-
-    private final static long DEFAULT_INACTIVITY_TRESHOLD_MILLIS = 300 * MILLIS;
-
     private final static int DEFAULT_MAX_ERRORS_TO_IGNORE = 3;
 
-    private final static int DEFAULT_MAX_ATTEMPTS = 10;
-
-    private final static long DEFAULT_TIME_TO_SLEEP_AFTER_COPY_FAILS = 5 * MILLIS;
-
     private final IImmutableCopier fallbackCopierOrNull;
 
     private final IFileImmutableCopier fastFileCopierOrNull;
 
     private final IDirectoryImmutableCopier fastDirectoryCopierOrNull;
 
-    public final static IImmutableCopier tryCreate(final File rsyncExecutable,
-            boolean neverUseNative)
+    public final static IImmutableCopier tryCreate()
     {
-        return tryCreate(rsyncExecutable, DEFAULT_INACTIVITY_TRESHOLD_MILLIS, DEFAULT_MAX_ATTEMPTS,
-                DEFAULT_TIME_TO_SLEEP_AFTER_COPY_FAILS, neverUseNative);
+        return tryCreate(TimingParameters.getDefaultParameters());
     }
 
-    public final static IImmutableCopier tryCreate(final File rsyncExecutable)
+    public final static IImmutableCopier tryCreate(final TimingParameters timingParameters)
     {
-        return tryCreate(rsyncExecutable, DEFAULT_INACTIVITY_TRESHOLD_MILLIS, DEFAULT_MAX_ATTEMPTS,
-                DEFAULT_TIME_TO_SLEEP_AFTER_COPY_FAILS);
+        final File rsyncExecOrNull = OSUtilities.findExecutable(RSYNC_EXEC);
+        if (rsyncExecOrNull == null)
+        {
+            return null;
+        }
+        return create(rsyncExecOrNull, timingParameters);
     }
 
-    public final static IImmutableCopier tryCreate(final long millisToWaitForCompletion,
-            final int maxRetryOnFailure, final long millisToSleepOnFailure)
+    public final static IImmutableCopier create(final File rsyncExecutable,
+            final TimingParameters parameters)
     {
-        return tryCreate(OSUtilities.findExecutable(RSYNC_EXEC), millisToWaitForCompletion,
-                maxRetryOnFailure, millisToSleepOnFailure);
+        return create(rsyncExecutable, parameters, false);
     }
 
-    public final static IImmutableCopier tryCreate(final File rsyncExecutable,
-            final long millisToWaitForCompletion, final int maxRetryOnFailure,
-            final long millisToSleepOnFailure)
+    public final static IImmutableCopier create(final File rsyncExecutable)
     {
-        return tryCreate(rsyncExecutable, millisToWaitForCompletion, maxRetryOnFailure,
-                millisToSleepOnFailure, false);
+        return create(rsyncExecutable, TimingParameters.getDefaultParameters(), false);
     }
 
-    public final static IImmutableCopier tryCreate(final File rsyncExecutable,
-            final long millisToWaitForCompletion, final int maxRetryOnFailure,
-            final long millisToSleepOnFailure, final boolean neverUseNative)
+    public final static IImmutableCopier create(final File rsyncExecutable,
+            final TimingParameters parameters, final boolean neverUseNative)
     {
-        try
-        {
-            return new FastRecursiveHardLinkMaker(rsyncExecutable, millisToSleepOnFailure,
-                    maxRetryOnFailure, millisToSleepOnFailure, neverUseNative);
-        } catch (ConfigurationFailureException ex)
-        {
-            return null;
-        }
+        return new FastRecursiveHardLinkMaker(rsyncExecutable, parameters, neverUseNative);
     }
 
     private FastRecursiveHardLinkMaker(final File rsyncExcutable,
-            final long inactivityThresholdMillis, final int maxRetryOnFailure,
-            final long millisToSleepOnFailure, final boolean neverUseNative)
+            final TimingParameters timingParameters, final boolean neverUseNative)
             throws ConfigurationFailureException
     {
         this.fastFileCopierOrNull =
-                neverUseNative ? null : FastHardLinkMaker.tryCreate(inactivityThresholdMillis,
-                        maxRetryOnFailure, millisToSleepOnFailure);
+                neverUseNative ? null : FastHardLinkMaker.tryCreate(timingParameters);
         this.fastDirectoryCopierOrNull =
-                new RsyncBasedRecursiveHardLinkMaker(rsyncExcutable, inactivityThresholdMillis,
-                        DEFAULT_MAX_ERRORS_TO_IGNORE, maxRetryOnFailure, millisToSleepOnFailure);
+                new RsyncBasedRecursiveHardLinkMaker(rsyncExcutable, timingParameters,
+                        DEFAULT_MAX_ERRORS_TO_IGNORE);
         if (fastFileCopierOrNull == null)
         {
             this.fallbackCopierOrNull =
-                    RecursiveHardLinkMaker.tryCreate(HardLinkMaker.tryCreateRetrying(
-                            inactivityThresholdMillis, maxRetryOnFailure, millisToSleepOnFailure));
+                    RecursiveHardLinkMaker.tryCreate(HardLinkMaker.tryCreate(timingParameters));
         } else
         {
             this.fallbackCopierOrNull = RecursiveHardLinkMaker.tryCreate(fastFileCopierOrNull);
@@ -123,6 +103,7 @@ public class FastRecursiveHardLinkMaker implements IImmutableCopier
         }
         if (operationLog.isInfoEnabled())
         {
+            operationLog.info(timingParameters.toString());
             if (fastFileCopierOrNull != null)
             {
                 operationLog.info("Using native library to create hard link copies of files.");
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 b7e6c5d78b01d0a695c70beb7f416ff1b2cf1e22..a0f712b4c568eb2774bac6c9e6c4655661ea47b9 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/FaultyPathDirectoryScanningHandler.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/FaultyPathDirectoryScanningHandler.java
@@ -51,10 +51,14 @@ public final class FaultyPathDirectoryScanningHandler implements IDirectoryScann
 
     private long faultyPathsLastChanged;
 
-    public FaultyPathDirectoryScanningHandler(final File faultyPathDirectory)
+    private IStopSignaler stopSignaler;
+
+    public FaultyPathDirectoryScanningHandler(final File faultyPathDirectory,
+            final IStopSignaler stopSignaler)
     {
         this.faultyPaths = new HashSet<String>();
         this.faultyPathsFile = new File(faultyPathDirectory, Constants.FAULTY_PATH_FILENAME);
+        this.stopSignaler = stopSignaler;
     }
 
     private final void checkForFaultyPathsFileChanged()
@@ -148,7 +152,7 @@ public final class FaultyPathDirectoryScanningHandler implements IDirectoryScann
     {
         // 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))
+        if (scannedStore.existsOrError(storeItem) && stopSignaler.isStopped() == false)
         {
             addToFaultyPaths(scannedStore, storeItem);
             return Status.createError("Failed to move item '%s'.", storeItem);
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/FileUtilities.java b/common/source/java/ch/systemsx/cisd/common/utilities/FileUtilities.java
index c6dd265ab01eb2ec2089197e2e512ca24ca6ab57..13344ac004d2eadd69a5c732739f52b63f7b7bdc 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/FileUtilities.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/FileUtilities.java
@@ -20,7 +20,6 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileFilter;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.FileWriter;
@@ -35,6 +34,7 @@ import java.text.DecimalFormat;
 import java.text.NumberFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -45,11 +45,16 @@ import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.comparator.LastModifiedFileComparator;
 import org.apache.commons.lang.CharUtils;
 import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.time.DurationFormatUtils;
 
+import ch.systemsx.cisd.common.concurrent.IActivityObserver;
+import ch.systemsx.cisd.common.concurrent.RecordingActivityObserverSensor;
+import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IDescribingActivitySensor;
 import ch.systemsx.cisd.common.exceptions.CheckedExceptionTunnel;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
 import ch.systemsx.cisd.common.exceptions.StopException;
 import ch.systemsx.cisd.common.exceptions.UnknownLastChangedException;
+import ch.systemsx.cisd.common.exceptions.WrappedIOException;
 import ch.systemsx.cisd.common.logging.ISimpleLogger;
 import ch.systemsx.cisd.common.logging.LogLevel;
 import ch.systemsx.cisd.common.parser.filter.AlwaysAcceptLineFilter;
@@ -64,7 +69,8 @@ import ch.systemsx.cisd.common.parser.filter.ILineFilter;
  * <p>
  * If you are tempted to add new functionality to this class, ensure that the new functionality does
  * not yet exist in <code>org.apache.common.io.FileUtils</code>, see <a
- * href="http://jakarta.apache.org/commons/io/api-release/org/apache/commons/io/FileUtils.html">javadoc</a>.
+ * href="http://jakarta.apache.org/commons/io/api-release/org/apache/commons/io/FileUtils.html"
+ * >javadoc</a>.
  * 
  * @author Bernd Rinn
  */
@@ -96,7 +102,7 @@ public final class FileUtilities
      * @throws EnvironmentFailureException if a {@link IOException} occured.
      */
     public static void copyFileTo(final File sourceFile, final File destinationFile,
-            final boolean preservesLastModifiedDate) throws CheckedExceptionTunnel
+            final boolean preservesLastModifiedDate) throws WrappedIOException
     {
         FileInputStream inputStream = null;
         FileOutputStream outputStream = null;
@@ -133,10 +139,10 @@ public final class FileUtilities
      *            is not <code>null</code>.
      * @return The content of the file. All newline characters are '\n' (Unix convention). Never
      *         returns <code>null</code>.
-     * @throws CheckedExceptionTunnel for wrapping an {@link IOException}, e.g. if the file does
-     *             not exist.
+     * @throws WrappedIOException for wrapping an {@link IOException}, e.g. if the file does not
+     *             exist.
      */
-    public static String loadToString(final File file) throws CheckedExceptionTunnel
+    public static String loadToString(final File file) throws WrappedIOException
     {
         assert file != null;
 
@@ -147,7 +153,7 @@ public final class FileUtilities
             return readString(new BufferedReader(fileReader));
         } catch (final IOException ex)
         {
-            throw new CheckedExceptionTunnel(ex);
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
         } finally
         {
             IOUtils.closeQuietly(fileReader);
@@ -161,10 +167,10 @@ public final class FileUtilities
      *            is not <code>null</code>.
      * @return The content of the file. All newline characters are '\n' (Unix convention). Never
      *         returns <code>null</code>.
-     * @throws CheckedExceptionTunnel for wrapping an {@link IOException}, e.g. if the file does
-     *             not exist.
+     * @throws WrappedIOException for wrapping an {@link IOException}, e.g. if the file does not
+     *             exist.
      */
-    public static String loadExactToString(final File file) throws CheckedExceptionTunnel
+    public static String loadExactToString(final File file) throws WrappedIOException
     {
         assert file != null;
 
@@ -175,7 +181,7 @@ public final class FileUtilities
             return readExactString(new BufferedReader(fileReader));
         } catch (final IOException ex)
         {
-            throw new CheckedExceptionTunnel(ex);
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
         } finally
         {
             IOUtils.closeQuietly(fileReader);
@@ -185,9 +191,9 @@ public final class FileUtilities
     /**
      * Writes the specified string to the specified file.
      * 
-     * @throws CheckedExceptionTunnel for wrapping an {@link IOException}.
+     * @throws WrappedIOException for wrapping an {@link IOException}.
      */
-    public static void writeToFile(final File file, final String str) throws CheckedExceptionTunnel
+    public static void writeToFile(final File file, final String str) throws WrappedIOException
     {
         assert file != null : "Unspecified file.";
         assert str != null : "Unspecified string.";
@@ -199,7 +205,7 @@ public final class FileUtilities
             fileWriter.write(str);
         } catch (final IOException ex)
         {
-            throw new CheckedExceptionTunnel(ex);
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
         } finally
         {
             IOUtils.closeQuietly(fileWriter);
@@ -212,11 +218,10 @@ public final class FileUtilities
      * @param file the file that should be loaded. This method asserts that given <code>File</code>
      *            is not <code>null</code>.
      * @return The content of the file line by line.
-     * @throws CheckedExceptionTunnel for wrapping an {@link IOException}, e.g. if the file does
-     *             not exist.
+     * @throws WrappedIOException for wrapping an {@link IOException}, e.g. if the file does not
+     *             exist.
      */
-    public final static List<String> loadToStringList(final File file)
-            throws CheckedExceptionTunnel
+    public final static List<String> loadToStringList(final File file) throws WrappedIOException
     {
         return loadToStringList(file, null);
     }
@@ -229,11 +234,11 @@ public final class FileUtilities
      * @param lineFilterOrNull a line filter if you are not interested in all lines. May be
      *            <code>null</code>.
      * @return The content of the file line by line.
-     * @throws CheckedExceptionTunnel for wrapping an {@link IOException}, e.g. if the file does
-     *             not exist.
+     * @throws WrappedIOException for wrapping an {@link IOException}, e.g. if the file does not
+     *             exist.
      */
     public final static List<String> loadToStringList(final File file,
-            final ILineFilter lineFilterOrNull) throws CheckedExceptionTunnel
+            final ILineFilter lineFilterOrNull) throws WrappedIOException
     {
         assert file != null : "Unspecified file.";
 
@@ -244,7 +249,7 @@ public final class FileUtilities
             return readStringList(new BufferedReader(fileReader), lineFilterOrNull);
         } catch (final IOException ex)
         {
-            throw new CheckedExceptionTunnel(ex);
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
         } finally
         {
             IOUtils.closeQuietly(fileReader);
@@ -261,12 +266,12 @@ public final class FileUtilities
      *            <code>null</code>).
      * @param resource Absolute path of the resource (will be the argument of
      *            <code>getResource()</code>).
-     * @return The content of the resource, or <code>null</code> if the specified resource does
-     *         not exist.
-     * @throws CheckedExceptionTunnel for wrapping an {@link IOException}
+     * @return The content of the resource, or <code>null</code> if the specified resource does not
+     *         exist.
+     * @throws WrappedIOException for wrapping an {@link IOException}
      */
     public static String loadToString(final Class<?> clazz, final String resource)
-            throws CheckedExceptionTunnel
+            throws WrappedIOException
     {
         assert clazz != null : "Given class can not be null.";
         assert resource != null && resource.length() > 0 : "Given resource can not be null.";
@@ -278,7 +283,7 @@ public final class FileUtilities
             return reader == null ? null : readString(reader);
         } catch (final IOException ex)
         {
-            throw new CheckedExceptionTunnel(ex);
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
         } finally
         {
             IOUtils.closeQuietly(reader);
@@ -295,11 +300,11 @@ public final class FileUtilities
      * @param resource absolute path of the resource (will be the argument of
      *            <code>getResource()</code>).
      * @return The content of the resource line by line.
-     * @throws CheckedExceptionTunnel for wrapping an {@link IOException}, e.g. if the file does
-     *             not exist.
+     * @throws WrappedIOException for wrapping an {@link IOException}, e.g. if the file does not
+     *             exist.
      */
     public final static List<String> loadToStringList(final Class<?> clazz, final String resource)
-            throws CheckedExceptionTunnel
+            throws WrappedIOException
     {
         return loadToStringList(clazz, resource, null);
     }
@@ -317,11 +322,11 @@ public final class FileUtilities
      *            <code>null</code>.
      * @return The content of the resource line by line or <code>null</code> if given
      *         <var>resource</var> can not be found.
-     * @throws CheckedExceptionTunnel for wrapping an {@link IOException}, e.g. if the file does
-     *             not exist.
+     * @throws WrappedIOException for wrapping an {@link IOException}, e.g. if the file does not
+     *             exist.
      */
     public final static List<String> loadToStringList(final Class<?> clazz, final String resource,
-            final ILineFilter lineFilterOrNull) throws CheckedExceptionTunnel
+            final ILineFilter lineFilterOrNull) throws WrappedIOException
     {
         assert clazz != null : "Given class can not be null.";
         assert StringUtils.isNotEmpty(resource) : "Given resource can not be empty.";
@@ -333,7 +338,7 @@ public final class FileUtilities
             return reader == null ? null : readStringList(reader, lineFilterOrNull);
         } catch (final IOException ex)
         {
-            throw new CheckedExceptionTunnel(ex);
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
         } finally
         {
             IOUtils.closeQuietly(reader);
@@ -341,7 +346,7 @@ public final class FileUtilities
     }
 
     private final static BufferedReader tryGetBufferedReader(final Class<?> clazz,
-            final String resource) throws FileNotFoundException
+            final String resource)
     {
         final URL url = clazz.getResource(resource);
         if (url == null)
@@ -351,9 +356,12 @@ public final class FileUtilities
         try
         {
             return new BufferedReader(new FileReader(new File(url.toURI())));
+        } catch (final IOException ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
         } catch (final URISyntaxException ex)
         {
-            throw new CheckedExceptionTunnel(ex);
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
         }
     }
 
@@ -407,8 +415,7 @@ public final class FileUtilities
     }
 
     /**
-     * Checks whether a <var>path</var> of some <var>kind</var> is fully accessible to the
-     * program.
+     * Checks whether a <var>path</var> of some <var>kind</var> is fully accessible to the program.
      * 
      * @param kindOfPath description of given <var>path</var>. Mainly used for error messages.
      * @return <code>null</code> if the <var>directory</var> is fully accessible and an error
@@ -442,8 +449,8 @@ public final class FileUtilities
      * Checks whether a <var>directory</var> of some <var>kind</var> is accessible for reading to
      * the program (it's a directory, you can read and write in it)
      * 
-     * @return <code>null</code> if the <var>directory</var> is accessible for reading and an
-     *         error message describing the problem with the <var>directory</var> otherwise.
+     * @return <code>null</code> if the <var>directory</var> is accessible for reading and an error
+     *         message describing the problem with the <var>directory</var> otherwise.
      */
     public static String checkDirectoryReadAccessible(final File directory,
             final String kindOfDirectory)
@@ -504,8 +511,8 @@ public final class FileUtilities
     }
 
     /**
-     * Checks whether a <var>file</var> of some <var>kindOfFile</var> is accessible for reading
-     * and writing to the program (so it's a file and you can read and write it)
+     * Checks whether a <var>file</var> of some <var>kindOfFile</var> is accessible for reading and
+     * writing to the program (so it's a file and you can read and write it)
      * 
      * @return <code>null</code> if the <var>file</var> is fully accessible and an error message
      *         describing the problem with the <var>file</var> otherwise.
@@ -552,14 +559,25 @@ public final class FileUtilities
     }
 
     /**
-     * A simple role that can observe activity.
+     * A class that monitors activity on recursive delete processes.
      */
-    public interface SimpleActivityObserver
+    public static class DeleteActivityDetector extends RecordingActivityObserverSensor implements
+            IDescribingActivitySensor
     {
-        /**
-         * Called when activity occurred.
-         */
-        public void update();
+        private final File path;
+
+        public DeleteActivityDetector(File path)
+        {
+            super();
+            this.path = path;
+        }
+
+        public String describeInactivity(long now)
+        {
+            return "No delete activity of path " + path.getPath() + " for "
+                    + DurationFormatUtils.formatDurationHMS(now - getLastActivityMillis());
+        }
+
     }
 
     /**
@@ -570,10 +588,11 @@ public final class FileUtilities
      * <code>null</code>.
      * 
      * @param path Path of the file or directory to delete.
-     * @return <code>true</code> if the path has been delete successfully, <code>false</code>
+     * @return <code>true</code> if the path has been deleted successfully, <code>false</code>
      *         otherwise.
+     * @throws StopException If the current thread has been interrupted.
      */
-    public static boolean deleteRecursively(final File path)
+    public static boolean deleteRecursively(final File path) throws StopException
     {
         assert path != null;
 
@@ -587,10 +606,12 @@ public final class FileUtilities
      * @param path Path of the file or directory to delete.
      * @param loggerOrNull The logger that should be used to log deletion of path entries, or
      *            <code>null</code> if nothing should be logged.
-     * @return <code>true</code> if the path has been delete successfully, <code>false</code>
+     * @return <code>true</code> if the path has been deleted successfully, <code>false</code>
      *         otherwise.
+     * @throws StopException If the current thread has been interrupted.
      */
     public static boolean deleteRecursively(final File path, final ISimpleLogger loggerOrNull)
+            throws StopException
     {
         return deleteRecursively(path, loggerOrNull, null);
     }
@@ -602,15 +623,16 @@ public final class FileUtilities
      * @param path Path of the file or directory to delete.
      * @param loggerOrNull The logger that should be used to log deletion of path entries, or
      *            <code>null</code> if nothing should be logged.
-     * @param observerOrNull If not <code>null</code>, will be updated on progress in the
-     *            deletion. This can be used to find out whether a (potentially) long-running
-     *            recursive deletion call is alive-and-kicking or hangs (e.g. due to a remote
-     *            directory becoming unresponsive).
-     * @return <code>true</code> if the path has been delete successfully, <code>false</code>
+     * @param observerOrNull If not <code>null</code>, will be updated on progress in the deletion.
+     *            This can be used to find out whether a (potentially) long-running recursive
+     *            deletion call is alive-and-kicking or hangs (e.g. due to a remote directory
+     *            becoming unresponsive).
+     * @return <code>true</code> if the path has been deleted successfully, <code>false</code>
      *         otherwise.
+     * @throws StopException If the current thread has been interrupted.
      */
     public static boolean deleteRecursively(final File path, final ISimpleLogger loggerOrNull,
-            final SimpleActivityObserver observerOrNull)
+            final IActivityObserver observerOrNull) throws StopException
     {
         assert path != null;
 
@@ -618,6 +640,10 @@ public final class FileUtilities
         {
             for (final File file : path.listFiles())
             {
+                if (Thread.interrupted())
+                {
+                    throw new StopException();
+                }
                 if (observerOrNull != null)
                 {
                     observerOrNull.update();
@@ -655,9 +681,10 @@ public final class FileUtilities
      * @param logger The logger that should be used to log deletion of path entries, or
      *            <code>null</code> if nothing should be logged.
      * @return <code>true</code> if the <var>path</var> itself has been deleted.
+     * @throws StopException If the current thread has been interrupted.
      */
     public static boolean deleteRecursively(final File path, final FileFilter filter,
-            final ISimpleLogger logger)
+            final ISimpleLogger logger) throws StopException
     {
         assert path != null;
 
@@ -674,14 +701,16 @@ public final class FileUtilities
      * @param filter The {@link FileFilter} to use when deciding which paths to delete.
      * @param logger The logger that should be used to log deletion of path entries, or
      *            <code>null</code> if nothing should be logged.
-     * @param observerOrNull If not <code>null</code>, will be updated on progress in the
-     *            deletion. This can be used to find out whether a (potentially) long-running
-     *            recursive deletion call is alive-and-kicking or hangs (e.g. due to a remote
-     *            directory becoming unresponsive).
+     * @param observerOrNull If not <code>null</code>, will be updated on progress in the deletion.
+     *            This can be used to find out whether a (potentially) long-running recursive
+     *            deletion call is alive-and-kicking or hangs (e.g. due to a remote directory
+     *            becoming unresponsive).
      * @return <code>true</code> if the <var>path</var> itself has been deleted.
+     * @throws StopException If the current thread has been interrupted.
      */
     public static boolean deleteRecursively(final File path, final FileFilter filter,
-            final ISimpleLogger logger, final SimpleActivityObserver observerOrNull)
+            final ISimpleLogger logger, final IActivityObserver observerOrNull)
+            throws StopException
     {
         assert path != null;
         assert filter != null;
@@ -695,6 +724,10 @@ public final class FileUtilities
             {
                 for (final File file : path.listFiles())
                 {
+                    if (Thread.interrupted())
+                    {
+                        throw new StopException();
+                    }
                     if (observerOrNull != null)
                     {
                         observerOrNull.update();
@@ -813,22 +846,21 @@ public final class FileUtilities
     }
 
     /**
-     * Determines the time (in milliseconds since start of the epoch) when any item below <var>path</var>
-     * has last been changed in the file system.
+     * Determines the time (in milliseconds since start of the epoch) when any item below
+     * <var>path</var> has last been changed in the file system.
      * 
      * @param path The path (file or directory) to check for last change.
-     * @param subDirectoriesOnly If <code>true</code>, only subdirectories of <var>path</var>
-     *            are checked, if <var>path</var> is a directory. If <var>path</var> is a file,
-     *            this parameter is ignored. When considering what this parameter is good for, note
-     *            that the mtime of a directory is changed when an entry in the directory changes.
+     * @param subDirectoriesOnly If <code>true</code>, only subdirectories of <var>path</var> are
+     *            checked, if <var>path</var> is a directory. If <var>path</var> is a file, this
+     *            parameter is ignored. When considering what this parameter is good for, note that
+     *            the mtime of a directory is changed when an entry in the directory changes.
      * @param stopWhenFindYounger If &gt; 0, the recursive search for younger file will be stopped
      *            when a file or directory is found that is younger than the time specified in this
      *            parameter. Supposed to be used when one does not care about the absolute youngest
      *            entry, but only, if there are entries that are "young enough".
      * @return The time when any file in (or below) <var>path</var> has last been changed in the
      *         file system.
-     * @throws UnknownLastChangedException if the <var>path</var> does not exist or is not
-     *             readable.
+     * @throws UnknownLastChangedException if the <var>path</var> does not exist or is not readable.
      * @throws StopException if the thread that the method runs in gets interrupted.
      */
     public static long lastChanged(final File path, final boolean subDirectoriesOnly,
@@ -839,23 +871,22 @@ public final class FileUtilities
     }
 
     /**
-     * Determines the time (in milliseconds since start of the epoch) when any item below <var>path</var>
-     * has last been changed in the file system.
+     * Determines the time (in milliseconds since start of the epoch) when any item below
+     * <var>path</var> has last been changed in the file system.
      * 
      * @param path The path (file or directory) to check for last change.
-     * @param subDirectoriesOnly If <code>true</code>, only subdirectories of <var>path</var>
-     *            are checked, if <var>path</var> is a directory. If <var>path</var> is a file,
-     *            this parameter is ignored. When considering what this parameter is good for, note
-     *            that the mtime of a directory is changed when an entry in the directory changes.
+     * @param subDirectoriesOnly If <code>true</code>, only subdirectories of <var>path</var> are
+     *            checked, if <var>path</var> is a directory. If <var>path</var> is a file, this
+     *            parameter is ignored. When considering what this parameter is good for, note that
+     *            the mtime of a directory is changed when an entry in the directory changes.
      * @param stopWhenFindYoungerRelative If &gt; 0, the recursive search for younger file will be
      *            stopped when a file or directory is found that is younger than
-     *            <code>System.currentTimeMillis() - stopWhenYoungerRelative</code>. Supposed to
-     *            be used when one does not care about the absolute youngest entry, but only, if
-     *            there are entries that are "young enough".
+     *            <code>System.currentTimeMillis() - stopWhenYoungerRelative</code>. Supposed to be
+     *            used when one does not care about the absolute youngest entry, but only, if there
+     *            are entries that are "young enough".
      * @return The time when any file in (or below) <var>path</var> has last been changed in the
      *         file system.
-     * @throws UnknownLastChangedException if the <var>path</var> does not exist or is not
-     *             readable.
+     * @throws UnknownLastChangedException if the <var>path</var> does not exist or is not readable.
      * @throws StopException if the thread that the method runs in gets interrupted.
      */
     public static long lastChangedRelative(final File path, final boolean subDirectoriesOnly,
@@ -868,8 +899,7 @@ public final class FileUtilities
     /**
      * @return The time when any file in (or below) <var>path</var> has last been changed in the
      *         file system.
-     * @throws UnknownLastChangedException if the <var>path</var> does not exist or is not
-     *             readable.
+     * @throws UnknownLastChangedException if the <var>path</var> does not exist or is not readable.
      * @throws StopException if the thread that the method runs in gets interrupted.
      */
     public static long lastChanged(final File path) throws UnknownLastChangedException
@@ -919,9 +949,9 @@ public final class FileUtilities
      * 
      * @param defaultFileNameOrNull the default name for the new file if the digit pattern could not
      *            be found in its name. If empty then "1" will be appended to its name.
-     * @param regexOrNull pattern to find out the counter. If <code>null</code> then a default (<code>(\\d+)</code>)
-     *            will be used. The given <var>regex</var> must contain <code>(\\d+)</code> or
-     *            <code>([0-9]+)</code>.
+     * @param regexOrNull pattern to find out the counter. If <code>null</code> then a default (
+     *            <code>(\\d+)</code>) will be used. The given <var>regex</var> must contain
+     *            <code>(\\d+)</code> or <code>([0-9]+)</code>.
      */
     public final static File createNextNumberedFile(final File path, final Pattern regexOrNull,
             final String defaultFileNameOrNull)
@@ -1006,8 +1036,8 @@ public final class FileUtilities
      * 
      * @param directory the directory to list
      * @param loggerOrNull logger, if <code>null</code> than no logging occurs
-     * @return all files in <var>directory</var> or <code>null</code>, if <var>directory</var>
-     *         does not exist or is not a directory.
+     * @return all files in <var>directory</var> or <code>null</code>, if <var>directory</var> does
+     *         not exist or is not a directory.
      */
     public static File[] tryListFiles(final File directory, final ISimpleLogger loggerOrNull)
     {
@@ -1019,7 +1049,7 @@ public final class FileUtilities
      * 
      * @param directory the directory to list
      * @param filterOrNull only files matching this filter will show up in the result, if it is not
-     *            <code>null</code>.
+     *            <code>null</code>
      * @param loggerOrNull logger, if <code>null</code> than no logging occurs
      * @return all files in <var>directory</var> that match the filter, or <code>null</code>, if
      *         <var>directory</var> does not exist or is not a directory.
@@ -1081,10 +1111,10 @@ public final class FileUtilities
      * @param postfix The postfix to use for the temporary name.
      * @return The name of the temporary file.
      * @throws IllegalArgumentException If the resource cannot be found in the class path.
-     * @throws CheckedExceptionTunnel If an {@link IOException} occurs.
+     * @throws WrappedIOException If an {@link IOException} occurs.
      */
     public final static String copyResourceToTempFile(final String resource, final String prefix,
-            final String postfix)
+            final String postfix) throws WrappedIOException
     {
         final InputStream resourceStream = FileUtilities.class.getResourceAsStream(resource);
         if (resourceStream == null)
@@ -1106,7 +1136,7 @@ public final class FileUtilities
             return tempFile.getAbsolutePath();
         } catch (final IOException ex)
         {
-            throw new CheckedExceptionTunnel(ex);
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
         } finally
         {
             IOUtils.closeQuietly(resourceStream);
@@ -1133,8 +1163,8 @@ public final class FileUtilities
      * Loads the native library <var>libraryName</var> from a Java resource that follows tha naming
      * convention described in {@link #tryCopyNativeLibraryToTempFile(String)}.
      * 
-     * @return <code>true</code> if the library has been loaded successfully and
-     *         <code>false</code> otherwise.
+     * @return <code>true</code> if the library has been loaded successfully and <code>false</code>
+     *         otherwise.
      */
     public final static boolean loadNativeLibraryFromResource(final String libraryName)
     {
@@ -1200,6 +1230,226 @@ public final class FileUtilities
         return fileList;
     }
 
+    /**
+     * A {@link FileFilter} that matches against a list of file extensions and that
+     * 
+     * @author Bernd Rinn
+     */
+    private static final class ExtensionFileFilter implements FileFilter
+    {
+        private final String[] extensionsOrNull;
+
+        private final IActivityObserver observerOrNull;
+
+        private final boolean recursive;
+
+        ExtensionFileFilter(String[] extensionsOrNull, boolean recursive,
+                IActivityObserver observerOrNull)
+        {
+            this.extensionsOrNull = extensionsOrNull;
+            this.recursive = recursive;
+            this.observerOrNull = observerOrNull;
+        }
+
+        private boolean correctType(File file)
+        {
+            // Small optimization: if recursive, we know alrady that file.isDirectory() == false.
+            return recursive || file.isFile();
+        }
+
+        private boolean match(String extensionFound)
+        {
+            if (extensionsOrNull == null)
+            {
+                return true;
+            }
+            if (extensionFound.length() == 0)
+            {
+                return false;
+            }
+            for (String ext : extensionsOrNull)
+            {
+                if (extensionFound.equals(ext))
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public boolean accept(File file)
+        {
+            if (observerOrNull != null)
+            {
+                observerOrNull.update();
+            }
+            if (recursive && file.isDirectory())
+            {
+                return true; // We need to traverse directories in any case.
+            }
+            return correctType(file) && match(FilenameUtils.getExtension(file.getName()));
+        }
+
+    }
+
+    /**
+     * A {@link FileFilter} that matches against a list of file extensions and that
+     * 
+     * @author Bernd Rinn
+     */
+    private static final class DirectoryFilter implements FileFilter
+    {
+        private final IActivityObserver observerOrNull;
+
+        DirectoryFilter(IActivityObserver observerOrNull)
+        {
+            this.observerOrNull = observerOrNull;
+        }
+
+        public boolean accept(File pathname)
+        {
+            if (observerOrNull != null)
+            {
+                observerOrNull.update();
+            }
+            return pathname.isDirectory();
+        }
+
+    }
+
+    /**
+     * A {@link FileFilter} that matches against a list of file extensions and that
+     * 
+     * @author Bernd Rinn
+     */
+    private static final class TrueFilter implements FileFilter
+    {
+        private final IActivityObserver observerOrNull;
+
+        TrueFilter(IActivityObserver observerOrNull)
+        {
+            this.observerOrNull = observerOrNull;
+        }
+
+        public boolean accept(File pathname)
+        {
+            if (observerOrNull != null)
+            {
+                observerOrNull.update();
+            }
+            return true;
+        }
+
+    }
+
+    /**
+     * Finds files within a given directory (and optionally its subdirectories) which match an array
+     * of extensions.
+     * 
+     * @param directory The directory to search in.
+     * @param extensionsOrNull An array of extensions, ex. {"java","xml"}. If this parameter is
+     *            <code>null</code>, all files are returned.
+     * @param recursive If true all subdirectories are searched as well.
+     * @param observerOrNull If not <code>null</code>, will be updated on progress of file
+     *            gathering. This can be used to find out whether a (potentially) long-running file
+     *            gathering call is alive-and-kicking or hangs (e.g. due to a remote directory
+     *            becoming unresponsive).
+     * @return A list of java.io.File (all files) with the matching files, or an empty list, if
+     *         <var>directory</var> ist not a directory.
+     */
+    public static List<File> listFiles(File directory, String[] extensionsOrNull,
+            boolean recursive, IActivityObserver observerOrNull)
+    {
+        assert directory != null;
+
+        final List<File> result = new LinkedList<File>();
+        internalListFiles(directory, result, new ExtensionFileFilter(extensionsOrNull, recursive,
+                observerOrNull), observerOrNull, recursive, FType.FILE);
+        return result;
+    }
+
+    /**
+     * Finds directories within a given directory (and optionally its subdirectories).
+     * 
+     * @param directory The directory to search in.
+     * @param recursive If true all subdirectories are searched as well.
+     * @param observerOrNull If not <code>null</code>, will be updated on progress of file
+     *            gathering. This can be used to find out whether a (potentially) long-running file
+     *            gathering call is alive-and-kicking or hangs (e.g. due to a remote directory
+     *            becoming unresponsive).
+     * @return A list of java.io.File (all directories), or an empty list, if <var>directory</var>
+     *         ist not a directory.
+     */
+    public static List<File> listDirectories(File directory, boolean recursive,
+            IActivityObserver observerOrNull)
+    {
+        assert directory != null;
+
+        final List<File> result = new LinkedList<File>();
+        internalListFiles(directory, result, new DirectoryFilter(observerOrNull), observerOrNull,
+                recursive, FType.DIRECTORY);
+        return result;
+    }
+
+    /**
+     * Finds files and directories within a given directory (and optionally its subdirectories).
+     * 
+     * @param directory The directory to search in.
+     * @param recursive If true all subdirectories are searched as well.
+     * @param observerOrNull If not <code>null</code>, will be updated on progress of file
+     *            gathering. This can be used to find out whether a (potentially) long-running file
+     *            gathering call is alive-and-kicking or hangs (e.g. due to a remote directory
+     *            becoming unresponsive).
+     * @return A list of java.io.File (all directories), or an empty list, if <var>directory</var>
+     *         ist not a directory.
+     */
+    public static List<File> listFilesAndDirectories(File directory, boolean recursive,
+            IActivityObserver observerOrNull)
+    {
+        assert directory != null;
+
+        final List<File> result = new LinkedList<File>();
+        internalListFiles(directory, result, new TrueFilter(observerOrNull), observerOrNull,
+                recursive, FType.EITHER);
+        return result;
+    }
+
+    private enum FType
+    {
+        FILE, DIRECTORY, EITHER
+    }
+
+    private static void internalListFiles(File directory, List<File> result, FileFilter filter,
+            IActivityObserver observerOrNull, boolean recursive, FType ftype)
+    {
+        final File[] filteredFilesAndDirectories = directory.listFiles(filter);
+        if (filteredFilesAndDirectories == null)
+        {
+            return;
+        }
+        for (File f : filteredFilesAndDirectories)
+        {
+            if (observerOrNull != null)
+            {
+                observerOrNull.update();
+            }
+            if (f.isDirectory())
+            {
+                if (ftype != FType.FILE)
+                {
+                    result.add(f);
+                }
+                if (recursive)
+                {
+                    internalListFiles(f, result, filter, observerOrNull, recursive, ftype);
+                }
+            } else if (ftype != FType.DIRECTORY)
+            {
+                result.add(f);
+            }
+        }
+    }
+
     /**
      * Normalizes given <var>file</var> path, removing double and single dot path steps.
      * <p>
@@ -1243,8 +1493,8 @@ public final class FileUtilities
      * Returns a human-readable version of the file size, where the input represents a specific
      * number of bytes.
      * <p>
-     * By comparison with {@link FileUtils#byteCountToDisplaySize(long)}, the output of this
-     * version is more exact.
+     * By comparison with {@link FileUtils#byteCountToDisplaySize(long)}, the output of this version
+     * is more exact.
      * </p>
      * 
      * @param size the number of bytes
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/HardLinkMaker.java b/common/source/java/ch/systemsx/cisd/common/utilities/HardLinkMaker.java
index 755384d207655fb6a1a8cb18d86bb24040f8f77f..104dea9df03d5f1d7ed2be496e3f2c825f49739b 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/HardLinkMaker.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/HardLinkMaker.java
@@ -25,6 +25,7 @@ import java.util.concurrent.Callable;
 import org.apache.commons.io.FileUtils;
 import org.apache.log4j.Logger;
 
+import ch.systemsx.cisd.common.TimingParameters;
 import ch.systemsx.cisd.common.exceptions.StopException;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
@@ -32,6 +33,8 @@ import ch.systemsx.cisd.common.process.CallableExecutor;
 import ch.systemsx.cisd.common.process.ProcessExecutionHelper;
 
 /**
+ * A class for creating hard links based on the Unix 'ln' program.
+ * 
  * @author Bernd Rinn
  */
 public class HardLinkMaker implements IFileImmutableCopier
@@ -46,111 +49,80 @@ public class HardLinkMaker implements IFileImmutableCopier
 
     private final String linkExecPath;
 
-    private final RetryingOperationTimeout singleFileLinkTimeout;
-
-    private static class RetryingOperationTimeout
-    {
-        private final long millisToWaitForCompletion;
-
-        private final int maxRetryOnFailure;
-
-        private final long millisToSleepOnFailure;
-
-        private RetryingOperationTimeout(long millisToWaitForCompletion, int maxRetryOnFailure,
-                long millisToSleepOnFailure)
-        {
-            this.millisToWaitForCompletion = millisToWaitForCompletion;
-            this.maxRetryOnFailure = maxRetryOnFailure;
-            this.millisToSleepOnFailure = millisToSleepOnFailure;
-        }
-
-        public long getMillisToWaitForCompletion()
-        {
-            return millisToWaitForCompletion;
-        }
-
-        public int getMaxRetryOnFailure()
-        {
-            return maxRetryOnFailure;
-        }
-
-        public long getMillisToSleepOnFailure()
-        {
-            return millisToSleepOnFailure;
-        }
-    }
-
-    private HardLinkMaker(final String linkExecPath,
-            RetryingOperationTimeout singleFileLinkTimeoutOrNull)
-    {
-        this.linkExecPath = linkExecPath;
-        this.singleFileLinkTimeout =
-                singleFileLinkTimeoutOrNull != null ? singleFileLinkTimeoutOrNull
-                        : createNoTimeout();
-    }
+    private final TimingParameters timingParameters;
 
     //
     // Factory methods
     //
 
     /**
-     * Creates copier which won't retry an operation if it fails.
+     * Creates copier trying to find the path to the <code>ln</code> executable.
      * 
-     * @param linkExecPath The path to the <code>ln</code> executable.
+     * @param timingParameters The timing parameters to use when monitoring the call to 'ln'.
+     * @return <code>null</code> if the <code>ln</code> executable was not found.
      */
-    public static final IFileImmutableCopier create(final String linkExecPath)
+    public static final IFileImmutableCopier tryCreate(TimingParameters timingParameters)
     {
-        return new HardLinkMaker(linkExecPath, null);
+        final File lnExec = OSUtilities.findExecutable(HARD_LINK_EXEC);
+        if (lnExec == null)
+        {
+            return null;
+        }
+        return create(lnExec, timingParameters);
     }
 
     /**
-     * Creates copier trying to find the path to the <code>ln</code> executable.
+     * Creates copier trying to find the path to the <code>ln</code> executable. Uses default timing
+     * parameters.
      * 
      * @return <code>null</code> if the <code>ln</code> executable was not found.
+     * @see TimingParameters#getDefaultParameters()
      */
     public static final IFileImmutableCopier tryCreate()
     {
-        return tryCreate(null);
+        final File lnExec = OSUtilities.findExecutable(HARD_LINK_EXEC);
+        if (lnExec == null)
+        {
+            return null;
+        }
+        return create(lnExec);
     }
 
     /**
-     * Creates copier which is able to retry the operation of creating the hard link of a file if
-     * it does not complete after a specified timeout.
+     * Creates copier which is able to retry the operation of creating the hard link of a file if it
+     * does not complete after a specified timeout.
      * 
-     * @param millisToWaitForCompletion The time to wait for the process creating one hard link to a
-     *            file to complete in milli seconds. If the process is not finished after that time,
-     *            it will be terminated.
-     * @param maxRetryOnFailure The number of times we should try to create each hard link if copy
-     *            operation fails.
-     * @param millisToSleepOnFailure The number of milliseconds we should wait before re-executing
-     *            the copy of a single file. Specify 0 to wait till the first operation completes.
+     * @param lnExec The executable of the 'ln' program.
+     * @param timingParameters The timing parameters to use when monitoring the call to 'ln'.
      */
-    public static final IFileImmutableCopier tryCreateRetrying(
-            final long millisToWaitForCompletion, final int maxRetryOnFailure,
-            final long millisToSleepOnFailure)
+    public static final IFileImmutableCopier create(final File lnExec,
+            final TimingParameters timingParameters)
     {
-        RetryingOperationTimeout timeout =
-                new RetryingOperationTimeout(millisToWaitForCompletion, maxRetryOnFailure,
-                        millisToSleepOnFailure);
-        return tryCreate(timeout);
+        return new HardLinkMaker(lnExec.getAbsolutePath(), timingParameters);
     }
 
-    private static final IFileImmutableCopier tryCreate(
-            RetryingOperationTimeout singleFileLinkTimeoutOrNull)
+    /**
+     * Creates copier which is able to retry the operation of creating the hard link of a file if it
+     * does not complete after a specified timeout. Uses default timing parameters.
+     * 
+     * @param lnExec The executable of the 'ln' program.
+     * @see TimingParameters#getDefaultParameters()
+     */
+    public static final IFileImmutableCopier create(final File lnExec)
     {
-        final File lnExec = OSUtilities.findExecutable(HARD_LINK_EXEC);
-        if (lnExec == null)
-        {
-            return null;
-        }
-        return new HardLinkMaker(lnExec.getAbsolutePath(), singleFileLinkTimeoutOrNull);
+        return new HardLinkMaker(lnExec.getAbsolutePath(), TimingParameters.getDefaultParameters());
     }
 
-    private RetryingOperationTimeout createNoTimeout()
+    private HardLinkMaker(final String linkExecPath, TimingParameters timingParameters)
     {
-        return new RetryingOperationTimeout(0, 1, 0);
+        this.linkExecPath = linkExecPath;
+        this.timingParameters = timingParameters;
     }
 
+    //
+    // IFileImutableCopier
+    //
+
     public boolean copyFileImmutably(final File source, final File destinationDirectory,
             final String nameOrNull)
     {
@@ -165,9 +137,9 @@ public class HardLinkMaker implements IFileImmutableCopier
                 {
                     boolean result =
                             ProcessExecutionHelper.runAndLog(cmd, operationLog, machineLog,
-                                    singleFileLinkTimeout.getMillisToWaitForCompletion());
-                    // NOTE: we have noticed that sometimes the result is false although the file
-                    // have been copied
+                                    timingParameters.getTimeoutMillis());
+                    // NOTE: we have noticed that in some environments sometimes the result is
+                    // false although the file have been copied
                     if (result == false && destFile.exists()
                             && checkIfIdenticalContent(source, destFile))
                     {
@@ -182,8 +154,8 @@ public class HardLinkMaker implements IFileImmutableCopier
                 }
             };
         boolean ok =
-                runRepeatableProcess(processTask, singleFileLinkTimeout.getMaxRetryOnFailure(),
-                        singleFileLinkTimeout.getMillisToSleepOnFailure());
+                runRepeatableProcess(processTask, timingParameters.getMaxRetriesOnFailure(),
+                        timingParameters.getIntervalToWaitAfterFailureMillis());
         return ok;
     }
 
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/IPathHandler.java b/common/source/java/ch/systemsx/cisd/common/utilities/IPathHandler.java
index 3380e2f31d489474eb6c0f791bbcb55725274bde..eada9474a2befeae599a3b7913a3676410314b7a 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/IPathHandler.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/IPathHandler.java
@@ -25,7 +25,7 @@ import java.io.File;
  * @see IStoreHandler
  * @author Bernd Rinn
  */
-public interface IPathHandler
+public interface IPathHandler extends IStopSignaler
 {
     /**
      * Handles the <var>path</var>. Successful handling is indicated by <var>path</var> being gone
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/IStopSignaler.java b/common/source/java/ch/systemsx/cisd/common/utilities/IStopSignaler.java
new file mode 100644
index 0000000000000000000000000000000000000000..6563a0b8e20a64a57eb0567269b09b09f6472963
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/IStopSignaler.java
@@ -0,0 +1,36 @@
+/*
+ * 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.common.utilities;
+
+/**
+ * A role that allows to signal that a worker was stopped. 
+ *
+ * @author Bernd Rinn
+ */
+public interface IStopSignaler
+{
+
+    /**
+     * Returns <code>true</code>, if the signaler has been stopped and <code>false</code> otherwise.
+     * <p>
+     * Not throwing a StopException, but signaling back that the worker was stopped by means of
+     * this method implies that it was prepared to handle the StopException and clean up
+     * after itself.
+     */
+    boolean isStopped();
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/IStoreHandler.java b/common/source/java/ch/systemsx/cisd/common/utilities/IStoreHandler.java
index 0ff94bca6176c7ab7ba3abc252a523e982462ce2..34fa6c35087c5d3581aa0d1ee015ea2349aacc16 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/IStoreHandler.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/IStoreHandler.java
@@ -16,23 +16,21 @@
 
 package ch.systemsx.cisd.common.utilities;
 
-import java.io.File;
-
 /**
  * Handles items in the file store.
  * <p>
  * Note that this interface is a higher abstraction of {@link IPathHandler} which works with
- * {@link File}.
+ * {@link java.io.File}.
  * </p>
  * 
  * @see IPathHandler
  * @author Tomasz Pylak
  */
-public interface IStoreHandler
+public interface IStoreHandler extends IStopSignaler
 {
     /**
-     * Handles given <var>item</var>. Successful handling is indicated by <var>item</var> being
-     * gone when the method returns.
+     * Handles given <var>item</var>. Successful handling is indicated by <var>item</var> being gone
+     * when the method returns.
      */
     void handle(StoreItem item);
 }
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/PathHandlerAdapter.java b/common/source/java/ch/systemsx/cisd/common/utilities/PathHandlerAdapter.java
index 43e0a20ce9d5492b94f8ea8fd45bfb2e63dd4b2d..808a8c5dabc4a722b1bd606778a21658fbfee604 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/PathHandlerAdapter.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/PathHandlerAdapter.java
@@ -54,4 +54,9 @@ public class PathHandlerAdapter implements IStoreHandler
         pathHandler.handle(asFile(item));
     }
 
+    public boolean isStopped()
+    {
+        return pathHandler.isStopped();
+    }
+
 }
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/RemoteDirectoryCopyActivitySensor.java b/common/source/java/ch/systemsx/cisd/common/utilities/RemoteDirectoryCopyActivitySensor.java
index 6cf609f25445c89e805ca9ee8c9cfe831b0ec6e8..6a702dc3f78903b4076e817e53f652d0ecae1cf6 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/RemoteDirectoryCopyActivitySensor.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/RemoteDirectoryCopyActivitySensor.java
@@ -20,14 +20,14 @@ import java.io.File;
 
 import org.apache.log4j.Logger;
 
-import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IActivitySensor;
+import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IDescribingActivitySensor;
 import ch.systemsx.cisd.common.exceptions.StatusWithResult;
 import ch.systemsx.cisd.common.exceptions.UnknownLastChangedException;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 
 /**
- * A {@link IActivitySensor} that senses changes in copy operations to a directory.
+ * A {@link IDescribingActivitySensor} that senses changes in copy operations to a directory.
  * 
  * @author Bernd Rinn
  */
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/RsyncBasedRecursiveHardLinkMaker.java b/common/source/java/ch/systemsx/cisd/common/utilities/RsyncBasedRecursiveHardLinkMaker.java
index 5ae3bcb668394978c15fa46a2c921bed4d09d5af..bdf367fa33987ddea1b8e0604640c5cd57e0a779 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/RsyncBasedRecursiveHardLinkMaker.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/RsyncBasedRecursiveHardLinkMaker.java
@@ -20,6 +20,7 @@ import java.io.File;
 
 import org.apache.log4j.Logger;
 
+import ch.systemsx.cisd.common.TimingParameters;
 import ch.systemsx.cisd.common.concurrent.ConcurrencyUtilities;
 import ch.systemsx.cisd.common.concurrent.InactivityMonitor;
 import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IInactivityObserver;
@@ -40,24 +41,12 @@ public class RsyncBasedRecursiveHardLinkMaker implements IDirectoryImmutableCopi
     private static final Logger machineLog =
             LogFactory.getLogger(LogCategory.MACHINE, RsyncBasedRecursiveHardLinkMaker.class);
 
-    private final static long MILLIS = 1000L;
-
-    private final static long DEFAULT_INACTIVITY_TRESHOLD_MILLIS = 300 * MILLIS;
-
     private final static int DEFAULT_MAX_ERRORS_TO_IGNORE = 3;
 
-    private final static int DEFAULT_MAX_ATTEMPTS = 10;
-
-    private final static long DEFAULT_TIME_TO_SLEEP_AFTER_COPY_FAILS = 5 * MILLIS;
-
-    private final long inactivityThresholdMillis;
+    private final TimingParameters timingParameters;
 
     private final int maxErrorsToIgnore;
 
-    private final int maxAttempts;
-
-    private final long timeToSleepAfterCopyFails;
-
     private final RsyncCopier rsyncCopier;
 
     public interface ILastChangedChecker
@@ -67,20 +56,17 @@ public class RsyncBasedRecursiveHardLinkMaker implements IDirectoryImmutableCopi
 
     public RsyncBasedRecursiveHardLinkMaker()
     {
-        this(null, DEFAULT_INACTIVITY_TRESHOLD_MILLIS, DEFAULT_MAX_ERRORS_TO_IGNORE,
-                DEFAULT_MAX_ATTEMPTS, DEFAULT_TIME_TO_SLEEP_AFTER_COPY_FAILS);
+        this(null, TimingParameters.getDefaultParameters(), DEFAULT_MAX_ERRORS_TO_IGNORE);
     }
 
     public RsyncBasedRecursiveHardLinkMaker(File rsyncExecutableOrNull)
     {
-        this(rsyncExecutableOrNull, DEFAULT_INACTIVITY_TRESHOLD_MILLIS,
-                DEFAULT_MAX_ERRORS_TO_IGNORE, DEFAULT_MAX_ATTEMPTS,
-                DEFAULT_TIME_TO_SLEEP_AFTER_COPY_FAILS);
+        this(rsyncExecutableOrNull, TimingParameters.getDefaultParameters(),
+                DEFAULT_MAX_ERRORS_TO_IGNORE);
     }
 
     public RsyncBasedRecursiveHardLinkMaker(File rsyncExecutableOrNull,
-            long inactivityThresholdMillis, int maxErrorsToIgnore, int maxAttempts,
-            long timeToSleepAfterCopyFails)
+            TimingParameters timingParameters, int maxErrorsToIgnore)
     {
         if (rsyncExecutableOrNull == null)
         {
@@ -89,12 +75,14 @@ public class RsyncBasedRecursiveHardLinkMaker implements IDirectoryImmutableCopi
         {
             rsyncCopier = new RsyncCopier(rsyncExecutableOrNull);
         }
-        this.inactivityThresholdMillis = inactivityThresholdMillis;
-        this.timeToSleepAfterCopyFails = timeToSleepAfterCopyFails;
+        this.timingParameters = timingParameters;
         this.maxErrorsToIgnore = maxErrorsToIgnore;
-        this.maxAttempts = maxAttempts;
     }
 
+    //
+    // IDirectoryImmutableCopier
+    //
+    
     public boolean copyDirectoryImmutably(final File sourceDirectory,
             final File destinationDirectory, final String targetNameOrNull)
     {
@@ -110,19 +98,19 @@ public class RsyncBasedRecursiveHardLinkMaker implements IDirectoryImmutableCopi
                 }
             };
         boolean ok;
-        int attempt = 0;
+        int counter = 0;
         while (true)
         {
             ok =
                     createHardLinks(sourceDirectory, destinationDirectory, targetNameOrNull,
                             target, observer);
-            if (ok || attempt++ > maxAttempts)
+            if (ok || counter++ > timingParameters.getMaxRetriesOnFailure())
             {
                 break;
             }
             if (ok == false)
             {
-                ConcurrencyUtilities.sleep(timeToSleepAfterCopyFails);
+                ConcurrencyUtilities.sleep(timingParameters.getIntervalToWaitAfterFailureMillis());
             }
         }
         return ok;
@@ -133,7 +121,7 @@ public class RsyncBasedRecursiveHardLinkMaker implements IDirectoryImmutableCopi
     {
         final InactivityMonitor monitor =
                 new InactivityMonitor(new RemoteDirectoryCopyActivitySensor(target,
-                        maxErrorsToIgnore), observer, inactivityThresholdMillis, true);
+                        maxErrorsToIgnore), observer, timingParameters.getTimeoutMillis(), true);
         final boolean result =
                 rsyncCopier.copyDirectoryImmutably(sourceDirectory, destinationDirectory,
                         targetNameOrNull);
diff --git a/common/source/java/org/apache/commons/io/FileCopyUtils.java b/common/source/java/org/apache/commons/io/FileCopyUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..5b3594e2da6a549c246c593d24da1fd315fe3e33
--- /dev/null
+++ b/common/source/java/org/apache/commons/io/FileCopyUtils.java
@@ -0,0 +1,562 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.io;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import ch.systemsx.cisd.common.concurrent.IActivityObserver;
+
+/**
+ * General file copying utilities.
+ * <p>
+ * Origin of code: Excalibur, Alexandria, Commons-Utils, CISD common
+ * 
+ * @author <a href="mailto:burton@relativity.yi.org">Kevin A. Burton</A>
+ * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
+ * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
+ * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph.Reck</a>
+ * @author <a href="mailto:peter@apache.org">Peter Donald</a>
+ * @author <a href="mailto:jefft@apache.org">Jeff Turner</a>
+ * @author Matthew Hawthorne
+ * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
+ * @author Stephen Colebourne
+ * @author Ian Springer
+ * @author Chris Eldredge
+ * @author Jim Harrington
+ * @author Niall Pemberton
+ * @author Sandy McArthur
+ * @author Bernd Rinn
+ */
+public class FileCopyUtils
+{
+
+    private FileCopyUtils()
+    {
+        // Not to be instantiated.
+    }
+
+    /**
+     * Copies a file or directory to a new location, preserving the file date.
+     * <p>
+     * This method copies the contents of the specified source file to the specified destination
+     * file. The directory holding the destination file is created if it does not exist. If the
+     * destination file exists, then this method will overwrite it.
+     * 
+     * @param source an existing file to copy, must not be <code>null</code>
+     * @param destination the new file, must not be <code>null</code>
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws IOException if source or destination is invalid
+     * @throws IOException if an IO error occurs during copying
+     */
+    public static void copy(File source, File destination) throws IOException
+    {
+        if (source.isDirectory())
+        {
+            copyDirectory(source, destination);
+        } else
+        {
+            copyFile(source, destination);
+        }
+    }
+
+    /**
+     * Copies a file or directory to a new location, preserving the file date.
+     * <p>
+     * This method copies the contents of the specified source file to the specified destination
+     * file. The directory holding the destination file is created if it does not exist. If the
+     * destination file exists, then this method will overwrite it.
+     * 
+     * @param source an existing file to copy, must not be <code>null</code>
+     * @param destination the new file, must not be <code>null</code>
+     * @param observerOrNull activity observer of the copy process
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws IOException if source or destination is invalid
+     * @throws IOException if an IO error occurs during copying
+     */
+    public static void copy(File source, File destination, IActivityObserver observerOrNull)
+            throws IOException
+    {
+        if (source.isDirectory())
+        {
+            copyDirectory(source, destination, observerOrNull);
+        } else
+        {
+            copyFile(source, destination);
+        }
+    }
+
+    /**
+     * Copies a file or directory to a directory preserving the file or directory date.
+     * <p>
+     * This method copies the contents of the specified source file to a file of the same name in
+     * the specified destination directory. The destination directory is created if it does not
+     * exist. If the destination file exists, then this method will overwrite it.
+     * 
+     * @param source an existing file to copy, must not be <code>null</code>
+     * @param destDir the directory to place the copy in, must not be <code>null</code>
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws IOException if source or destination is invalid
+     * @throws IOException if an IO error occurs during copying
+     */
+    public static void copyToDirectory(File source, File destDir) throws IOException
+    {
+        if (source.isDirectory())
+        {
+            copyDirectoryToDirectory(source, destDir);
+        } else
+        {
+            copyFileToDirectory(source, destDir);
+        }
+    }
+
+    /**
+     * Copies a file or directory to a directory preserving the file or directory date.
+     * <p>
+     * This method copies the contents of the specified source file to a file of the same name in
+     * the specified destination directory. The destination directory is created if it does not
+     * exist. If the destination file exists, then this method will overwrite it.
+     * 
+     * @param source an existing file to copy, must not be <code>null</code>
+     * @param destination the directory to place the copy in, must not be <code>null</code>
+     * @param observerOrNull activity observer of the copy process
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws IOException if source or destination is invalid
+     * @throws IOException if an IO error occurs during copying
+     */
+    public static void copyToDirectory(File source, File destination,
+            IActivityObserver observerOrNull) throws IOException
+    {
+        if (source.isDirectory())
+        {
+            copyDirectoryToDirectory(source, destination, observerOrNull);
+        } else
+        {
+            copyFileToDirectory(source, destination);
+        }
+    }
+
+    /**
+     * Copies a file to a directory preserving the file date.
+     * <p>
+     * This method copies the contents of the specified source file to a file of the same name in
+     * the specified destination directory. The destination directory is created if it does not
+     * exist. If the destination file exists, then this method will overwrite it.
+     * 
+     * @param srcFile an existing file to copy, must not be <code>null</code>
+     * @param destDir the directory to place the copy in, must not be <code>null</code>
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws IOException if source or destination is invalid
+     * @throws IOException if an IO error occurs during copying
+     * @since Commons IO 1.3
+     */
+    public static void copyFileToDirectory(File srcFile, File destDir) throws IOException
+    {
+        if (destDir == null)
+        {
+            throw new NullPointerException("Destination must not be null");
+        }
+        if (destDir.exists() && destDir.isDirectory() == false)
+        {
+            throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory");
+        }
+        copyFile(srcFile, new File(destDir, srcFile.getName()));
+    }
+
+    /**
+     * Copies a file to a new location, preserving the file date.
+     * <p>
+     * This method copies the contents of the specified source file to the specified destination
+     * file. The directory holding the destination file is created if it does not exist. If the
+     * destination file exists, then this method will overwrite it.
+     * 
+     * @param srcFile an existing file to copy, must not be <code>null</code>
+     * @param destFile the new file, must not be <code>null</code>
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws IOException if source or destination is invalid
+     * @throws IOException if an IO error occurs during copying
+     */
+    public static void copyFile(File srcFile, File destFile) throws IOException
+    {
+        if (srcFile == null)
+        {
+            throw new NullPointerException("Source must not be null");
+        }
+        if (destFile == null)
+        {
+            throw new NullPointerException("Destination must not be null");
+        }
+        if (srcFile.exists() == false)
+        {
+            throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
+        }
+        if (srcFile.isDirectory())
+        {
+            throw new IOException("Source '" + srcFile + "' exists but is a directory");
+        }
+        if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath()))
+        {
+            throw new IOException("Source '" + srcFile + "' and destination '" + destFile
+                    + "' are the same");
+        }
+        if (destFile.getParentFile() != null && destFile.getParentFile().exists() == false)
+        {
+            if (destFile.getParentFile().mkdirs() == false)
+            {
+                throw new IOException("Destination '" + destFile + "' directory cannot be created");
+            }
+        }
+        if (destFile.exists() && destFile.canWrite() == false)
+        {
+            throw new IOException("Destination '" + destFile + "' exists but is read-only");
+        }
+        doCopyFile(srcFile, destFile);
+    }
+
+    /**
+     * Internal copy file method.
+     * 
+     * @param srcFile the validated source file, must not be <code>null</code>
+     * @param destFile the validated destination file, must not be <code>null</code>
+     * @throws IOException if an error occurs
+     */
+    private static void doCopyFile(File srcFile, File destFile) throws IOException
+    {
+        if (destFile.exists() && destFile.isDirectory())
+        {
+            throw new IOException("Destination '" + destFile + "' exists but is a directory");
+        }
+
+        FileInputStream input = new FileInputStream(srcFile);
+        try
+        {
+            FileOutputStream output = new FileOutputStream(destFile);
+            try
+            {
+                IOUtils.copy(input, output);
+            } finally
+            {
+                IOUtils.closeQuietly(output);
+            }
+        } finally
+        {
+            IOUtils.closeQuietly(input);
+        }
+
+        if (srcFile.length() != destFile.length())
+        {
+            throw new IOException("Failed to copy full contents from '" + srcFile + "' to '"
+                    + destFile + "'");
+        }
+        destFile.setLastModified(srcFile.lastModified());
+    }
+
+    /**
+     * Copies a directory to within another directory preserving the file dates.
+     * <p>
+     * This method copies the source directory and all its contents to a directory of the same name
+     * in the specified destination directory.
+     * <p>
+     * The destination directory is created if it does not exist. If the destination directory did
+     * exist, then this method merges the source with the destination, with the source taking
+     * precedence.
+     * 
+     * @param srcDir an existing directory to copy, must not be <code>null</code>
+     * @param destDir the directory to place the copy in, must not be <code>null</code>
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws IOException if source or destination is invalid
+     * @throws IOException if an IO error occurs during copying
+     * @since Commons IO 1.2
+     */
+    public static void copyDirectoryToDirectory(File srcDir, File destDir) throws IOException
+    {
+        copyDirectoryToDirectory(srcDir, destDir, null);
+    }
+
+    /**
+     * Copies a directory to within another directory preserving the file dates.
+     * <p>
+     * This method copies the source directory and all its contents to a directory of the same name
+     * in the specified destination directory.
+     * <p>
+     * The destination directory is created if it does not exist. If the destination directory did
+     * exist, then this method merges the source with the destination, with the source taking
+     * precedence.
+     * 
+     * @param srcDir an existing directory to copy, must not be <code>null</code>
+     * @param destDir the directory to place the copy in, must not be <code>null</code>
+     * @param observerOrNull activity observer of the copy process
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws IOException if source or destination is invalid
+     * @throws IOException if an IO error occurs during copying
+     * @since Commons IO 1.2
+     */
+    public static void copyDirectoryToDirectory(File srcDir, File destDir,
+            IActivityObserver observerOrNull) throws IOException
+    {
+        if (srcDir == null)
+        {
+            throw new NullPointerException("Source must not be null");
+        }
+        if (srcDir.exists() && srcDir.isDirectory() == false)
+        {
+            throw new IllegalArgumentException("Source '" + destDir + "' is not a directory");
+        }
+        if (destDir == null)
+        {
+            throw new NullPointerException("Destination must not be null");
+        }
+        if (destDir.exists() && destDir.isDirectory() == false)
+        {
+            throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory");
+        }
+        copyDirectory(srcDir, new File(destDir, srcDir.getName()), null, observerOrNull);
+    }
+
+    /**
+     * Copies a whole directory to a new location preserving the file dates.
+     * <p>
+     * This method copies the specified directory and all its child directories and files to the
+     * specified destination. The destination is the new location and name of the directory.
+     * <p>
+     * The destination directory is created if it does not exist. If the destination directory did
+     * exist, then this method merges the source with the destination, with the source taking
+     * precedence.
+     * 
+     * @param srcDir an existing directory to copy, must not be <code>null</code>
+     * @param destDir the new directory, must not be <code>null</code>
+     * @param observerOrNull activity observer of the copy process
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws IOException if source or destination is invalid
+     * @throws IOException if an IO error occurs during copying
+     * @since Commons IO 1.1
+     */
+    public static void copyDirectory(File srcDir, File destDir, IActivityObserver observerOrNull)
+            throws IOException
+    {
+        copyDirectory(srcDir, destDir, null, observerOrNull);
+    }
+
+    /**
+     * Copies a whole directory to a new location.
+     * <p>
+     * This method copies the contents of the specified source directory to within the specified
+     * destination directory.
+     * <p>
+     * The destination directory is created if it does not exist. If the destination directory did
+     * exist, then this method merges the source with the destination, with the source taking
+     * precedence.
+     * 
+     * @param srcDir an existing directory to copy, must not be <code>null</code>
+     * @param destDir the new directory, must not be <code>null</code>
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws IOException if source or destination is invalid
+     * @throws IOException if an IO error occurs during copying
+     * @since Commons IO 1.1
+     */
+    public static void copyDirectory(File srcDir, File destDir) throws IOException
+    {
+        copyDirectory(srcDir, destDir, (FileFilter) null);
+    }
+
+    /**
+     * Copies a filtered directory to a new location.
+     * <p>
+     * This method copies the contents of the specified source directory to within the specified
+     * destination directory.
+     * <p>
+     * The destination directory is created if it does not exist. If the destination directory did
+     * exist, then this method merges the source with the destination, with the source taking
+     * precedence.
+     * <h4>Example: Copy directories only</h4>
+     * 
+     * <pre>
+     * // only copy the directory structure
+     * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
+     * </pre>
+     * 
+     * <h4>Example: Copy directories and txt files</h4>
+     * 
+     * <pre>
+     * // Create a filter for &quot;.txt&quot; files
+     * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(&quot;.txt&quot;);
+     * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
+     * // Create a filter for either directories or &quot;.txt&quot; files
+     * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
+     * // Copy using the filter
+     * FileUtils.copyDirectory(srcDir, destDir, filter, false);
+     * </pre>
+     * 
+     * @param srcDir an existing directory to copy, must not be <code>null</code>
+     * @param destDir the new directory, must not be <code>null</code>
+     * @param filter the filter to apply, null means copy all directories and files
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws IOException if source or destination is invalid
+     * @throws IOException if an IO error occurs during copying
+     * @since Commons IO 1.4
+     */
+    public static void copyDirectory(File srcDir, File destDir, FileFilter filter)
+            throws IOException
+    {
+        copyDirectory(srcDir, destDir, filter, null);
+    }
+
+    /**
+     * Copies a filtered directory to a new location.
+     * <p>
+     * This method copies the contents of the specified source directory to within the specified
+     * destination directory.
+     * <p>
+     * The destination directory is created if it does not exist. If the destination directory did
+     * exist, then this method merges the source with the destination, with the source taking
+     * precedence.
+     * <h4>Example: Copy directories only</h4>
+     * 
+     * <pre>
+     * // only copy the directory structure
+     * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
+     * </pre>
+     * 
+     * <h4>Example: Copy directories and txt files</h4>
+     * 
+     * <pre>
+     * // Create a filter for &quot;.txt&quot; files
+     * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(&quot;.txt&quot;);
+     * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
+     * // Create a filter for either directories or &quot;.txt&quot; files
+     * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
+     * // Copy using the filter
+     * FileUtils.copyDirectory(srcDir, destDir, filter, false);
+     * </pre>
+     * 
+     * @param srcDir an existing directory to copy, must not be <code>null</code>
+     * @param destDir the new directory, must not be <code>null</code>
+     * @param filter the filter to apply, null means copy all directories and files
+     * @param observerOrNull activity observer of the copy process
+     * @throws NullPointerException if source or destination is <code>null</code>
+     * @throws IOException if source or destination is invalid
+     * @throws IOException if an IO error occurs during copying
+     * @since Commons IO 1.4
+     */
+    public static void copyDirectory(File srcDir, File destDir, FileFilter filter,
+            IActivityObserver observerOrNull) throws IOException
+    {
+        if (srcDir == null)
+        {
+            throw new NullPointerException("Source must not be null");
+        }
+        if (destDir == null)
+        {
+            throw new NullPointerException("Destination must not be null");
+        }
+        if (srcDir.exists() == false)
+        {
+            throw new FileNotFoundException("Source '" + srcDir + "' does not exist");
+        }
+        if (srcDir.isDirectory() == false)
+        {
+            throw new IOException("Source '" + srcDir + "' exists but is not a directory");
+        }
+        if (srcDir.getCanonicalPath().equals(destDir.getCanonicalPath()))
+        {
+            throw new IOException("Source '" + srcDir + "' and destination '" + destDir
+                    + "' are the same");
+        }
+
+        // Cater for destination being directory within the source directory (see IO-141)
+        List<String> exclusionList = null;
+        if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath()))
+        {
+            File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter);
+            if (srcFiles != null && srcFiles.length > 0)
+            {
+                exclusionList = new ArrayList<String>(srcFiles.length);
+                for (int i = 0; i < srcFiles.length; i++)
+                {
+                    File copiedFile = new File(destDir, srcFiles[i].getName());
+                    exclusionList.add(copiedFile.getCanonicalPath());
+                }
+            }
+        }
+        doCopyDirectory(srcDir, destDir, filter, exclusionList, observerOrNull);
+    }
+
+    /**
+     * Internal copy directory method.
+     * 
+     * @param srcDir the validated source directory, must not be <code>null</code>
+     * @param destDir the validated destination directory, must not be <code>null</code>
+     * @param filter the filter to apply, null means copy all directories and files
+     * @param exclusionList List of files and directories to exclude from the copy, may be null
+     * @param observerOrNull activity observer of the copy process
+     * @throws IOException if an error occurs
+     * @since Commons IO 1.1
+     */
+    private static void doCopyDirectory(File srcDir, File destDir, FileFilter filter,
+            List<String> exclusionList, IActivityObserver observerOrNull) throws IOException
+    {
+        if (destDir.exists())
+        {
+            if (destDir.isDirectory() == false)
+            {
+                throw new IOException("Destination '" + destDir + "' exists but is not a directory");
+            }
+        } else
+        {
+            if (destDir.mkdirs() == false)
+            {
+                throw new IOException("Destination '" + destDir + "' directory cannot be created");
+            }
+            destDir.setLastModified(srcDir.lastModified());
+        }
+        if (destDir.canWrite() == false)
+        {
+            throw new IOException("Destination '" + destDir + "' cannot be written to");
+        }
+        // recurse
+        File[] files = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter);
+        if (files == null)
+        { // null if security restricted
+            throw new IOException("Failed to list contents of " + srcDir);
+        }
+        for (int i = 0; i < files.length; i++)
+        {
+            if (observerOrNull != null)
+            {
+                observerOrNull.update();
+            }
+            File copiedFile = new File(destDir, files[i].getName());
+            if (exclusionList == null || !exclusionList.contains(files[i].getCanonicalPath()))
+            {
+                if (files[i].isDirectory())
+                {
+                    doCopyDirectory(files[i], copiedFile, filter, exclusionList, observerOrNull);
+                } else
+                {
+                    doCopyFile(files[i], copiedFile);
+                }
+            }
+        }
+    }
+}
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/TimingParametersTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/TimingParametersTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..86dc893c885a893c651c34644eacc9be5f412b86
--- /dev/null
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/TimingParametersTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.common;
+
+import java.util.Properties;
+
+import org.apache.commons.lang.time.DateUtils;
+import org.testng.annotations.Test;
+
+import static org.testng.AssertJUnit.*;
+
+/**
+ * Test cases for the {@link TimingParameters}.
+ * 
+ * @author Bernd Rinn
+ */
+public class TimingParametersTest
+{
+
+    @Test
+    public void testCreateTimingParametersDefaults()
+    {
+        final Properties props = new Properties();
+        final TimingParameters parameters = TimingParameters.create(props);
+        assertEquals(TimingParameters.getDefaultParameters(), parameters);
+    }
+
+    @Test
+    public void testCreateTimingParameters()
+    {
+        final int timeout = 33;
+        final int maxRetries = 7;
+        final int failureInterval = 13;
+        final Properties props = new Properties();
+        props.put(TimingParameters.TIMEOUT_PROPERTY, Integer.toString(timeout));
+        props.put(TimingParameters.MAX_RETRY_PROPERTY, Integer.toString(maxRetries));
+        props.put(TimingParameters.FAILURE_INTERVAL, Integer.toString(failureInterval));
+        final TimingParameters parameters = TimingParameters.create(props);
+        assertEquals(timeout * DateUtils.MILLIS_PER_SECOND, parameters.getTimeoutMillis());
+        assertEquals(maxRetries, parameters.getMaxRetriesOnFailure());
+        assertEquals(failureInterval * DateUtils.MILLIS_PER_SECOND, parameters
+                .getIntervalToWaitAfterFailureMillis());
+    }
+
+    @Test
+    public void testHasTimingParametersFalse()
+    {
+        final Properties props = new Properties();
+        assertFalse(TimingParameters.hasTimingParameters(props));
+    }
+
+    @Test
+    public void testHasTimingParametersTrue()
+    {
+        final Properties props = new Properties();
+        props.put(TimingParameters.TIMEOUT_PROPERTY, "30");
+        assertTrue(TimingParameters.hasTimingParameters(props));
+    }
+
+    @Test
+    public void testHasTimingParametersTrue2()
+    {
+        final Properties props = new Properties();
+        props.put(TimingParameters.MAX_RETRY_PROPERTY, "2");
+        assertTrue(TimingParameters.hasTimingParameters(props));
+    }
+
+    @Test
+    public void testHasTimingParametersTrue3()
+    {
+        final Properties props = new Properties();
+        props.put(TimingParameters.FAILURE_INTERVAL, "xxx");
+        assertTrue(TimingParameters.hasTimingParameters(props));
+    }
+}
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/concurrent/ConcurrencyUtilitiesTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/concurrent/ConcurrencyUtilitiesTest.java
index 38c1f7538921f3b5643c85541f69b35e3614593c..a2f4553bcd54f0c525e25c6f103089f36b907f18 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/concurrent/ConcurrencyUtilitiesTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/concurrent/ConcurrencyUtilitiesTest.java
@@ -18,18 +18,25 @@ package ch.systemsx.cisd.common.concurrent;
 
 import static org.testng.AssertJUnit.*;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Future;
 import java.util.concurrent.ThreadPoolExecutor;
 
-import org.testng.annotations.BeforeClass;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
 
+import ch.systemsx.cisd.common.concurrent.ConcurrencyUtilities.ILogSettings;
 import ch.systemsx.cisd.common.exceptions.CheckedExceptionTunnel;
 import ch.systemsx.cisd.common.exceptions.StopException;
+import ch.systemsx.cisd.common.logging.ISimpleLogger;
 import ch.systemsx.cisd.common.logging.LogInitializer;
+import ch.systemsx.cisd.common.logging.LogLevel;
 
 /**
  * Test cases for {@link ConcurrencyUtilities}.
@@ -41,12 +48,85 @@ public class ConcurrencyUtilitiesTest
 
     private final static String name = "This is the pool name";
 
-    @BeforeClass
+    private static class LogRecord
+    {
+        final LogLevel level;
+
+        final String message;
+
+        LogRecord(LogLevel level, String message)
+        {
+            this.level = level;
+            this.message = message;
+        }
+    }
+
+    private class AssertingLogger implements ISimpleLogger
+    {
+        private final List<LogRecord> records = new ArrayList<LogRecord>();
+
+        public void log(LogLevel level, String message)
+        {
+            records.add(new LogRecord(level, message));
+        }
+
+        public void assertNumberOfMessage(int expectedNumberOfMessages)
+        {
+            assertEquals(expectedNumberOfMessages, records.size());
+        }
+
+        public void assertEq(int i, LogLevel expectedLevel, String expectedMessage)
+        {
+            assertEquals(expectedLevel, records.get(i).level);
+            assertEquals(expectedMessage, records.get(i).message);
+        }
+
+    }
+
+    private ILogSettings logSettings;
+
+    private AssertingLogger logger;
+
+    @BeforeTest
     public void init()
     {
         LogInitializer.init();
     }
 
+    @BeforeMethod
+    public void beforeMethod()
+    {
+        logger = new AssertingLogger();
+        createLogSettings(LogLevel.WARN);
+    }
+
+    @AfterClass
+    public void clearThreadInterruptionState()
+    {
+        Thread.interrupted();
+    }
+
+    private void createLogSettings(final LogLevel level)
+    {
+        logSettings = new ILogSettings()
+            {
+                public LogLevel getLogLevelForError()
+                {
+                    return level;
+                }
+
+                public AssertingLogger getLogger()
+                {
+                    return logger;
+                }
+
+                public String getOperationName()
+                {
+                    return name;
+                }
+            };
+    }
+
     @Test
     public void testTryGetFutureOK()
     {
@@ -60,13 +140,37 @@ public class ConcurrencyUtilitiesTest
                     return valueProvided;
                 }
             });
-        final String valueObtained = ConcurrencyUtilities.tryGetResult(future, 200L);
+        final String valueObtained =
+                ConcurrencyUtilities.tryGetResult(future, 200L, logSettings, true);
         assertEquals(valueProvided, valueObtained);
         assertTrue(future.isDone());
+        logger.assertNumberOfMessage(0);
     }
 
     @Test
     public void testGetExecutionResultOK()
+    {
+        final String valueProvided = "This is the execution return value";
+        final ThreadPoolExecutor eservice =
+                new NamingThreadPoolExecutor(name).corePoolSize(1).maximumPoolSize(2);
+        final Future<String> future = eservice.submit(new Callable<String>()
+            {
+                public String call() throws Exception
+                {
+                    return valueProvided;
+                }
+            });
+        final ExecutionResult<String> result =
+                ConcurrencyUtilities.getResult(future, 200L, logSettings);
+        assertEquals(ExecutionStatus.COMPLETE, result.getStatus());
+        assertNull(result.tryGetException());
+        assertEquals(valueProvided, result.tryGetResult());
+        assertTrue(future.isDone());
+        logger.assertNumberOfMessage(0);
+    }
+
+    @Test
+    public void testGetExecutionResultOKWithoutLogging()
     {
         final String valueProvided = "This is the execution return value";
         final ThreadPoolExecutor eservice =
@@ -104,9 +208,12 @@ public class ConcurrencyUtilitiesTest
                     return null;
                 }
             });
-        final String shouldBeNull = ConcurrencyUtilities.tryGetResult(future, 20L);
+        final String shouldBeNull =
+                ConcurrencyUtilities.tryGetResult(future, 20L, logSettings, true);
         assertNull(shouldBeNull);
         assertTrue(future.isDone());
+        logger.assertNumberOfMessage(1);
+        logger.assertEq(0, LogLevel.WARN, name + ": timeout of 0.02 s exceeded, cancelled.");
     }
 
     @Test(groups = "slow")
@@ -128,17 +235,67 @@ public class ConcurrencyUtilitiesTest
                     return null;
                 }
             });
-        final ExecutionResult<String> result = ConcurrencyUtilities.getResult(future, 20L);
+        final ExecutionResult<String> result =
+                ConcurrencyUtilities.getResult(future, 20L, logSettings);
         assertEquals(ExecutionStatus.TIMED_OUT, result.getStatus());
         assertNull(result.tryGetResult());
         assertNull(result.tryGetException());
         assertTrue(future.isDone());
         assertTrue(future.isCancelled());
+        logger.assertNumberOfMessage(1);
+        logger.assertEq(0, LogLevel.WARN, name + ": timeout of 0.02 s exceeded, cancelled.");
+    }
+
+    @Test(groups = "slow")
+    public void testGetExecutionResultNoTimeoutDueToSensor()
+    {
+        final RecordingActivityObserverSensor sensor = new RecordingActivityObserverSensor();
+        final ThreadPoolExecutor eservice =
+                new NamingThreadPoolExecutor(name).corePoolSize(1).maximumPoolSize(2);
+        final Timer updatingTimer = new Timer();
+        final String msg = "success";
+        try
+        {
+            updatingTimer.schedule(new TimerTask()
+                {
+                    @Override
+                    public void run()
+                    {
+                        sensor.update();
+                    }
+                }, 0L, 10L);
+            final Future<String> future = eservice.submit(new Callable<String>()
+                {
+                    public String call() throws Exception
+                    {
+                        try
+                        {
+                            Thread.sleep(200L);
+                        } catch (InterruptedException ex)
+                        {
+                            throw new CheckedExceptionTunnel(ex);
+                        }
+                        return msg;
+                    }
+                });
+            final ExecutionResult<String> result =
+                    ConcurrencyUtilities.getResult(future, 20L, true, logSettings, sensor);
+            assertEquals(ExecutionStatus.COMPLETE, result.getStatus());
+            assertEquals(msg, result.tryGetResult());
+            assertNull(result.tryGetException());
+            assertTrue(future.isDone());
+            assertFalse(future.isCancelled());
+            logger.assertNumberOfMessage(0);
+        } finally
+        {
+            updatingTimer.cancel();
+        }
     }
 
     @Test(groups = "slow")
     public void testGetExecutionResultTimeoutWithoutCancelation()
     {
+        createLogSettings(LogLevel.INFO);
         final ThreadPoolExecutor eservice =
                 new NamingThreadPoolExecutor(name).corePoolSize(1).maximumPoolSize(2);
         final Future<String> future = eservice.submit(new Callable<String>()
@@ -156,12 +313,14 @@ public class ConcurrencyUtilitiesTest
                 }
             });
         final ExecutionResult<String> result =
-                ConcurrencyUtilities.getResult(future, 20L, false, null, null);
+                ConcurrencyUtilities.getResult(future, 20L, false, logSettings);
         assertEquals(ExecutionStatus.TIMED_OUT, result.getStatus());
         assertNull(result.tryGetResult());
         assertNull(result.tryGetException());
         assertFalse(future.isDone());
         assertFalse(future.isCancelled());
+        logger.assertNumberOfMessage(1);
+        logger.assertEq(0, LogLevel.INFO, name + ": timeout of 0.02 s exceeded.");
     }
 
     @Test
@@ -193,11 +352,14 @@ public class ConcurrencyUtilitiesTest
                     thread.interrupt();
                 }
             }, 20L);
-        final String shouldBeNull = ConcurrencyUtilities.tryGetResult(future, 200L, false);
+        final String shouldBeNull =
+                ConcurrencyUtilities.tryGetResult(future, 200L, logSettings, false);
         t.cancel();
         assertNull(shouldBeNull);
         assertTrue(future.isCancelled());
         assertFalse(Thread.interrupted());
+        logger.assertNumberOfMessage(1);
+        logger.assertEq(0, LogLevel.WARN, name + ": interrupted.");
     }
 
     @Test(expectedExceptions =
@@ -237,6 +399,7 @@ public class ConcurrencyUtilitiesTest
     @Test
     public void testGetExecutionResultInterrupted()
     {
+        createLogSettings(LogLevel.DEBUG);
         final ThreadPoolExecutor eservice =
                 new NamingThreadPoolExecutor(name).corePoolSize(1).maximumPoolSize(2);
         final Thread thread = Thread.currentThread();
@@ -263,23 +426,36 @@ public class ConcurrencyUtilitiesTest
                     thread.interrupt();
                 }
             }, 20L);
-        final ExecutionResult<String> result = ConcurrencyUtilities.getResult(future, 200L);
+        final ExecutionResult<String> result =
+                ConcurrencyUtilities.getResult(future, 200L, logSettings);
         t.cancel();
         assertEquals(ExecutionStatus.INTERRUPTED, result.getStatus());
         assertNull(result.tryGetResult());
         assertNull(result.tryGetException());
         assertTrue(future.isCancelled());
         assertFalse(Thread.interrupted());
+        logger.assertNumberOfMessage(1);
+        logger.assertEq(0, LogLevel.DEBUG, name + ": interrupted.");
     }
 
     private static class TaggedException extends RuntimeException
     {
         private static final long serialVersionUID = 1L;
+
+        public TaggedException()
+        {
+        }
+
+        public TaggedException(String msg)
+        {
+            super(msg);
+        }
     }
 
     @Test
     public void testGetExecutionResultException()
     {
+        createLogSettings(LogLevel.ERROR);
         final ThreadPoolExecutor eservice =
                 new NamingThreadPoolExecutor(name).corePoolSize(1).maximumPoolSize(2);
         final Future<String> future = eservice.submit(new Callable<String>()
@@ -289,23 +465,54 @@ public class ConcurrencyUtilitiesTest
                     throw new TaggedException();
                 }
             });
-        final ExecutionResult<String> result = ConcurrencyUtilities.getResult(future, 100L);
+        final ExecutionResult<String> result =
+                ConcurrencyUtilities.getResult(future, 100L, logSettings);
         assertEquals(ExecutionStatus.EXCEPTION, result.getStatus());
         assertTrue(result.tryGetException() instanceof TaggedException);
         assertNull(result.tryGetResult());
         assertTrue(future.isDone());
+        logger.assertNumberOfMessage(1);
+        logger.assertEq(0, LogLevel.ERROR, name + ": exception: <no message> [TaggedException].");
     }
 
     @Test
     public void testTryGetFutureException()
     {
+        final String msg = "This is some sort of error message";
         final ThreadPoolExecutor eservice =
                 new NamingThreadPoolExecutor(name).corePoolSize(1).maximumPoolSize(2);
         final Future<String> future = eservice.submit(new Callable<String>()
             {
                 public String call() throws Exception
                 {
-                    throw new TaggedException();
+                    throw new TaggedException(msg);
+                }
+            });
+        try
+        {
+            ConcurrencyUtilities.tryGetResult(future, 100L, logSettings, true);
+            fail("Should have been a TaggedException");
+        } catch (TaggedException ex)
+        {
+            // Good
+        }
+        assertTrue(future.isDone());
+        logger.assertNumberOfMessage(1);
+        logger.assertEq(0, LogLevel.WARN, name
+                + ": exception: This is some sort of error message [TaggedException].");
+    }
+
+    @Test
+    public void testTryGetFutureExceptionWithoutLogging()
+    {
+        final String msg = "This is some sort of error message";
+        final ThreadPoolExecutor eservice =
+                new NamingThreadPoolExecutor(name).corePoolSize(1).maximumPoolSize(2);
+        final Future<String> future = eservice.submit(new Callable<String>()
+            {
+                public String call() throws Exception
+                {
+                    throw new TaggedException(msg);
                 }
             });
         try
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/concurrent/InactivityMonitorTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/concurrent/InactivityMonitorTest.java
index 7c292e4405499f8a2fd4306ba9781303d9eb2f8a..8ddda4c08e9386ecc3eb483ed1eed130f3c0f5e4 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/concurrent/InactivityMonitorTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/concurrent/InactivityMonitorTest.java
@@ -27,7 +27,7 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IActivitySensor;
+import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IDescribingActivitySensor;
 import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IInactivityObserver;
 import ch.systemsx.cisd.common.logging.LogInitializer;
 import ch.systemsx.cisd.common.test.StoringUncaughtExceptionHandler;
@@ -43,11 +43,11 @@ public class InactivityMonitorTest
 
     private static final long TIME_TO_WAIT_MILLIS = 4 * INACTIVITY_THRESHOLD_MILLIS;
 
-    private final static long DELTA = 10L;
+    private final static long DELTA = 20L;
 
     private Mockery context;
 
-    private IActivitySensor sensor;
+    private IDescribingActivitySensor sensor;
 
     private IInactivityObserver observer;
 
@@ -130,7 +130,7 @@ public class InactivityMonitorTest
     {
         exceptionHandler.reset();
         context = new Mockery();
-        sensor = context.mock(IActivitySensor.class);
+        sensor = context.mock(IDescribingActivitySensor.class);
         observer = context.mock(IInactivityObserver.class);
     }
 
@@ -153,7 +153,7 @@ public class InactivityMonitorTest
         context.checking(new Expectations()
             {
                 {
-                    atLeast(1).of(sensor).getTimeOfLastActivityMoreRecentThan(
+                    atLeast(1).of(sensor).getLastActivityMillisMoreRecentThan(
                             INACTIVITY_THRESHOLD_MILLIS);
                     will(new ReturnNowMinus(0L));
                 }
@@ -173,7 +173,7 @@ public class InactivityMonitorTest
         context.checking(new Expectations()
             {
                 {
-                    one(sensor).getTimeOfLastActivityMoreRecentThan(INACTIVITY_THRESHOLD_MILLIS);
+                    one(sensor).getLastActivityMillisMoreRecentThan(INACTIVITY_THRESHOLD_MILLIS);
                     will(new ReturnNowMinus(2 * INACTIVITY_THRESHOLD_MILLIS));
                     one(sensor).describeInactivity(with(new NowMatcher()));
                     will(returnValue(descriptionOfInactivity));
@@ -190,14 +190,16 @@ public class InactivityMonitorTest
         context.assertIsSatisfied();
     }
 
-    @Test
+    @Test(groups = "slow")
     public void testInactivityMultipleTimes() throws Throwable
     {
+        // Wait for system to become quiet to get more accurate measurement.
+        ConcurrencyUtilities.sleep(300L);
         final String descriptionOfInactivity = "DESCRIPTION";
         context.checking(new Expectations()
             {
                 {
-                    atLeast(3).of(sensor).getTimeOfLastActivityMoreRecentThan(
+                    atLeast(3).of(sensor).getLastActivityMillisMoreRecentThan(
                             INACTIVITY_THRESHOLD_MILLIS);
                     will(new ReturnNowMinus(2 * INACTIVITY_THRESHOLD_MILLIS));
                     atLeast(3).of(sensor).describeInactivity(with(new NowMatcher()));
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/concurrent/MonitoringProxyTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/concurrent/MonitoringProxyTest.java
index a37ddde3fa67457b0579897430f5a2cdcc93d63f..d0598c77b076b55f98bb919138d47ee49a48bc36 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/concurrent/MonitoringProxyTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/concurrent/MonitoringProxyTest.java
@@ -16,18 +16,25 @@
 
 package ch.systemsx.cisd.common.concurrent;
 
-import static org.testng.AssertJUnit.*;
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertNull;
+import static org.testng.AssertJUnit.assertTrue;
 
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.regex.Pattern;
 
+import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeTest;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import ch.systemsx.cisd.common.TimingParameters;
 import ch.systemsx.cisd.common.exceptions.StopException;
 import ch.systemsx.cisd.common.exceptions.TimeoutException;
+import ch.systemsx.cisd.common.logging.ConsoleLogger;
+import ch.systemsx.cisd.common.logging.ISimpleLogger;
 
 /**
  * Test cases for the {@link MonitoringProxy}.
@@ -47,19 +54,30 @@ public class MonitoringProxyTest
 
     private static final Status THE_STATUS = Status.TWO;
 
-    private static final long TIMEOUT_MILLIS = 50L;
+    private static final long TIMEOUT_MILLIS = 100L;
 
     private volatile Thread threadToStop;
-    
+
+    private RecordingActivityObserverSensor observerSensor;
+
     private ITest defaultReturningProxy;
 
     private ITest exceptionThrowingProxy;
 
+    private ITest retryingOnceExceptionThrowingProxy;
+
+    private ITest retryingTwiceExceptionThrowingProxy;
+
     private static class SignalException extends RuntimeException
     {
         private static final long serialVersionUID = 1L;
     }
 
+    private static class RetryItException extends RuntimeException
+    {
+        private static final long serialVersionUID = 1L;
+    }
+
     enum Status
     {
         ONE, TWO, THREE, UUUPS, SPECIAL_UUUPS
@@ -69,6 +87,8 @@ public class MonitoringProxyTest
     {
         void idle(boolean hang);
 
+        void busyUpdatingActivity();
+
         String getString(boolean hang);
 
         boolean getBoolean(boolean hang);
@@ -80,13 +100,24 @@ public class MonitoringProxyTest
         Status getSpecialStatus(boolean hang);
 
         void throwSignalException() throws SignalException;
+
+        void worksOnSecondInvocation() throws RetryItException;
+
+        void worksOnThirdInvocation() throws RetryItException;
     }
 
     private final static Pattern THREAD_NAME_PATTERN =
-        Pattern.compile("Monitoring Proxy-T[0-9]+::main::" + THREAD_NAME);
+            Pattern.compile("Monitoring Proxy-T[0-9]+::main::" + THREAD_NAME);
 
     private class TestImpl implements ITest
     {
+        private final IActivityObserver observer;
+
+        TestImpl(IActivityObserver observer)
+        {
+            this.observer = observer;
+        }
+
         private void hang(boolean hang)
         {
             if (hang)
@@ -110,6 +141,18 @@ public class MonitoringProxyTest
             hang(hang);
         }
 
+        public void busyUpdatingActivity()
+        {
+            checkThreadName();
+            threadToStop = Thread.currentThread();
+            final long timeToHangMillis = (long) (TIMEOUT_MILLIS * 1.5);
+            final long start = System.currentTimeMillis();
+            while (System.currentTimeMillis() - start < timeToHangMillis)
+            {
+                observer.update();
+            }
+        }
+
         public boolean getBoolean(boolean hang)
         {
             checkThreadName();
@@ -150,20 +193,58 @@ public class MonitoringProxyTest
             checkThreadName();
             throw new SignalException();
         }
+
+        int invocationCount1 = 0;
+
+        public void worksOnSecondInvocation() throws RetryItException
+        {
+            checkThreadName();
+            if (++invocationCount1 < 2)
+            {
+                throw new RetryItException();
+            }
+        }
+
+        int invocationCount2 = 0;
+
+        public void worksOnThirdInvocation() throws RetryItException
+        {
+            checkThreadName();
+            if (++invocationCount2 < 3)
+            {
+                throw new RetryItException();
+            }
+        }
+
     }
 
-    @BeforeTest
+    @BeforeClass
     public void createMonitoringProxy() throws NoSuchMethodException
     {
+        final ISimpleLogger logger = new ConsoleLogger();
+        observerSensor = new RecordingActivityObserverSensor();
         defaultReturningProxy =
-                MonitoringProxy.create(ITest.class, new TestImpl()).timeoutMillis(TIMEOUT_MILLIS)
-                        .errorValueOnTimeout().name(THREAD_NAME).errorTypeValueMapping(
-                                Status.class, Status.UUUPS).errorMethodValueMapping(
+                MonitoringProxy.create(ITest.class, new TestImpl(observerSensor)).timing(
+                        TimingParameters.createNoRetries(TIMEOUT_MILLIS)).errorValueOnTimeout()
+                        .name(THREAD_NAME).errorTypeValueMapping(Status.class, Status.UUUPS)
+                        .errorMethodValueMapping(
                                 ITest.class.getMethod("getSpecialStatus", new Class<?>[]
-                                    { Boolean.TYPE }), Status.SPECIAL_UUUPS).get();
+                                    { Boolean.TYPE }), Status.SPECIAL_UUUPS).sensor(observerSensor)
+                        .errorLog(logger).get();
         exceptionThrowingProxy =
-                MonitoringProxy.create(ITest.class, new TestImpl()).timeoutMillis(TIMEOUT_MILLIS)
-                        .name(THREAD_NAME).get();
+                MonitoringProxy.create(ITest.class, new TestImpl(observerSensor)).timing(
+                        TimingParameters.createNoRetries(TIMEOUT_MILLIS)).name(THREAD_NAME).sensor(
+                        observerSensor).errorLog(logger).get();
+        retryingOnceExceptionThrowingProxy =
+                MonitoringProxy.create(ITest.class, new TestImpl(observerSensor)).timing(
+                        TimingParameters.create(TIMEOUT_MILLIS, 1, 0L)).name(THREAD_NAME).sensor(
+                        observerSensor).exceptionClassSuitableForRetrying(RetryItException.class)
+                        .errorLog(logger).get();
+        retryingTwiceExceptionThrowingProxy =
+                MonitoringProxy.create(ITest.class, new TestImpl(observerSensor)).timing(
+                        TimingParameters.create(TIMEOUT_MILLIS, 2, 0L)).name(THREAD_NAME).sensor(
+                        observerSensor).exceptionClassSuitableForRetrying(RetryItException.class)
+                        .errorLog(logger).get();
     }
 
     @SuppressWarnings("deprecation")
@@ -176,7 +257,14 @@ public class MonitoringProxyTest
             t.stop();
         }
     }
-    
+
+    @BeforeMethod
+    @AfterClass
+    public void clearThreadInterruptionState()
+    {
+        Thread.interrupted();
+    }
+
     @Test
     public void testVoid()
     {
@@ -207,6 +295,12 @@ public class MonitoringProxyTest
         exceptionThrowingProxy.idle(true);
     }
 
+    @Test(groups = "slow")
+    public void testNoTimeoutDueToSensorUpdate()
+    {
+        exceptionThrowingProxy.busyUpdatingActivity();
+    }
+
     @Test
     public void testGetStringNullReturningPolicy()
     {
@@ -283,8 +377,8 @@ public class MonitoringProxyTest
     public void testInterruptTheUninterruptableThrowsException()
     {
         final ITest proxy =
-                MonitoringProxy.create(ITest.class, new TestImpl()).timeoutMillis(1000L).name(
-                        THREAD_NAME).get();
+                MonitoringProxy.create(ITest.class, new TestImpl(observerSensor)).timing(
+                        TimingParameters.create(1000L)).name(THREAD_NAME).get();
         final Thread currentThread = Thread.currentThread();
         final Timer timer = new Timer();
         timer.schedule(new TimerTask()
@@ -295,7 +389,7 @@ public class MonitoringProxyTest
                     currentThread.interrupt();
                 }
             }, 50L);
-        // This call would not be interruptable if it wasn't proxied, but we get a StopException
+        // This call would not be interruptible if it wasn't proxied, but we get a StopException
         // from the proxy.
         proxy.idle(true);
         timer.cancel();
@@ -306,9 +400,9 @@ public class MonitoringProxyTest
     {
         final String defaultReturnValue = "That's the default return value.";
         final ITest proxy =
-                MonitoringProxy.create(ITest.class, new TestImpl()).timeoutMillis(1000L).name(
-                        THREAD_NAME).errorValueOnInterrupt().errorTypeValueMapping(String.class,
-                        defaultReturnValue).get();
+                MonitoringProxy.create(ITest.class, new TestImpl(observerSensor)).timing(
+                        TimingParameters.create(1000L)).name(THREAD_NAME).errorValueOnInterrupt()
+                        .errorTypeValueMapping(String.class, defaultReturnValue).get();
         final Thread currentThread = Thread.currentThread();
         final Timer timer = new Timer();
         timer.schedule(new TimerTask()
@@ -324,4 +418,28 @@ public class MonitoringProxyTest
         assertEquals(defaultReturnValue, proxy.getString(true));
         timer.cancel();
     }
+
+    @Test(expectedExceptions = RetryItException.class)
+    public void testNoRetryFailOnce()
+    {
+        exceptionThrowingProxy.worksOnSecondInvocation();
+    }
+
+    @Test
+    public void testRetryOnceFailOnce()
+    {
+        retryingOnceExceptionThrowingProxy.worksOnSecondInvocation();
+    }
+
+    @Test(expectedExceptions = RetryItException.class)
+    public void testRetryOnceFailTwice()
+    {
+        retryingOnceExceptionThrowingProxy.worksOnThirdInvocation();
+    }
+
+    @Test(groups = "slow")
+    public void testRetryTwiceFailTwice()
+    {
+        retryingTwiceExceptionThrowingProxy.worksOnThirdInvocation();
+    }
 }
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/filesystem/FileOperationsTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/filesystem/FileOperationsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..20e0c7460bbda0f2a7c89d0790861925cf62c9ba
--- /dev/null
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/filesystem/FileOperationsTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.common.filesystem;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.common.TimingParameters;
+import ch.systemsx.cisd.common.concurrent.ConcurrencyUtilities;
+import ch.systemsx.cisd.common.concurrent.IActivityObserver;
+import ch.systemsx.cisd.common.concurrent.MonitoringProxy;
+import ch.systemsx.cisd.common.concurrent.RecordingActivityObserverSensor;
+import ch.systemsx.cisd.common.exceptions.TimeoutException;
+import ch.systemsx.cisd.common.exceptions.WrappedIOException;
+import ch.systemsx.cisd.common.logging.Log4jSimpleLogger;
+
+/**
+ * Test cases for {@link FileOperations}.
+ * 
+ * @author Bernd Rinn
+ */
+public class FileOperationsTest
+{
+
+    static class HangingIInputStream implements IInputStream
+    {
+        private final long hangMillis;
+
+        HangingIInputStream(long hangMillis)
+        {
+            this.hangMillis = hangMillis;
+        }
+
+        private void hang()
+        {
+            ConcurrencyUtilities.sleep(hangMillis);
+        }
+
+        public int available() throws WrappedIOException
+        {
+            hang();
+            return 0;
+        }
+
+        public void close() throws WrappedIOException
+        {
+            hang();
+        }
+
+        public void mark(int readlimit)
+        {
+            hang();
+        }
+
+        public boolean markSupported()
+        {
+            hang();
+            return false;
+        }
+
+        public int read() throws WrappedIOException
+        {
+            hang();
+            return 0;
+        }
+
+        public int read(byte[] b) throws WrappedIOException
+        {
+            hang();
+            return 0;
+        }
+
+        public int read(byte[] b, int off, int len) throws WrappedIOException
+        {
+            hang();
+            return 0;
+        }
+
+        public void reset() throws WrappedIOException
+        {
+            hang();
+        }
+
+        public long skip(long n) throws WrappedIOException
+        {
+            hang();
+            return 0;
+        }
+
+    }
+
+    static class HangingFileOperations extends FileOperations
+    {
+
+        private final long hangMillis;
+
+        HangingFileOperations(TimingParameters parameters, IActivityObserver observer,
+                long hangMillis)
+        {
+            super(parameters, observer);
+            this.hangMillis = hangMillis;
+        }
+
+        @Override
+        IInputStream internalGetIInputStream(File file) throws FileNotFoundException
+        {
+            return new HangingIInputStream(hangMillis);
+        }
+
+    }
+
+    private static IFileOperations create(TimingParameters parameters, long hangMillis)
+    {
+        final RecordingActivityObserverSensor observerSensor =
+                new RecordingActivityObserverSensor();
+        return MonitoringProxy.create(
+                IFileOperations.class,
+                new HangingFileOperations(parameters, observerSensor,
+                        hangMillis)).timing(parameters).sensor(
+                observerSensor).errorLog(new Log4jSimpleLogger(FileOperations.operationLog)).name(
+                "remote file operations").get();
+    }
+
+    @Test(groups = "slow", expectedExceptions = TimeoutException.class)
+    public void testNoTimeoutOnInputStream() throws IOException
+    {
+        final IFileOperations ops = create(TimingParameters.createNoRetries(50L), 100L);
+        final InputStream is = ops.getInputStream(new File("bla.txt"));
+        is.read(); // times out
+    }
+
+    @Test(groups = "slow")
+    public void testTimeoutOnInputStream() throws IOException
+    {
+        final IFileOperations ops = create(TimingParameters.createNoRetries(50L), 20L);
+        final InputStream is = ops.getInputStream(new File("bla.txt"));
+        is.read(); // doesn't time out
+    }
+
+}
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/filesystem/QueueingPathRemoverServiceTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/filesystem/QueueingPathRemoverServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..8941ec0e10c947d0779fbd08d32bf716f26d9f37
--- /dev/null
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/filesystem/QueueingPathRemoverServiceTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.common.filesystem;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.common.concurrent.ConcurrencyUtilities;
+import ch.systemsx.cisd.common.logging.LogInitializer;
+import ch.systemsx.cisd.common.utilities.FileUtilities;
+
+import static org.testng.AssertJUnit.*;
+
+/**
+ * Test cases for the {@link QueueingPathRemoverService}.
+ * 
+ * @author Bernd Rinn
+ */
+public class QueueingPathRemoverServiceTest
+{
+
+    private static final long WAIT_MILLIS = 120L;
+    
+    private static final long DELAY_MILLIS = 1L;
+
+    private static final File wdRootDirectory =
+            new File("targets" + File.separator + "unit-test-wd");
+
+    private static final File workingDirectory = new File(wdRootDirectory, "ShredderTest");
+
+    private static final File queueFile = new File(workingDirectory, "qfile.dat");
+
+    private static final FilenameFilter SHREDDER_FILTER = new FilenameFilter()
+        {
+            public boolean accept(File dir, String name)
+            {
+                return name.startsWith(QueueingPathRemoverService.SHREDDER_PREFIX);
+            }
+        };
+
+    private final QueueingPathRemoverService shredder = QueueingPathRemoverService.getInstance();
+
+    @BeforeTest
+    public void setUp()
+    {
+        LogInitializer.init();
+        queueFile.deleteOnExit();
+        workingDirectory.mkdirs();
+        workingDirectory.deleteOnExit();
+        QueueingPathRemoverService.start(queueFile);
+    }
+
+    @BeforeMethod
+    public void cleanUp() throws IOException
+    {
+        for (File f : workingDirectory.listFiles(SHREDDER_FILTER))
+        {
+            FileUtilities.deleteRecursively(f);
+            assertFalse(f.exists());
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testShredderFile() throws IOException
+    {
+        final File f = new File(workingDirectory, "someFile");
+        f.createNewFile();
+        f.deleteOnExit();
+        assertTrue(f.exists());
+        shredder.removeRecursively(f);
+        assertFalse(f.exists());
+        ConcurrencyUtilities.sleep(WAIT_MILLIS);
+        assertEquals(0, workingDirectory.list(SHREDDER_FILTER).length);
+    }
+
+   @Test(groups = "slow")
+    public void testShredderDirectory() throws IOException
+    {
+        final File f = new File(workingDirectory, "someDir");
+        f.mkdir();
+        for (int i = 0; i < 100; ++i)
+        {
+            (new File(f, "d" + i)).mkdir();
+        }
+        for (int i = 0; i < 100; ++i)
+        {
+            (new File(f, "f" + i)).createNewFile();
+        }
+        assertTrue(f.exists());
+        shredder.removeRecursively(f);
+        assertFalse(f.exists());
+        ConcurrencyUtilities.sleep(WAIT_MILLIS);
+        assertEquals(0, workingDirectory.list(SHREDDER_FILTER).length);
+    }
+
+    @Test(groups = "slow")
+    public void testManyFiles() throws IOException
+    {
+        final List<File> list = new ArrayList<File>();
+        for (int i = 0; i < 100; ++i)
+        {
+            final File f = new File(workingDirectory, "oneOfMany" + i);
+            f.createNewFile();
+            list.add(f);
+        }
+        for (File f : list)
+        {
+            shredder.removeRecursively(f);
+            assertFalse(f.exists());
+        }
+        ConcurrencyUtilities.sleep(WAIT_MILLIS);
+        assertEquals(0, workingDirectory.list(SHREDDER_FILTER).length);
+    }
+
+    @Test(groups = "slow")
+    public void testManyFilesSlowlyComingIn() throws IOException
+    {
+        final List<File> list = new ArrayList<File>();
+        for (int i = 0; i < 100; ++i)
+        {
+            final File f = new File(workingDirectory, "oneOfMany" + i);
+            f.createNewFile();
+            list.add(f);
+        }
+        for (File f : list)
+        {
+            shredder.removeRecursively(f);
+            ConcurrencyUtilities.sleep(DELAY_MILLIS);
+            assertFalse(f.exists());
+        }
+        ConcurrencyUtilities.sleep(WAIT_MILLIS);
+        assertEquals(0, workingDirectory.list(SHREDDER_FILTER).length);
+    }
+
+}
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/filesystem/rsync/RsyncCopierTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/filesystem/rsync/RsyncCopierTest.java
index 5ce6c9f5a077c18b11d42bb1d3177cc761a4775d..e8eb15f7271626fb7498dd97bd6f1fa3ded114aa 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/filesystem/rsync/RsyncCopierTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/filesystem/rsync/RsyncCopierTest.java
@@ -25,8 +25,11 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Logger;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -35,8 +38,9 @@ import ch.rinn.restrictions.Friend;
 import ch.systemsx.cisd.common.collections.CollectionIO;
 import ch.systemsx.cisd.common.exceptions.Status;
 import ch.systemsx.cisd.common.exceptions.StatusFlag;
-import ch.systemsx.cisd.common.filesystem.rsync.RsyncCopier;
-import ch.systemsx.cisd.common.filesystem.rsync.RsyncExitValueTranslator;
+import ch.systemsx.cisd.common.exceptions.StopException;
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.common.logging.LogInitializer;
 import ch.systemsx.cisd.common.test.StoringUncaughtExceptionHandler;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
@@ -49,6 +53,10 @@ import ch.systemsx.cisd.common.utilities.FileUtilities;
 @Friend(toClasses = RsyncCopier.class)
 public final class RsyncCopierTest
 {
+    private static final Logger operationLog =
+            LogFactory.getLogger(LogCategory.OPERATION, RsyncCopierTest.class);
+
+    private static final long SLEEP_MILLIS = 1000L;
 
     private static final File unitTestRootDirectory =
             new File("targets" + File.separator + "unit-test-wd");
@@ -64,6 +72,28 @@ public final class RsyncCopierTest
     private final StoringUncaughtExceptionHandler exceptionHandler =
             new StoringUncaughtExceptionHandler();
 
+    private File createExecutable(String name, String... lines) throws IOException,
+            InterruptedException
+    {
+        final File executable = new File(workingDirectory, name);
+        executable.delete();
+        CollectionIO.writeIterable(executable, Arrays.asList(lines));
+        Runtime.getRuntime().exec(String.format("/bin/chmod +x %s", executable.getPath()))
+                .waitFor();
+        executable.deleteOnExit();
+        return executable;
+    }
+
+    private final String sleepyMessage = "I am feeling sooo sleepy...";
+
+    private File createSleepingRsyncExecutable(String name, long millisToSleep) throws IOException,
+            InterruptedException
+    {
+        return createExecutable(name, "#! /bin/sh",
+                "if [ \"$1\" = \"--version\" ]; then echo \"rsync  version 3.0.3\"; exit 0; fi",
+                "echo " + sleepyMessage, "sleep " + (millisToSleep / 1000.0f), "exit 0");
+    }
+
     @BeforeClass
     public void init()
     {
@@ -410,4 +440,39 @@ public final class RsyncCopierTest
         return createRsync("2.6.9", "/bin/sleep " + seconds);
     }
 
+    @Test(groups =
+        { "slow", "requires_unix" }, expectedExceptions = StopException.class)
+    public void testStopRsyncCopierCopyImmutably() throws Exception
+    {
+        final File rsyncExecutable = createSleepingRsyncExecutable("rsync", SLEEP_MILLIS);
+        assertTrue(rsyncExecutable.exists());
+        final File source = new File(unitTestRootDirectory, "a");
+        source.mkdir();
+        source.deleteOnExit();
+        assertTrue(source.isDirectory());
+        final File destination = new File(unitTestRootDirectory, "b");
+        destination.mkdir();
+        destination.deleteOnExit();
+        assertTrue(destination.isDirectory());
+        final RsyncCopier copier = new RsyncCopier(rsyncExecutable);
+        final Thread thisThread = Thread.currentThread();
+        final Timer timer = new Timer();
+        try
+        {
+            timer.schedule(new TimerTask()
+                {
+                    @Override
+                    public void run()
+                    {
+                        operationLog.info("Interrupting thread");
+                        thisThread.interrupt();
+                    }
+                }, SLEEP_MILLIS / 10);
+            copier.copyDirectoryImmutably(source, destination, null);
+        } finally
+        {
+            timer.cancel();
+        }
+    }
+
 }
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/process/ProcessExecutionHelperTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/process/ProcessExecutionHelperTest.java
index 9747f1f17ba4754fac229d639daeca333642597a..bfa1a656c7450afd876a76f5c71ab83881a0071a 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/process/ProcessExecutionHelperTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/process/ProcessExecutionHelperTest.java
@@ -28,6 +28,7 @@ import java.util.TimerTask;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.log4j.Logger;
+import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -69,6 +70,7 @@ public class ProcessExecutionHelperTest
         final File executable = new File(workingDirectory, name);
         executable.delete();
         CollectionIO.writeIterable(executable, Arrays.asList(lines));
+        Thread.interrupted(); // Paranoia: clear interrupted state.
         Runtime.getRuntime().exec(String.format("/bin/chmod +x %s", executable.getPath()))
                 .waitFor();
         executable.deleteOnExit();
@@ -106,6 +108,13 @@ public class ProcessExecutionHelperTest
         workingDirectory.deleteOnExit();
     }
 
+    @BeforeMethod
+    @AfterClass
+    public void clearThreadInterruptionState()
+    {
+        Thread.interrupted();
+    }
+
     @Test(groups =
         { "requires_unix" })
     public void testExecutionOKWithoutTimeOut() throws Exception
@@ -191,6 +200,37 @@ public class ProcessExecutionHelperTest
         }
     }
 
+    @Test(groups =
+        { "requires_unix", "slow" }, expectedExceptions =
+        { StopException.class })
+    public void testSleepyExecutionGetsStoppedUsingRunUnblocking() throws Exception
+    {
+        final Thread thisThread = Thread.currentThread();
+        final Timer timer = new Timer();
+        try
+        {
+            timer.schedule(new TimerTask()
+                {
+                    @Override
+                    public void run()
+                    {
+                        thisThread.interrupt();
+                    }
+                }, WATCHDOG_WAIT_MILLIS / 10);
+            final File dummyExec =
+                    createSleepingExecutable("dummySleepyFailedWithTimeOut.sh",
+                            2 * WATCHDOG_WAIT_MILLIS);
+            final IProcessHandler processHandler =
+                    ProcessExecutionHelper.runUnblocking(
+                            Arrays.asList(dummyExec.getAbsolutePath()),
+                            OutputReadingStrategy.ON_ERROR, operationLog, machineLog);
+            processHandler.getResult();
+        } finally
+        {
+            timer.cancel();
+        }
+    }
+
     @Test(groups =
         { "requires_unix", "slow" })
     public void testSleepyExecutionGetsInterrupted() throws Exception
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/AbstractFileSystemTestCase.java b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/AbstractFileSystemTestCase.java
index 739f9fdedfca16587f68e4c1e0fa6420c7a3d5f5..0d33bfc6f8b1aa0167424469a5117b55ce333778 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/AbstractFileSystemTestCase.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/AbstractFileSystemTestCase.java
@@ -77,6 +77,6 @@ public abstract class AbstractFileSystemTestCase
         {
             return;
         }
-        FileUtils.deleteDirectory(workingDirectory);
+        FileUtilities.deleteRecursively(workingDirectory);
     }
 }
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 358749b0cbfd775126061e70177bba82f1dd43b5..8640b887e0f245ca685d7a37b261d5c2ab082981 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTaskTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTaskTest.java
@@ -17,7 +17,7 @@
 package ch.systemsx.cisd.common.utilities;
 
 import static ch.systemsx.cisd.common.utilities.FileUtilities.ACCEPT_ALL_FILTER;
-import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.*;
 
 import java.io.File;
 import java.io.FileFilter;
@@ -25,6 +25,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
@@ -93,6 +94,11 @@ public class DirectoryScanningTimerTaskTest
             handledPaths.add(path);
             path.delete();
         }
+
+        public boolean isStopped()
+        {
+            return false;
+        }
     }
 
     @BeforeClass
@@ -204,7 +210,7 @@ public class DirectoryScanningTimerTaskTest
         assertEquals(1, faulty.size());
         assertEquals(someFile.getPath(), faulty.get(0));
         // See whether fault_paths resetting works.
-        assert faultyPaths.delete();
+        assertTrue(faultyPaths.delete());
         myPathHandler.clear(); // Isn't necessary, just for expressing intention.
         // See whether faulty_paths settings works.
         scanner.run();
@@ -212,6 +218,62 @@ public class DirectoryScanningTimerTaskTest
         assertEquals(someFile, myPathHandler.handledPaths.get(0));
     }
 
+    @Test
+    public void testStopped() throws IOException
+    {
+        final DirectoryScanningTimerTask scanner =
+            new DirectoryScanningTimerTask(workingDirectory, ACCEPT_ALL_FILTER, new IPathHandler()
+            {
+                public void handle(File path)
+                {
+                    throw new AssertionError("Shouldn't have been called");
+                }
+
+                public boolean isStopped()
+                {
+                    return true;
+                }
+            });
+        final File someFile = new File(workingDirectory, "some_file");
+        someFile.createNewFile();
+        assertTrue(someFile.exists());
+        someFile.deleteOnExit();
+        scanner.run();
+    }
+
+    @Test
+    public void testStopInFirstFile() throws IOException
+    {
+        final File faultyPaths =
+            new File(workingDirectory, Constants.FAULTY_PATH_FILENAME);
+        faultyPaths.createNewFile();
+        assertTrue(faultyPaths.exists());
+        final AtomicInteger counter = new AtomicInteger(0);
+        final DirectoryScanningTimerTask scanner =
+            new DirectoryScanningTimerTask(workingDirectory, ACCEPT_ALL_FILTER, new IPathHandler()
+            {
+                boolean stop = false;
+                public void handle(File path)
+                {
+                    counter.incrementAndGet();
+                    stop = true;
+                }
+
+                public boolean isStopped()
+                {
+                    return stop;
+                }
+            });
+        final File someFile = new File(workingDirectory, "some_file");
+        someFile.createNewFile();
+        assertTrue(someFile.exists());
+        final File someOtherFile = new File(workingDirectory, "some_other_file");
+        someOtherFile.deleteOnExit();
+        scanner.run();
+        assertEquals(1, counter.get());
+        assertEquals(0L, faultyPaths.length());
+    }
+
     @Test
     public void testPathOrder() throws IOException
     {
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/FileUtilitiesDeleteRecursivelyTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/FileUtilitiesDeleteRecursivelyTest.java
index 31b7dc9beada3dae953acda331c49745d6e0c47f..ba04e0b6c0e320001736d2fb266b1220941d879a 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/FileUtilitiesDeleteRecursivelyTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/FileUtilitiesDeleteRecursivelyTest.java
@@ -25,8 +25,8 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import org.testng.annotations.Test;
 
+import ch.systemsx.cisd.common.concurrent.IActivityObserver;
 import ch.systemsx.cisd.common.exceptions.CheckedExceptionTunnel;
-import ch.systemsx.cisd.common.utilities.FileUtilities.SimpleActivityObserver;
 
 /**
  * Test cases for the <code>deleteRecursively</code> methods of {@link FileUtilities}.
@@ -174,7 +174,7 @@ public final class FileUtilitiesDeleteRecursivelyTest extends AbstractFileSystem
                 {
                     return pathname.getAbsolutePath().equals(workingDirectory.getAbsolutePath()) == false;
                 }
-            }, null, new SimpleActivityObserver()
+            }, null, new IActivityObserver()
             {
                 public void update()
                 {
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/FileUtilitiesTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/FileUtilitiesTest.java
index f83938daf24be44db5530700866db9852b5e4662..943658e52ec2dd4aff74f4bc58d23ef25f4c944a 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/FileUtilitiesTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/FileUtilitiesTest.java
@@ -29,6 +29,7 @@ import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.regex.Pattern;
 
@@ -37,6 +38,7 @@ import org.apache.commons.io.IOUtils;
 import org.testng.annotations.Test;
 
 import ch.systemsx.cisd.common.Constants;
+import ch.systemsx.cisd.common.concurrent.IActivityObserver;
 import ch.systemsx.cisd.common.exceptions.CheckedExceptionTunnel;
 import ch.systemsx.cisd.common.parser.filter.ExcludeEmptyAndCommentLineFilter;
 
@@ -432,6 +434,72 @@ public final class FileUtilitiesTest extends AbstractFileSystemTestCase
 
     }
 
+    private class CountingActivityObserver implements IActivityObserver
+    {
+        int count = 0;
+
+        public void update()
+        {
+            ++count;
+        }
+    }
+
+    @Test
+    public void testListFiles() throws IOException
+    {
+        final File dir = new File(workingDirectory, "listFiles");
+        dir.mkdir();
+        final File nonExistentDir = new File(dir, "nonExistent");
+        assertTrue(FileUtilities.listFiles(dir, null, true, null).isEmpty());
+        assertTrue(FileUtilities.listFiles(nonExistentDir, null, true, null).isEmpty());
+        final File subDir = new File(dir, "subdir");
+        subDir.mkdir();
+        assertTrue(FileUtilities.listFiles(dir, null, true, null).isEmpty());
+        assertEquals("subdir", FileUtilities.listDirectories(dir, true, null).get(0).getName());
+        final File f1 = new File(dir, "f1.dat");
+        f1.createNewFile();
+        final File f2 = new File(dir, "f2.bla");
+        f2.createNewFile();
+        final File f3 = new File(subDir, "f3");
+        f3.createNewFile();
+        final File f4 = new File(subDir, "f4.dat");
+        f4.createNewFile();
+        final CountingActivityObserver observer = new CountingActivityObserver();
+
+        final List<File> list1 = FileUtilities.listFiles(dir, null, true, null);
+        assertEquals(4, list1.size());
+        assertEquals(new HashSet<File>(Arrays.asList(f1, f2, f3, f4)), new HashSet<File>(list1));
+
+        final List<File> list2 = FileUtilities.listFiles(dir, null, true, observer);
+        assertEquals(list1, list2);
+        assertTrue("" + observer.count, observer.count >= list2.size());
+
+        observer.count = 0;
+        final List<File> list3 = FileUtilities.listFiles(dir, new String[]
+            { "dat" }, true, observer);
+        assertEquals(2, list3.size());
+        assertEquals(new HashSet<File>(Arrays.asList(f1, f4)), new HashSet<File>(list3));
+        assertTrue("" + observer.count, observer.count >= list3.size());
+
+        final File subDir2 = new File(subDir, "subDir2");
+        subDir2.mkdir();
+        final File subDir3 = new File(dir, "subDir3");
+        subDir3.mkdir();
+        observer.count = 0;
+        final List<File> list4 = FileUtilities.listDirectories(dir, true, observer);
+        assertEquals(3, list4.size());
+        assertEquals(new HashSet<File>(Arrays.asList(subDir, subDir2, subDir3)), new HashSet<File>(
+                list4));
+        assertTrue("" + observer.count, observer.count >= list4.size());
+
+        observer.count = 0;
+        final List<File> list5 = FileUtilities.listDirectories(dir, false, observer);
+        assertEquals(2, list5.size());
+        assertEquals(new HashSet<File>(Arrays.asList(subDir, subDir3)), new HashSet<File>(
+                list5));
+        assertTrue("" + observer.count, observer.count >= list5.size());
+    }
+
     @Test
     public final void testNormalizeFile() throws IOException
     {
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/DataMover.java b/datamover/source/java/ch/systemsx/cisd/datamover/DataMover.java
index 1a0298af053294416863c26eac88ae5a6e9a52fe..3a23f00534696b8d28f1841925edabef5bb5b854 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/DataMover.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/DataMover.java
@@ -252,7 +252,7 @@ public final class DataMover
                 createRemotePathMover(readyToMoveStore, outgoingStore);
         final HighwaterMarkDirectoryScanningHandler directoryScanningHandler =
                 new HighwaterMarkDirectoryScanningHandler(new FaultyPathDirectoryScanningHandler(
-                        sourceDirectory), outgoingStore.getHighwaterMarkWatcher());
+                        sourceDirectory, remoteStoreMover), outgoingStore.getHighwaterMarkWatcher());
         final DirectoryScanningTimerTask outgoingMovingTask =
                 new DirectoryScanningTimerTask(sourceDirectory, FileUtilities.ACCEPT_ALL_FILTER,
                         remoteStoreMover, directoryScanningHandler);
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/IncomingProcessor.java b/datamover/source/java/ch/systemsx/cisd/datamover/IncomingProcessor.java
index 1662daf3f85d5287ce80f06e47986588ef1a3aa3..5b4df75ad7f498b498edb4749080031685584b90 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/IncomingProcessor.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/IncomingProcessor.java
@@ -159,10 +159,10 @@ public class IncomingProcessor implements IRecoverableTimerTaskFactory
         final File copyInProgressDir = bufferDirs.getCopyInProgressDir();
         final HighwaterMarkWatcher highwaterMarkWatcher =
                 new HighwaterMarkWatcher(bufferDirs.getBufferDirHighwaterMark());
+        final IStoreHandler pathHandler = createIncomingMovingPathHandler();
         final HighwaterMarkDirectoryScanningHandler directoryScanningHandler =
                 new HighwaterMarkDirectoryScanningHandler(new FaultyPathDirectoryScanningHandler(
-                        copyInProgressDir), highwaterMarkWatcher, copyInProgressDir);
-        final IStoreHandler pathHandler = createIncomingMovingPathHandler();
+                        copyInProgressDir, pathHandler), highwaterMarkWatcher, copyInProgressDir);
         final DirectoryScanningTimerTask movingTask =
                 new DirectoryScanningTimerTask(
                         new FileScannedStore(incomingStore, storeItemFilter),
@@ -206,6 +206,12 @@ public class IncomingProcessor implements IRecoverableTimerTaskFactory
                         moveFromLocalIncoming(extendedFileStore, sourceItem);
                     }
                 }
+
+                public boolean isStopped()
+                {
+                    return (incomingStore.tryAsExtended() == null) ? remotePathMover.isStopped()
+                            : false;
+                }
             };
     }
 
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/LocalProcessor.java b/datamover/source/java/ch/systemsx/cisd/datamover/LocalProcessor.java
index e7fe31f8391eaee68468f1bcbe58f3c6ef0699c7..5608239b1d8eb0d72d9436118a57fb691e15a0d4 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/LocalProcessor.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/LocalProcessor.java
@@ -26,6 +26,7 @@ import org.apache.commons.io.filefilter.RegexFileFilter;
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 
+import ch.systemsx.cisd.common.exceptions.StopException;
 import ch.systemsx.cisd.common.logging.ISimpleLogger;
 import ch.systemsx.cisd.common.logging.Log4jSimpleLogger;
 import ch.systemsx.cisd.common.logging.LogCategory;
@@ -80,6 +81,8 @@ public final class LocalProcessor implements IPathHandler, IRecoverableTimerTask
 
     private final File manualInterventionDir;
 
+    private boolean stopped = false;
+
     LocalProcessor(final Parameters parameters, final LocalBufferDirs bufferDirs,
             final IImmutableCopier copier, final IPathMover mover)
     {
@@ -249,63 +252,75 @@ public final class LocalProcessor implements IPathHandler, IRecoverableTimerTask
 
     public final void handle(final File path)
     {
-        final boolean continueProcessing = doMoveManualOrClean(path);
-        if (continueProcessing == false)
-        {
-            // Stop processing
-            return;
-        }
-        File extraTmpCopy = null;
-        if (extraCopyDirOrNull != null)
+        stopped = false;
+        try
         {
-            extraTmpCopy = new File(tempDir, path.getName());
-            if (extraTmpCopy.exists())
+            final boolean continueProcessing = doMoveManualOrClean(path);
+            if (continueProcessing == false)
             {
-                operationLog.warn(String.format(
-                        "Half-finished extra copy directory '%s' exists - removing it.",
-                        extraTmpCopy.getAbsolutePath()));
-                if (FileUtilities.deleteRecursively(extraTmpCopy) == false)
+                // Stop processing
+                return;
+            }
+            File extraTmpCopy = null;
+            if (extraCopyDirOrNull != null)
+            {
+                extraTmpCopy = new File(tempDir, path.getName());
+                if (extraTmpCopy.exists())
                 {
-                    notificationLog.error(String.format(
-                            "Removal of half-finished extra copy directory '%s' failed.",
+                    operationLog.warn(String.format(
+                            "Half-finished extra copy directory '%s' exists - removing it.",
                             extraTmpCopy.getAbsolutePath()));
+                    if (FileUtilities.deleteRecursively(extraTmpCopy) == false)
+                    {
+                        notificationLog.error(String.format(
+                                "Removal of half-finished extra copy directory '%s' failed.",
+                                extraTmpCopy.getAbsolutePath()));
+                        return;
+                    }
+                }
+                if (operationLog.isInfoEnabled())
+                {
+                    operationLog.info(String.format("Creating extra copy of directory '%s' to '%s'.",
+                            path.getAbsolutePath(), tempDir.getAbsoluteFile()));
+                }
+                final boolean ok = copier.copyImmutably(path, tempDir, null);
+                if (ok == false)
+                {
+                    notificationLog.error(String.format("Creating extra copy of '%s' failed.", path
+                            .getAbsolutePath()));
                     return;
                 }
             }
-            if (operationLog.isInfoEnabled())
+    
+            final File movedFile = mover.tryMove(path, outputDir);
+            if (movedFile == null)
             {
-                operationLog.info(String.format("Creating extra copy of directory '%s' to '%s'.",
-                        path.getAbsolutePath(), tempDir.getAbsoluteFile()));
-            }
-            final boolean ok = copier.copyImmutably(path, tempDir, null);
-            if (ok == false)
-            {
-                notificationLog.error(String.format("Creating extra copy of '%s' failed.", path
-                        .getAbsolutePath()));
+                notificationLog.error(String.format(
+                        "Moving '%s' to '%s' for final moving process failed.", path, outputDir));
                 return;
             }
-        }
-
-        final File movedFile = mover.tryMove(path, outputDir);
-        if (movedFile == null)
-        {
-            notificationLog.error(String.format(
-                    "Moving '%s' to '%s' for final moving process failed.", path, outputDir));
-            return;
-        }
-
-        if (extraTmpCopy != null)
-        {
-            assert extraCopyDirOrNull != null;
-            final File extraCopy = mover.tryMove(extraTmpCopy, extraCopyDirOrNull);
-            if (extraCopy == null)
+    
+            if (extraTmpCopy != null)
             {
-                notificationLog.error(String.format(
-                        "Moving temporary extra copy '%s' to destination '%s' failed.",
-                        extraTmpCopy, extraCopyDirOrNull));
+                assert extraCopyDirOrNull != null;
+                final File extraCopy = mover.tryMove(extraTmpCopy, extraCopyDirOrNull);
+                if (extraCopy == null)
+                {
+                    notificationLog.error(String.format(
+                            "Moving temporary extra copy '%s' to destination '%s' failed.",
+                            extraTmpCopy, extraCopyDirOrNull));
+                }
             }
+        } catch (StopException ex)
+        {
+            stopped = true;
         }
     }
+    
+    public boolean isStopped()
+    {
+        return stopped;
+    }
 
     //
     // IRecoverableTimerTaskFactory
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/FileSysOperationsFactory.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/FileSysOperationsFactory.java
index 44b9f85d62ccf6d2785ea6fde9db08f09ec08214..5f53a886a8651477760dc25620404440a5978a20 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/FileSysOperationsFactory.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/FileSysOperationsFactory.java
@@ -81,7 +81,7 @@ public class FileSysOperationsFactory implements IFileSysOperationsFactory
     public final IImmutableCopier getImmutableCopier()
     {
         final File rsyncExecutable = findRsyncExecutable();
-        return FastRecursiveHardLinkMaker.tryCreate(rsyncExecutable, true);
+        return FastRecursiveHardLinkMaker.create(rsyncExecutable);
     }
 
     public final IPathCopier getCopier(final boolean requiresDeletionBeforeCreation)
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/RetryingPathRemover.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/RetryingPathRemover.java
index c99f2fc67e04d7f26a2dc2a540160fb194a8c4d7..b6a8c7b4775f07d2560669158a023a0fdf0f54bb 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/RetryingPathRemover.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/RetryingPathRemover.java
@@ -28,15 +28,15 @@ import ch.rinn.restrictions.Private;
 import ch.systemsx.cisd.common.Constants;
 import ch.systemsx.cisd.common.concurrent.ConcurrencyUtilities;
 import ch.systemsx.cisd.common.concurrent.ExecutionResult;
+import ch.systemsx.cisd.common.concurrent.IActivityObserver;
 import ch.systemsx.cisd.common.concurrent.InactivityMonitor;
 import ch.systemsx.cisd.common.concurrent.NamingThreadPoolExecutor;
-import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IActivitySensor;
+import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IDescribingActivitySensor;
 import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IInactivityObserver;
 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.FileUtilities;
-import ch.systemsx.cisd.common.utilities.FileUtilities.SimpleActivityObserver;
 import ch.systemsx.cisd.datamover.filesystem.intf.IPathRemover;
 
 /**
@@ -125,9 +125,9 @@ final class RetryingPathRemover implements IPathRemover
     }
 
     @Private
-    static class DeleteActivityDetector implements IActivitySensor, SimpleActivityObserver
+    static class DeleteActivityDetector implements IDescribingActivitySensor, IActivityObserver
     {
-        private volatile long lastActivityTime = System.currentTimeMillis();
+        private volatile long lastActivityMillis = System.currentTimeMillis();
 
         private final File path;
 
@@ -139,18 +139,23 @@ final class RetryingPathRemover implements IPathRemover
         // called each time when one file gets deleted
         synchronized public void update()
         {
-            lastActivityTime = System.currentTimeMillis();
+            lastActivityMillis = System.currentTimeMillis();
         }
 
         synchronized public String describeInactivity(long now)
         {
             return "No delete activity of path " + path.getPath() + " for "
-                    + DurationFormatUtils.formatDurationHMS(now - lastActivityTime);
+                    + DurationFormatUtils.formatDurationHMS(now - lastActivityMillis);
         }
 
-        synchronized public long getTimeOfLastActivityMoreRecentThan(long thresholdMillis)
+        synchronized public long getLastActivityMillisMoreRecentThan(long thresholdMillis)
         {
-            return lastActivityTime;
+            return lastActivityMillis;
+        }
+
+        synchronized public boolean hasActivityMoreRecentThan(long thresholdMillis)
+        {
+            return (System.currentTimeMillis() - lastActivityMillis) < thresholdMillis;
         }
 
     }
@@ -170,7 +175,7 @@ final class RetryingPathRemover implements IPathRemover
     }
 
     @Private
-    Boolean executeAndMonitor(final IActivitySensor sensor, final Callable<Boolean> deleteCallable,
+    Boolean executeAndMonitor(final IDescribingActivitySensor sensor, final Callable<Boolean> deleteCallable,
             final long inactivityThresholdMillis)
     {
         final Future<Boolean> deleteFuture = executor.submit(deleteCallable);
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/RemotePathMover.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/RemotePathMover.java
index 2f163b73cc5e34948542aa63a3ba4161dc9c3ac3..e177aeffd6a7eb4802334ed1712cc7cc4d7ee484 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/RemotePathMover.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/RemotePathMover.java
@@ -62,6 +62,9 @@ public final class RemotePathMover implements IStoreHandler
     private static final String MOVING_PATH_TO_REMOTE_FAILED_TEMPLATE =
             "Moving path '%s' to remote directory '%s' failed.";
 
+    private static final String MOVING_PATH_TO_REMOTE_STOPPED_TEMPLATE =
+            "Moving path '%s' to remote directory '%s' was stopped.";
+
     private static final String REMOVING_LOCAL_PATH_FAILED_TEMPLATE =
             "Removing local path '%s' failed (%s).";
 
@@ -97,6 +100,8 @@ public final class RemotePathMover implements IStoreHandler
 
     private final int maximalNumberOfRetries;
 
+    private boolean stopped;
+
     /**
      * Creates a <var>PathRemoteMover</var>.
      * 
@@ -108,8 +113,8 @@ public final class RemotePathMover implements IStoreHandler
      * @throws ConfigurationFailureException If the destination directory is not fully accessible.
      */
     public RemotePathMover(final IFileStore sourceDirectory, final IFileStore destinationDirectory,
-            final IStoreCopier copier,
-            final ITimingParameters timingParameters) throws ConfigurationFailureException
+            final IStoreCopier copier, final ITimingParameters timingParameters)
+            throws ConfigurationFailureException
     {
         assert sourceDirectory != null;
         assert destinationDirectory != null;
@@ -123,6 +128,7 @@ public final class RemotePathMover implements IStoreHandler
         this.intervallToWaitAfterFailure = timingParameters.getIntervalToWaitAfterFailure();
         this.maximalNumberOfRetries = timingParameters.getMaximalNumberOfRetries();
         this.inactivityPeriodMillis = timingParameters.getInactivityPeriodMillis();
+        this.stopped = false;
 
         assert intervallToWaitAfterFailure >= 0;
         assert maximalNumberOfRetries >= 0;
@@ -143,8 +149,9 @@ public final class RemotePathMover implements IStoreHandler
                                 public void update(long inactiveSinceMillis,
                                         String descriptionOfInactivity)
                                 {
-                                    operationLog.warn(String.format(TERMINATING_COPIER_LOG_TEMPLATE,
-                                            copier.getClass().getName(), descriptionOfInactivity));
+                                    operationLog.warn(String.format(
+                                            TERMINATING_COPIER_LOG_TEMPLATE, copier.getClass()
+                                                    .getName(), descriptionOfInactivity));
                                     copier.terminate();
                                 }
                             }, inactivityPeriodMillis, true);
@@ -336,6 +343,11 @@ public final class RemotePathMover implements IStoreHandler
         int tryCount = 0;
         do
         {
+            if (Thread.interrupted())
+            {
+                stopped = true;
+                break;
+            }
             if (operationLog.isInfoEnabled())
             {
                 if (tryCount > 0) // This is a retry
@@ -384,12 +396,24 @@ public final class RemotePathMover implements IStoreHandler
                 Thread.sleep(intervallToWaitAfterFailure);
             } catch (final InterruptedException e)
             {
-                // We don't expect to get interrupted, but even if, there is no need to handle this
-                // here.
+                stopped = true;
+                break;
             }
         } while (true);
-        notificationLog.error(String.format(MOVING_PATH_TO_REMOTE_FAILED_TEMPLATE,
-                getSrcPath(item), destinationDirectory));
+        if (stopped)
+        {
+            operationLog.warn(String.format(MOVING_PATH_TO_REMOTE_STOPPED_TEMPLATE,
+                    getSrcPath(item), destinationDirectory));
+        } else
+        {
+            notificationLog.error(String.format(MOVING_PATH_TO_REMOTE_FAILED_TEMPLATE,
+                    getSrcPath(item), destinationDirectory));
+        }
+    }
+
+    public boolean isStopped()
+    {
+        return stopped;
     }
 
 }
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/RemoteStoreCopyActivitySensor.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/RemoteStoreCopyActivitySensor.java
index f434f8599d3011df18151f702983710863e92716..37addf69193b59b9f6e618ae341ef0c8cf262aa5 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/RemoteStoreCopyActivitySensor.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/RemoteStoreCopyActivitySensor.java
@@ -18,7 +18,7 @@ package ch.systemsx.cisd.datamover.filesystem.remote;
 
 import org.apache.log4j.Logger;
 
-import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IActivitySensor;
+import ch.systemsx.cisd.common.concurrent.InactivityMonitor.IDescribingActivitySensor;
 import ch.systemsx.cisd.common.exceptions.StatusWithResult;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
@@ -27,15 +27,15 @@ import ch.systemsx.cisd.common.utilities.StoreItem;
 import ch.systemsx.cisd.datamover.filesystem.intf.IFileStore;
 
 /**
- * A {@link IActivitySensor} that senses changes in copy operations to a {@link StoreItem} in a
- * remote store.
+ * A {@link IDescribingActivitySensor} that senses changes in copy operations to a {@link StoreItem}
+ * in a remote store.
  * 
  * @author Bernd Rinn
  */
 public class RemoteStoreCopyActivitySensor extends AbstractCopyActivitySensor
 {
     private final static Logger machineLog =
-        LogFactory.getLogger(LogCategory.MACHINE, RemoteStoreCopyActivitySensor.class);
+            LogFactory.getLogger(LogCategory.MACHINE, RemoteStoreCopyActivitySensor.class);
 
     private final IFileStore destinationStore;
 
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/store/FileStoreRemoteMounted.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/store/FileStoreRemoteMounted.java
index b3d8c25614d0cde297f6fbe89c38b68830e7e24b..91ef614321ccb927ecbf96a9dd059f39587ef60c 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/store/FileStoreRemoteMounted.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/store/FileStoreRemoteMounted.java
@@ -16,6 +16,7 @@
 
 package ch.systemsx.cisd.datamover.filesystem.store;
 
+import ch.systemsx.cisd.common.TimingParameters;
 import ch.systemsx.cisd.common.concurrent.MonitoringProxy;
 import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
@@ -58,8 +59,8 @@ public final class FileStoreRemoteMounted extends AbstractFileStore
         super(file, description, factory);
         this.localImpl = new FileStoreLocal(file, description, factory);
         this.localImplMonitored =
-                MonitoringProxy.create(IFileStore.class, localImpl).timeoutMillis(
-                        lastChangedTimeoutMillis).get();
+                MonitoringProxy.create(IFileStore.class, localImpl).timing(
+                        TimingParameters.create(lastChangedTimeoutMillis)).get();
     }
 
     //
diff --git a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/filesystem/remote/RemoteStoreCopyActivitySensorTest.java b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/filesystem/remote/RemoteStoreCopyActivitySensorTest.java
index ee847deb628b3cbb62ab31160ef41f5963d03d9b..f1347e7a627821c2f2c8b9bf4903f1fc27f8005b 100644
--- a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/filesystem/remote/RemoteStoreCopyActivitySensorTest.java
+++ b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/filesystem/remote/RemoteStoreCopyActivitySensorTest.java
@@ -92,7 +92,7 @@ public class RemoteStoreCopyActivitySensorTest
             });
         final long delta =
                 System.currentTimeMillis()
-                        - sensorUnderTest.getTimeOfLastActivityMoreRecentThan(THRESHOLD);
+                        - sensorUnderTest.getLastActivityMillisMoreRecentThan(THRESHOLD);
         assertTrue("Delta is " + delta, delta < MAX_DELTA);
         final String msg = sensorUnderTest.describeInactivity(System.currentTimeMillis());
         assertTrue(
@@ -113,7 +113,7 @@ public class RemoteStoreCopyActivitySensorTest
                 }
             });
         final long now = System.currentTimeMillis();
-        final long delta = now - sensorUnderTest.getTimeOfLastActivityMoreRecentThan(THRESHOLD);
+        final long delta = now - sensorUnderTest.getLastActivityMillisMoreRecentThan(THRESHOLD);
         assertTrue("Delta is " + delta, delta < MAX_DELTA);
         assertEquals("Error: Unable to determine the time of write activity on "
                 + "item 'I am probed' in store 'iFileStore'\nERROR message", sensorUnderTest
@@ -133,14 +133,14 @@ public class RemoteStoreCopyActivitySensorTest
             });
         ConcurrencyUtilities.sleep(10L);
         final long now = System.currentTimeMillis();
-        final long lastActivity1 = sensorUnderTest.getTimeOfLastActivityMoreRecentThan(THRESHOLD);
+        final long lastActivity1 = sensorUnderTest.getLastActivityMillisMoreRecentThan(THRESHOLD);
         final long delta = lastActivity1 - now;
         assertTrue("Delta is " + delta, delta < MAX_DELTA);
         ConcurrencyUtilities.sleep(10L);
-        final long lastActivity2 = sensorUnderTest.getTimeOfLastActivityMoreRecentThan(THRESHOLD);
+        final long lastActivity2 = sensorUnderTest.getLastActivityMillisMoreRecentThan(THRESHOLD);
         assertEquals(lastActivity1, lastActivity2);
         ConcurrencyUtilities.sleep(10L);
-        final long lastActivity3 = sensorUnderTest.getTimeOfLastActivityMoreRecentThan(THRESHOLD);
+        final long lastActivity3 = sensorUnderTest.getLastActivityMillisMoreRecentThan(THRESHOLD);
         assertEquals(lastActivity1, lastActivity3);
     }
 
@@ -159,14 +159,14 @@ public class RemoteStoreCopyActivitySensorTest
             });
         ConcurrencyUtilities.sleep(10L);
         final long now1 = System.currentTimeMillis();
-        final long lastActivity1 = sensorUnderTest.getTimeOfLastActivityMoreRecentThan(THRESHOLD);
+        final long lastActivity1 = sensorUnderTest.getLastActivityMillisMoreRecentThan(THRESHOLD);
         assertTrue("Delta=" + (lastActivity1 - now1), lastActivity1 - now1 < MAX_DELTA);
         ConcurrencyUtilities.sleep(10L);
         final long now2 = System.currentTimeMillis();
-        final long lastActivity2 = sensorUnderTest.getTimeOfLastActivityMoreRecentThan(THRESHOLD);
+        final long lastActivity2 = sensorUnderTest.getLastActivityMillisMoreRecentThan(THRESHOLD);
         assertTrue("Delta=" + (lastActivity2 - now2), lastActivity2 - now2 < MAX_DELTA);
         ConcurrencyUtilities.sleep(10L);
-        final long lastActivity3 = sensorUnderTest.getTimeOfLastActivityMoreRecentThan(THRESHOLD);
+        final long lastActivity3 = sensorUnderTest.getLastActivityMillisMoreRecentThan(THRESHOLD);
         assertEquals(lastActivity1, lastActivity3);
     }