diff --git a/common/source/java/ch/systemsx/cisd/common/Constants.java b/common/source/java/ch/systemsx/cisd/common/Constants.java
index b6e0a6f06b125ee619397c710d8d0b22bb464d20..a3df6c694a0da4fa2235b31bbceeb770ce520d87 100644
--- a/common/source/java/ch/systemsx/cisd/common/Constants.java
+++ b/common/source/java/ch/systemsx/cisd/common/Constants.java
@@ -47,6 +47,9 @@ public final class Constants
     /** The number of milliseconds to sleep before retrying (<i>3s</i>). */
     public static final long MILLIS_TO_SLEEP_BEFORE_RETRYING = 3 * DateUtils.MILLIS_PER_SECOND;
 
+    /** The number of retries when a given process failed. */
+    public static final int MAXIMUM_RETRY_COUNT = 12;
+
     /** The prefix of marker files that indicate that a directory is currently being processed. */
     public static final String PROCESSING_PREFIX = MARKER_PREFIX + "processing_";
 }
diff --git a/common/source/java/ch/systemsx/cisd/common/concurrent/ExecutionResult.java b/common/source/java/ch/systemsx/cisd/common/concurrent/ExecutionResult.java
index 48bd83076d98f79dfc4f10dbb34567139cecb3a9..a79967f6c5d8cccf3c6d6e9fc9de86eaf7b3da9d 100644
--- a/common/source/java/ch/systemsx/cisd/common/concurrent/ExecutionResult.java
+++ b/common/source/java/ch/systemsx/cisd/common/concurrent/ExecutionResult.java
@@ -19,13 +19,17 @@ package ch.systemsx.cisd.common.concurrent;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+import ch.systemsx.cisd.common.utilities.ModifiedShortPrefixToStringStyle;
+
 /**
  * A class that contains the result of the execution of a {@link Runnable} (or {@link Callable}) in
  * an {@link ExecutorService}.
  * 
  * @author Bernd Rinn
  */
