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(); - } -}