-public class ExecutionResult<T>
+public final class ExecutionResult<T>
 {
     private final ExecutionStatus status;
 
@@ -33,7 +37,8 @@ public class ExecutionResult<T>
 
     private final Throwable exceptionOrNull;
 
-    private ExecutionResult(final ExecutionStatus status, final T resultOrNull, final Throwable exceptionOrNull)
+    private ExecutionResult(final ExecutionStatus status, final T resultOrNull,
+            final Throwable exceptionOrNull)
     {
         this.status = status;
         this.resultOrNull = resultOrNull;
@@ -45,7 +50,7 @@ public class ExecutionResult<T>
      * {@link Runnable} can also provide a <code>null</code> result, <code>null</code> is an
      * accepted value for the result.
      */
-    static <T> ExecutionResult<T> create(final T resultOrNull)
+    static final <T> ExecutionResult<T> create(final T resultOrNull)
     {
         return new ExecutionResult<T>(ExecutionStatus.COMPLETE, resultOrNull, null);
     }
@@ -53,7 +58,7 @@ public class ExecutionResult<T>
     /**
      * Creates an {@link ExecutionResult} that corresponds to an exception.
      */
-    static <T> ExecutionResult<T> createExceptional(final Throwable exception)
+    static final <T> ExecutionResult<T> createExceptional(final Throwable exception)
     {
         assert exception != null;
 
@@ -63,7 +68,7 @@ public class ExecutionResult<T>
     /**
      * Creates an {@link ExecutionResult} that corresponds to a time out.
      */
-    static <T> ExecutionResult<T> createTimedOut()
+    static final <T> ExecutionResult<T> createTimedOut()
     {
         return new ExecutionResult<T>(ExecutionStatus.TIMED_OUT, null, null);
     }
@@ -71,7 +76,7 @@ public class ExecutionResult<T>
     /**
      * Creates an {@link ExecutionResult} that corresponds to an interruption.
      */
-    static <T> ExecutionResult<T> createInterrupted()
+    static final <T> ExecutionResult<T> createInterrupted()
     {
         return new ExecutionResult<T>(ExecutionStatus.INTERRUPTED, null, null);
     }
@@ -79,7 +84,7 @@ public class ExecutionResult<T>
     /**
      * Returns the {@link ExecutionStatus} of the execution.
      */
-    public ExecutionStatus getStatus()
+    public final ExecutionStatus getStatus()
     {
         return status;
     }
@@ -88,7 +93,7 @@ public class ExecutionResult<T>
      * Returns the returned result of the execution, or <code>null</code>, if either the status
      * is not {@link ExecutionStatus#COMPLETE} or if the execution didn't provide a result.
      */
-    public T tryGetResult()
+    public final T tryGetResult()
     {
         return resultOrNull;
     }
@@ -97,9 +102,19 @@ public class ExecutionResult<T>
      * Returns the thrown exception (or error) of the execution, or <code>null</code>, if the
      * status is not {@link ExecutionStatus#EXCEPTION}.
      */
-    public Throwable tryGetException()
+    public final Throwable tryGetException()
     {
         return exceptionOrNull;
     }
 
-}
+    //
+    // Object
+    //
+
+    @Override
+    public final String toString()
+    {
+        return ToStringBuilder.reflectionToString(this,
+                ModifiedShortPrefixToStringStyle.MODIFIED_SHORT_PREFIX_STYLE);
+    }
+}
\ No newline at end of file
diff --git a/common/source/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkSelfTestable.java b/common/source/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkSelfTestable.java
index 2e1bd91120c933f7d2a664241a8a88bbfd3a7393..0033c7c8b99dbd18628a0de9c13b28c5725585ea 100644
--- a/common/source/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkSelfTestable.java
+++ b/common/source/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkSelfTestable.java
@@ -17,7 +17,6 @@
 package ch.systemsx.cisd.common.highwatermark;
 
 import java.io.File;
-import java.io.IOException;
 
 import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
@@ -31,6 +30,8 @@ import ch.systemsx.cisd.common.utilities.ISelfTestable;
  */
 public final class HighwaterMarkSelfTestable implements ISelfTestable
 {
+    static final String EXCEPTION_FORMAT =
+            "Highwater mark reached on '%s', required: %s, found: %s.";
 
     private final File path;
 
@@ -49,25 +50,17 @@ public final class HighwaterMarkSelfTestable implements ISelfTestable
 
     public final void check() throws EnvironmentFailureException, ConfigurationFailureException
     {
-        try
+        final HighwaterMarkState highwaterMarkState =
+                highwaterMarkWatcher.getHighwaterMarkState(path);
+        if (HighwaterMarkWatcher.isBelow(highwaterMarkState))
         {
-            final HighwaterMarkState highwaterMarkState =
-                    highwaterMarkWatcher.getHighwaterMarkState(path);
-            if (HighwaterMarkWatcher.isBelow(highwaterMarkState))
-            {
-                final String freeSpaceDisplayed =
-                        HighwaterMarkWatcher
-                                .displayKilobyteValue(highwaterMarkState.getFreeSpace());
-                final String highwaterMarkDisplayed =
-                        HighwaterMarkWatcher.displayKilobyteValue(highwaterMarkState
-                                .getHighwaterMark());
-                throw ConfigurationFailureException.fromTemplate(
-                        "Free space (%s) lies below given high water mark (%s).",
-                        freeSpaceDisplayed, highwaterMarkDisplayed);
-            }
-        } catch (IOException ex)
-        {
-            throw new EnvironmentFailureException(ex.getMessage());
+            final String freeSpaceDisplayed =
+                    HighwaterMarkWatcher.displayKilobyteValue(highwaterMarkState.getFreeSpace());
+            final String highwaterMarkDisplayed =
+                    HighwaterMarkWatcher
+                            .displayKilobyteValue(highwaterMarkState.getHighwaterMark());
+            throw ConfigurationFailureException.fromTemplate(EXCEPTION_FORMAT, path,
+                    highwaterMarkDisplayed, freeSpaceDisplayed);
         }
     }
 }
\ No newline at end of file
diff --git a/common/source/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkWatcher.java b/common/source/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkWatcher.java
index c4226ac31014a77236c0714f05d5c2fc97403013..1ae3cc404b0add851526984233f924e84f40032c 100644
--- a/common/source/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkWatcher.java
+++ b/common/source/java/ch/systemsx/cisd/common/highwatermark/HighwaterMarkWatcher.java
@@ -19,6 +19,7 @@ package ch.systemsx.cisd.common.highwatermark;
 import java.io.File;
 import java.io.IOException;
 import java.io.Serializable;
+import java.util.concurrent.Callable;
 
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
@@ -28,8 +29,11 @@ import org.apache.commons.io.FileSystemUtils;
 import org.apache.commons.io.FileUtils;
 import org.apache.log4j.Logger;
 
+import ch.systemsx.cisd.common.Constants;
+import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
+import ch.systemsx.cisd.common.process.CallableExecutor;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
 
 /**
@@ -166,10 +170,36 @@ public final class HighwaterMarkWatcher implements Runnable
     /**
      * Analyzes given <var>path</var> and returns a {@link HighwaterMarkState}.
      */
-    public final HighwaterMarkState getHighwaterMarkState(final File file) throws IOException
+    public final HighwaterMarkState getHighwaterMarkState(final File file)
+            throws EnvironmentFailureException
     {
         assert file != null : "Unspecified file";
-        final long freeSpaceInKb = freeSpaceProvider.freeSpaceKb(file);
+        final String errorMsg =
+                String.format("Could not compute available free space for '%s'.", file);
+        final Long freeSpaceInKb =
+                new CallableExecutor(5, Constants.MILLIS_TO_SLEEP_BEFORE_RETRYING)
+                        .executeCallable(new Callable<Long>()
+                            {
+
+                                //
+                                // Callable
+                                //
+
+                                public Long call() throws Exception
+                                {
+                                    try
+                                    {
+                                        return freeSpaceProvider.freeSpaceKb(file);
+                                    } catch (final IOException ex)
+                                    {
+                                        return null;
+                                    }
+                                }
+                            });
+        if (freeSpaceInKb == null)
+        {
+            throw new EnvironmentFailureException(errorMsg);
+        }
         return new HighwaterMarkState(new FileWithHighwaterMark(file, highwaterMarkInKb),
                 freeSpaceInKb);
     }
@@ -205,17 +235,15 @@ public final class HighwaterMarkWatcher implements Runnable
             }
             if (operationLog.isDebugEnabled())
             {
-                operationLog.debug(String.format(
-                        "Amount of available space on '%s' is %s (high water mark: %s).",
+                operationLog.debug(String.format("Free space on '%s': %s, highwater mark: %s.",
                         state.fileWithHighwaterMark.getCanonicalPath(),
                         displayKilobyteValue(state.freeSpace),
                         displayKilobyteValue(highwaterMarkInKb)));
             }
-        } catch (final IOException ex)
+        } catch (final EnvironmentFailureException ex)
         {
-            operationLog.error(
-                    "The high water mark watcher can not work properly due to an I/O exception.",
-                    ex);
+            operationLog.error("The highwater mark watcher can not work properly "
+                    + "due to an environment exception.", ex);
         }
     }
 
diff --git a/common/source/java/ch/systemsx/cisd/common/highwatermark/NonHangingFreeSpaceProvider.java b/common/source/java/ch/systemsx/cisd/common/highwatermark/NonHangingFreeSpaceProvider.java
index 4174acdc9ad648c2584a403f43a4b643c69812a7..f67b4886d999acabb1825d5385baca7f8658c64e 100644
--- a/common/source/java/ch/systemsx/cisd/common/highwatermark/NonHangingFreeSpaceProvider.java
+++ b/common/source/java/ch/systemsx/cisd/common/highwatermark/NonHangingFreeSpaceProvider.java
@@ -60,9 +60,8 @@ public final class NonHangingFreeSpaceProvider extends DelegateFreeSpaceProvider
             return resultOrNull;
         } else
         {
-            final Throwable cause = executionResult.tryGetException();
-            throw new IOException(cause == null ? String.format(
-                    "Computing free space on '%s' failed.", path) : cause.getMessage());
+            throw new IOException(String.format("Computing free space on '%s' failed: %s", path,
+                    executionResult));
         }
     }
 
diff --git a/common/source/java/ch/systemsx/cisd/common/process/CallableExecutor.java b/common/source/java/ch/systemsx/cisd/common/process/CallableExecutor.java
new file mode 100644
index 0000000000000000000000000000000000000000..da03f2e9908ac8f9bbc8aa75bbb6a2540948901c
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/process/CallableExecutor.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.process;
+
+import java.util.concurrent.Callable;
+
+import ch.systemsx.cisd.common.Constants;
+import ch.systemsx.cisd.common.exceptions.CheckedExceptionTunnel;
+import ch.systemsx.cisd.common.exceptions.StopException;
+
+/**
+ * Executes given {@link Callable}.
+ * 
+ * @author Christian Ribeaud
+ */
+public final class CallableExecutor
+{
+    private final int maxRetryOnFailure;
+
+    private final long millisToSleepOnFailure;
+
+    public CallableExecutor()
+    {
+        this(Constants.MAXIMUM_RETRY_COUNT, Constants.MILLIS_TO_SLEEP_BEFORE_RETRYING);
+    }
+
+    public CallableExecutor(final int maxRetryOnFailure, final long millisToSleepOnFailure)
+    {
+        assert millisToSleepOnFailure > -1 : "Negative value";
+        assert maxRetryOnFailure > -1 : "Negative value";
+        this.maxRetryOnFailure = maxRetryOnFailure;
+        this.millisToSleepOnFailure = millisToSleepOnFailure;
+    }
+
+    /**
+     * Executes given <var>callable</var> until it returns a non-<code>null</code> value (or
+     * until <code>maxRetryOnFailure</code> is reached).
+     */
+    public final <T> T executeCallable(final Callable<T> callable)
+    {
+        int counter = 0;
+        T result = null;
+        try
+        {
+            do
+            {
+                StopException.check();
+                result = callable.call();
+                if (counter > 0 && millisToSleepOnFailure > 0)
+                {
+                    try
+                    {
+                        Thread.sleep(millisToSleepOnFailure);
+                    } catch (final InterruptedException ex)
+                    {
+                        throw new CheckedExceptionTunnel(ex);
+                    }
+                }
+            } while (counter++ < maxRetryOnFailure && result == null);
+        } catch (final Exception ex)
+        {
+            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+        }
+        return result;
+    }
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/process/FileRenamingCallable.java b/common/source/java/ch/systemsx/cisd/common/process/FileRenamingCallable.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5d935731f43a1de8f956272017cbe092f0a2867
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/process/FileRenamingCallable.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.process;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+import org.apache.log4j.Logger;
+
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
+
+/**
+ * A <code>IProcess</code> implementation for file renaming.
+ * 
+ * @author Christian Ribeaud
+ */
+public final class FileRenamingCallable implements Callable<Boolean>
+{
+    private static final Logger operationLog =
+            LogFactory.getLogger(LogCategory.OPERATION, FileRenamingCallable.class);
+
+    private final File sourceFile;
+
+    private final File destinationFile;
+
+    private int failures;
+
+    public FileRenamingCallable(final File sourceFile, final File destinationFile)
+    {
+        assert sourceFile != null : "Unspecified source file";
+        assert destinationFile != null : "Unspecified d file";
+        this.sourceFile = sourceFile;
+        this.destinationFile = destinationFile;
+    }
+
+    //
+    // Callable
+    //
+
+    public final Boolean call() throws Exception
+    {
+        if (sourceFile.exists() == false)
+        {
+            operationLog.error(String.format(
+                    "Path '%s' doesn't exist, so it can't be moved to '%s'.", sourceFile,
+                    destinationFile));
+            // Nothing to do here. So exit the looping by returning true.
+            return true;
+        }
+        if (destinationFile.exists())
+        {
+            operationLog.error(String.format("Destination path '%s' already exists.",
+                    destinationFile));
+            // Nothing to do here. So exit the looping by returning true.
+            return true;
+        }
+        operationLog.warn(String.format("Moving path '%s' to directory '%s' failed (attempt %d).",
+                sourceFile, destinationFile, ++failures));
+        final boolean renamed = sourceFile.renameTo(destinationFile);
+        return renamed ? true : null;
+    }
+}
\ No newline at end of file
diff --git a/common/source/java/ch/systemsx/cisd/common/process/FileRenamingProcess.java b/common/source/java/ch/systemsx/cisd/common/process/FileRenamingProcess.java
deleted file mode 100644
index 035f185959de47022ea0978a896dd00f46741fe9..0000000000000000000000000000000000000000
--- a/common/source/java/ch/systemsx/cisd/common/process/FileRenamingProcess.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2007 ETH Zuerich, CISD
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.systemsx.cisd.common.process;
-
-import java.io.File;
-
-import org.apache.log4j.Logger;
-
-import ch.systemsx.cisd.common.Constants;
-import ch.systemsx.cisd.common.logging.LogCategory;
-import ch.systemsx.cisd.common.logging.LogFactory;
-
-/**
- * A <code>IProcess</code> implementation for file renaming.
- * 
- * @author Christian Ribeaud
- */
-public final class FileRenamingProcess implements IProcess
-{
-    private static final Logger operationLog =
-            LogFactory.getLogger(LogCategory.OPERATION, FileRenamingProcess.class);
-
-    public static final int DEFAULT_MAX_RETRIES = 12;
-
-    private final File sourceFile;
-
-    private final File destinationFile;
-
-    private final int maxRetries;
-
-    private final long millisToSleep;
-
-    private int failures;
-
-    private boolean renamed;
-
-    public FileRenamingProcess(final File sourceFile, final File destinationFile)
-    {
-        this(DEFAULT_MAX_RETRIES, Constants.MILLIS_TO_SLEEP_BEFORE_RETRYING, sourceFile, destinationFile);
-    }
-
-    public FileRenamingProcess(final int maxRetries, final long millisToSleep,
-            final File sourceFile, final File destinationFile)
-    {
-        this.sourceFile = sourceFile;
-        this.maxRetries = maxRetries;
-        this.millisToSleep = millisToSleep;
-        this.destinationFile = destinationFile;
-    }
-
-    /**
-     * Whether the file has been renamed.
-     * <p>
-     * This is the return value of {@link File#renameTo(File)}.
-     * </p>
-     */
-    public final boolean isRenamed()
-    {
-        return renamed;
-    }
-
-    //
-    // IProcess
-    //
-
-    public final int getMaxRetryOnFailure()
-    {
-        return maxRetries;
-    }
-
-    public final long getMillisToSleepOnFailure()
-    {
-        return millisToSleep;
-    }
-
-    public final void run()
-    {
-        renamed = sourceFile.renameTo(destinationFile);
-    }
-
-    public final boolean succeeded()
-    {
-        if (renamed == false)
-        {
-            if (sourceFile.exists() == false)
-            {
-                operationLog.error(String.format(
-                        "Path '%s' doesn't exist, so it can't be moved to '%s'.", sourceFile,
-                        destinationFile));
-                // Nothing to do here. So exit the looping by returning true.
-                return true;
-            }
-            if (destinationFile.exists())
-            {
-                operationLog.error(String.format("Destination path '%s' already exists.",
-                        destinationFile));
-                // Nothing to do here. So exit the looping by returning true.
-                return true;
-            }
-            operationLog.warn(String.format(
-                    "Moving path '%s' to directory '%s' failed (attempt %d).", sourceFile,
-                    destinationFile, ++failures));
-        }
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/common/source/java/ch/systemsx/cisd/common/process/IProcess.java b/common/source/java/ch/systemsx/cisd/common/process/IProcess.java
deleted file mode 100644
index 7d7bcb0cafcfbfc22c3d59b0a5736c17193cb30d..0000000000000000000000000000000000000000
--- a/common/source/java/ch/systemsx/cisd/common/process/IProcess.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2007 ETH Zuerich, CISD
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.systemsx.cisd.common.process;
-
-/**
- * A <code>Runnable</code> extension that describes a process.
- * 
- * @author Christian Ribeaud
- */
-public interface IProcess extends Runnable
-{
-
-    /**
-     * Whether this <code>IProcess</code> exited successfully.
-     * <p>
-     * Is typically called after {@link Runnable#run()} has performed.
-     * </p>
-     * 
-     * @return <code>true</code> if this <code>IProcess</code> succeeds, terminating so the
-     *         whole running process.
-     */
-    public boolean succeeded();
-
-    /**
-     * The number of times we should try if this <code>IProcess</code> failed (including the first
-     * excecution).
-     * <p>
-     * This is a static method: it only gets called once during the initialization process.
-     * </p>
-     */
-    public int getMaxRetryOnFailure();
-
-    /**
-     * The number of milliseconds we should wait before re-executing {@link Runnable#run()}.
-     * <p>
-     * This is a static method: it only gets called once during the initialization process.
-     * </p>
-     */
-    public long getMillisToSleepOnFailure();
-}
diff --git a/common/source/java/ch/systemsx/cisd/common/process/ProcessRunner.java b/common/source/java/ch/systemsx/cisd/common/process/ProcessRunner.java
deleted file mode 100644
index 0895b0ceef38fcad203ab19571368cb25ceadfeb..0000000000000000000000000000000000000000
--- a/common/source/java/ch/systemsx/cisd/common/process/ProcessRunner.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2007 ETH Zuerich, CISD
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.systemsx.cisd.common.process;
-
-import ch.systemsx.cisd.common.exceptions.CheckedExceptionTunnel;
-import ch.systemsx.cisd.common.exceptions.StopException;
-
-/**
- * This class takes cares of running encapsulated {@link IProcess}.
- * 
- * @author Christian Ribeaud
- */
-public final class ProcessRunner
-{
-
-    public ProcessRunner(final IProcess process)
-    {
-        runProcess(process);
-    }
-
-    final static void runProcess(final IProcess process)
-    {
-        final int maxRetryOnFailure = process.getMaxRetryOnFailure();
-        final long millisToSleepOnFailure = process.getMillisToSleepOnFailure();
-        assert millisToSleepOnFailure > -1;
-        int counter = 0;
-        do
-        {
-            StopException.check();
-            process.run();
-            if (counter > 0 && millisToSleepOnFailure > 0)
-            {
-                try
-                {
-                    Thread.sleep(millisToSleepOnFailure);
-                } catch (InterruptedException ex)
-                {
-                    throw new CheckedExceptionTunnel(ex);
-                }
-            }
-        } while (++counter < maxRetryOnFailure && process.succeeded() == false);
-    }
-}
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/RecursiveHardLinkMaker.java b/common/source/java/ch/systemsx/cisd/common/utilities/RecursiveHardLinkMaker.java
index 73b8435c96c4bbf9b2200b27df3b352523bf5ed6..5ec67dee50198456380f0f1c806d2a4b4952f658 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/RecursiveHardLinkMaker.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/RecursiveHardLinkMaker.java
@@ -20,6 +20,7 @@ import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Callable;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.log4j.Logger;
@@ -27,9 +28,8 @@ import org.apache.log4j.Logger;
 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.process.IProcess;
+import ch.systemsx.cisd.common.process.CallableExecutor;
 import ch.systemsx.cisd.common.process.ProcessExecutionHelper;
-import ch.systemsx.cisd.common.process.ProcessRunner;
 
 /**
  * Utility to create a hard link of a file or copy recursively a directories structure, creating a
@@ -241,9 +241,13 @@ public final class RecursiveHardLinkMaker implements IPathImmutableCopier
         assert file.isFile() : String.format("Given file '%s' must be a file and is not.", file);
         final File destFile = new File(destDir, nameOrNull == null ? file.getName() : nameOrNull);
         final List<String> cmd = createLnCmdLine(file, destFile);
-        IProcessTask processTask = new IProcessTask()
+        final Callable<Boolean> processTask = new Callable<Boolean>()
             {
-                public boolean run()
+                //
+                // Callable
+                //
+
+                public final Boolean call()
                 {
                     boolean result =
                             ProcessExecutionHelper.runAndLog(cmd, operationLog, machineLog,
@@ -293,39 +297,9 @@ public final class RecursiveHardLinkMaker implements IPathImmutableCopier
         return tokens;
     }
 
-    interface IProcessTask
-    {
-        boolean run(); // returns true if operation succeeded
-    }
-
-    private static boolean runRepeatableProcess(final IProcessTask task,
+    private static boolean runRepeatableProcess(final Callable<Boolean> task,
             final int maxRetryOnFailure, final long millisToSleepOnFailure)
     {
-        IProcess process = new IProcess()
-            {
-                private boolean succeded;
-
-                public int getMaxRetryOnFailure()
-                {
-                    return maxRetryOnFailure;
-                }
-
-                public long getMillisToSleepOnFailure()
-                {
-                    return millisToSleepOnFailure;
-                }
-
-                public boolean succeeded()
-                {
-                    return succeded;
-                }
-
-                public void run()
-                {
-                    succeded = task.run();
-                }
-            };
-        new ProcessRunner(process);
-        return process.succeeded();
+        return new CallableExecutor(maxRetryOnFailure, millisToSleepOnFailure).executeCallable(task);
     }
 }
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/process/CallableExecutorTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/process/CallableExecutorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d6ba0ed43b6005ade488158677a11e86227fd06f
--- /dev/null
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/process/CallableExecutorTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.process;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+
+import java.util.concurrent.Callable;
+
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Test cases for the {@link CallableExecutor}.
+ * 
+ * @author Christian Ribeaud
+ */
+public final class CallableExecutorTest
+{
+
+    private Mockery context;
+
+    private Callable<Boolean> callable;
+
+    @SuppressWarnings("unchecked")
+    @BeforeMethod
+    public final void beforeMethod()
+    {
+        context = new Mockery();
+        callable = context.mock(Callable.class);
+    }
+
+    @AfterMethod
+    public final void afterMethod()
+    {
+        // To following line of code should also be called at the end of each test method.
+        // Otherwise one do not known which test failed.
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public final void testConstructor()
+    {
+        boolean fail = true;
+        try
+        {
+            new CallableExecutor(-1, 1000L);
+        } catch (final AssertionError e)
+        {
+            fail = false;
+        }
+        assertFalse(fail);
+        fail = true;
+        try
+        {
+            new CallableExecutor(0, -1L);
+        } catch (final AssertionError e)
+        {
+            fail = false;
+        }
+        assertFalse(fail);
+    }
+
+    @DataProvider(name = "maxRetryOnFailure")
+    public final Object[][] getMaxRetryOnFailure()
+    {
+        return new Object[][]
+            {
+                { 0 },
+                { 1 },
+                { 3 } };
+    }
+
+    @Test(dataProvider = "maxRetryOnFailure")
+    public final void testRunUnsuccessfulCallable(final int maxRetryOnFailure) throws Exception
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    exactly(maxRetryOnFailure + 1).of(callable).call();
+                    will(returnValue(null));
+                }
+            });
+        assertEquals(null, new CallableExecutor(maxRetryOnFailure, 10L).executeCallable(callable));
+        context.assertIsSatisfied();
+    }
+
+    @Test(dataProvider = "maxRetryOnFailure")
+    public final void testRunSuccessfulCallable(final int maxRetryOnFailure) throws Exception
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(callable).call();
+                    will(returnValue(false));
+                }
+            });
+        assertEquals(Boolean.FALSE, new CallableExecutor(maxRetryOnFailure, 10L)
+                .executeCallable(callable));
+        context.assertIsSatisfied();
+    }
+}
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/process/ProcessRunnerTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/process/ProcessRunnerTest.java
deleted file mode 100644
index b3449410a1be18a4c96b52b594962e85c8ee1d3d..0000000000000000000000000000000000000000
--- a/common/sourceTest/java/ch/systemsx/cisd/common/process/ProcessRunnerTest.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2007 ETH Zuerich, CISD
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.systemsx.cisd.common.process;
-
-import static org.testng.AssertJUnit.assertTrue;
-
-import org.jmock.Expectations;
-import org.jmock.Mockery;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-/**
- * Test cases for the {@link ProcessRunner}.
- * 
- * @author Christian Ribeaud
- */
-public class ProcessRunnerTest
-{
-
-    private Mockery context;
-
-    private IProcess process;
-
-    @BeforeMethod
-    public final void beforeMethod()
-    {
-        context = new Mockery();
-        process = context.mock(IProcess.class);
-    }
-
-    @AfterMethod
-    public final void afterMethod()
-    {
-        // To following line of code should also be called at the end of each test method.
-        // Otherwise one do not known which test failed.
-        context.assertIsSatisfied();
-    }
-
-    @Test
-    public final void testRunSuccessfulProcess()
-    {
-        context.checking(new Expectations()
-            {
-                {
-                    one(process).run();
-
-                    one(process).succeeded();
-                    will(returnValue(true));
-
-                    one(process).getMillisToSleepOnFailure();
-                    will(returnValue(1L));
-
-                    one(process).getMaxRetryOnFailure();
-                    will(returnValue(2));
-                }
-            });
-        new ProcessRunner(process);
-        context.assertIsSatisfied();
-    }
-
-    @Test
-    public final void testRunUnsuccessfulProcess()
-    {
-        final int tries = 3;
-        context.checking(new Expectations()
-            {
-                {
-                    exactly(tries).of(process).run();
-
-                    exactly(tries - 1).of(process).succeeded();
-                    will(returnValue(false));
-
-                    one(process).getMillisToSleepOnFailure();
-                    will(returnValue(1L));
-
-                    one(process).getMaxRetryOnFailure();
-                    will(returnValue(tries));
-                }
-            });
-        new ProcessRunner(process);
-        context.assertIsSatisfied();
-    }
-
-    @Test
-    public final void testRunWithNegativeValues()
-    {
-        context.checking(new Expectations()
-            {
-                {
-                    one(process).getMillisToSleepOnFailure();
-                    will(returnValue(-1L));
-
-                    one(process).getMaxRetryOnFailure();
-                    will(returnValue(-1));
-                }
-            });
-        boolean exceptionThrown = false;
-        try
-        {
-            new ProcessRunner(process);
-
-        } catch (AssertionError ex)
-        {
-            exceptionThrown = true;
-        }
-        assertTrue("Negative value for millis to sleep on failure not allowed", exceptionThrown);
-        context.checking(new Expectations()
-            {
-                {
-                    one(process).getMillisToSleepOnFailure();
-                    will(returnValue(0L));
-
-                    one(process).getMaxRetryOnFailure();
-                    will(returnValue(-100));
-
-                    one(process).run();
-                }
-            });
-        new ProcessRunner(process);
-        context.assertIsSatisfied();
-    }
-}