diff --git a/common/source/java/ch/systemsx/cisd/common/compression/file/CompressionWorker.java b/common/source/java/ch/systemsx/cisd/common/compression/file/CompressionWorker.java new file mode 100644 index 0000000000000000000000000000000000000000..cb83f7c7387067a0cb5ff73785d8f60ba9160902 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/compression/file/CompressionWorker.java @@ -0,0 +1,116 @@ +/* + * 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.compression.file; + +import java.io.File; + +import java.util.Collection; +import java.util.Queue; + +import org.apache.log4j.Logger; + +import ch.systemsx.cisd.common.exceptions.Status; +import ch.systemsx.cisd.common.exceptions.StatusFlag; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; + +/** + * A worker {@link Runnable} for (image) compression. + * + * @author Bernd Rinn + */ +class CompressionWorker implements Runnable +{ + + // @Private + static final int MAX_RETRY_OF_FAILED_COMPRESSIONS = 3; + + // @Private + static final String COMPRESSING_MSG_TEMPLATE = "Compressing '%s'."; + + // @Private + static final String EXCEPTION_COMPRESSING_MSG_TEMPLATE = "Exceptional condition when trying to compress '%s'."; + + // @Private + static final String INTERRPTED_MSG = "Thread has been interrupted - exiting worker."; + + // @Private + static final String EXITING_MSG = "No more files to compress - exiting worker."; + + private final static Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, CompressionWorker.class); + + private final Queue<File> workerQueue; + + private final Collection<FailureRecord> failures; + + private final ICompressionMethod compressor; + + CompressionWorker(Queue<File> incommingQueue, Collection<FailureRecord> failures, ICompressionMethod compressor) + { + assert incommingQueue != null; + assert failures != null; + assert compressor != null; + + this.workerQueue = incommingQueue; + this.failures = failures; + this.compressor = compressor; + } + + public void run() + { + do + { + if (Thread.currentThread().isInterrupted()) + { + if (operationLog.isInfoEnabled()) + { + operationLog.info(INTERRPTED_MSG); + } + return; + } + final File fileToCompressOrNull = workerQueue.poll(); + if (fileToCompressOrNull == null) + { + operationLog.info(EXITING_MSG); + return; + } + if (operationLog.isDebugEnabled()) + { + operationLog.debug(String.format(COMPRESSING_MSG_TEMPLATE, fileToCompressOrNull)); + } + Status status = null; + int count = 0; + do { + try + { + status = compressor.compress(fileToCompressOrNull); + } catch (Throwable th) + { + operationLog.error(String.format(EXCEPTION_COMPRESSING_MSG_TEMPLATE, fileToCompressOrNull), th); + failures.add(new FailureRecord(fileToCompressOrNull, th)); + status = null; + break; + } + } while (StatusFlag.RETRIABLE_ERROR.equals(status.getFlag()) && ++count < MAX_RETRY_OF_FAILED_COMPRESSIONS); + if (status != null && Status.OK.equals(status) == false) + { + failures.add(new FailureRecord(fileToCompressOrNull, status)); + } + } while (true); + } + +} diff --git a/common/source/java/ch/systemsx/cisd/common/compression/file/Compressor.java b/common/source/java/ch/systemsx/cisd/common/compression/file/Compressor.java new file mode 100644 index 0000000000000000000000000000000000000000..dcdf93abd38148e17210f2e75485e1e12d71fd0a --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/compression/file/Compressor.java @@ -0,0 +1,103 @@ +/* + * 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.compression.file; + +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; + +import org.apache.log4j.Logger; + +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.logging.LogInitializer; +import ch.systemsx.cisd.common.utilities.FileUtilities; +import ch.systemsx.cisd.common.utilities.ISelfTestable; + +/** + * The base class for file compression. + * + * @author Bernd Rinn + */ +public class Compressor +{ + + static + { + LogInitializer.init(); + } + + private static final Logger machineLog = LogFactory.getLogger(LogCategory.MACHINE, Compressor.class); + + private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, Compressor.class); + + private static final int NUMBER_OF_WORKERS = Runtime.getRuntime().availableProcessors(); + + private static Queue<File> fillWorkerQueueOrExit(File directory, final FileFilter filter) + { + final File[] filesToCompressOrNull = + FileUtilities.tryListFiles(directory, filter, new Log4jSimpleLogger(machineLog)); + if (filesToCompressOrNull == null) + { + System.err.printf("Path '%s' is not a directory.\n", directory.getPath()); + System.exit(1); + } + if (filesToCompressOrNull.length == 0) + { + System.out.println("No files to compress."); + System.exit(0); + } + if (operationLog.isInfoEnabled()) + { + operationLog.info(String.format("Found %d files to compress.", filesToCompressOrNull.length)); + } + return new ArrayBlockingQueue<File>(filesToCompressOrNull.length, false, Arrays.asList(filesToCompressOrNull)); + } + + private static void startUpWorkerThreads(Queue<File> workerQueue, + Collection<FailureRecord> failed, ICompressionMethod compressor) + { + for (int i = 0; i < NUMBER_OF_WORKERS; ++i) + { + new Thread(new CompressionWorker(workerQueue, failed, compressor), "Compressor " + i).start(); + } + if (operationLog.isInfoEnabled()) + { + operationLog.info(String.format("Started up %d worker threads.", NUMBER_OF_WORKERS)); + } + } + + public static Collection<FailureRecord> start(String directoryName, + ICompressionMethod compressionMethod) + { + if (compressionMethod instanceof ISelfTestable) + { + ((ISelfTestable) compressionMethod).check(); + } + final Queue<File> workerQueue = fillWorkerQueueOrExit(new File(directoryName), compressionMethod); + final Collection<FailureRecord> failed = + Collections.synchronizedCollection(new ArrayList<FailureRecord>()); + startUpWorkerThreads(workerQueue, failed, compressionMethod); + return failed; + } +} diff --git a/common/source/java/ch/systemsx/cisd/common/compression/file/FailureRecord.java b/common/source/java/ch/systemsx/cisd/common/compression/file/FailureRecord.java new file mode 100644 index 0000000000000000000000000000000000000000..36d5108825e6e5f15bd75d499e892e080bd231de --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/compression/file/FailureRecord.java @@ -0,0 +1,77 @@ +/* + * 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.compression.file; + +import java.io.File; + +import ch.systemsx.cisd.common.exceptions.Status; +import ch.systemsx.cisd.common.exceptions.StatusFlag; + +/** + * A class that holds the information about a compression failure. + * + * @author Bernd Rinn + */ +public class FailureRecord +{ + private final File failedFile; + + private final Status failureStatus; + + private final Throwable throwableOrNull; + + FailureRecord(File failedFile, Status failureStatus) + { + this.failedFile = failedFile; + this.failureStatus = failureStatus; + this.throwableOrNull = null; + } + + FailureRecord(File failedFile, Throwable throwableOrNull) + { + this.failedFile = failedFile; + this.failureStatus = + new Status(StatusFlag.FATAL_ERROR, "Exceptional condition: " + + throwableOrNull.getClass().getSimpleName()); + this.throwableOrNull = throwableOrNull; + } + + /** + * Returns the file that caused the failure. + */ + public final File getFailedFile() + { + return failedFile; + } + + /** + * Returns the {@link Status} of the failure. Can have a {@link StatusFlag} of {@link StatusFlag#RETRIABLE_ERROR} if + * retrying the operation did not help. + */ + public final Status getFailureStatus() + { + return failureStatus; + } + + /** + * Returns the {@link Throwable}, if any has occurred in the compression method. + */ + public final Throwable tryGetThrowable() + { + return throwableOrNull; + } +} \ No newline at end of file diff --git a/common/source/java/ch/systemsx/cisd/common/compression/file/ICompressionMethod.java b/common/source/java/ch/systemsx/cisd/common/compression/file/ICompressionMethod.java new file mode 100644 index 0000000000000000000000000000000000000000..fb4b1a1212552a76dd1328204a67dc3a1eeb1ec0 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/compression/file/ICompressionMethod.java @@ -0,0 +1,40 @@ +/* + * 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.compression.file; + +import java.io.File; +import java.io.FileFilter; + +import ch.systemsx.cisd.common.exceptions.Status; + +/** + * A role that compresses a file. A compression method may only be suitable for some files, thus it is also a + * {@link FileFilter}. + * + * @author Bernd Rinn + */ +public interface ICompressionMethod extends FileFilter +{ + + /** + * Compress the <var>fileToCompress</var> + * + * @return {@link Status#OK} if the operation was successful, a status indicating the kind of problem otherwise. + */ + public Status compress(File fileToCompress); + +} diff --git a/common/source/java/ch/systemsx/cisd/common/compression/file/InPlaceCompressionMethod.java b/common/source/java/ch/systemsx/cisd/common/compression/file/InPlaceCompressionMethod.java new file mode 100644 index 0000000000000000000000000000000000000000..65cdedf9fefbea0115f8d8ff1387e73f1f8c4e87 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/compression/file/InPlaceCompressionMethod.java @@ -0,0 +1,204 @@ +/* + * 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.compression.file; + +import java.io.File; +import java.util.List; + +import org.apache.log4j.Logger; + +import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.exceptions.Status; +import ch.systemsx.cisd.common.exceptions.StatusFlag; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.common.utilities.ISelfTestable; +import ch.systemsx.cisd.common.utilities.ProcessExecutionHelper; + +/** + * An {@link ICompressionMethod} that performs in-place compression of a bulk of files by means of calling an external + * compression program and running it per file in an external process. + * + * @author Bernd Rinn + */ +public abstract class InPlaceCompressionMethod implements ICompressionMethod, ISelfTestable +{ + + private static final String INPROGRESS_MARKER = ".COMPRESSION_IN_PROGRESS_"; + + private static final String COMPRESSED_MARKER = ".COMPRESSED_"; + + protected static final Logger machineLog = + LogFactory.getLogger(LogCategory.MACHINE, InPlaceCompressionMethod.class); + + protected static final Logger operationLog = + LogFactory.getLogger(LogCategory.OPERATION, InPlaceCompressionMethod.class); + + private File prefixInProgress(File file) + { + assert file != null; + + return new File(file.getParent(), INPROGRESS_MARKER + file.getName()); + } + + private File prefixCompressed(File file) + { + assert file != null; + + return new File(file.getParent(), COMPRESSED_MARKER + file.getName()); + } + + private File tryRemovePrefix(File file) + { + assert file != null; + + final String name = file.getName(); + if (name.startsWith(INPROGRESS_MARKER)) + { + return new File(file.getParent(), name.substring(INPROGRESS_MARKER.length())); + } else if (name.startsWith(COMPRESSED_MARKER)) + { + return new File(file.getParent(), name.substring(COMPRESSED_MARKER.length())); + } else + { + return null; + } + } + + private boolean isCompressedFile(File fileToCompress) + { + return fileToCompress.getName().startsWith(COMPRESSED_MARKER); + } + + private boolean isInProgressFile(File fileToCompress) + { + return fileToCompress.getName().startsWith(INPROGRESS_MARKER); + } + + private Status createStatusAndLog(String msgTemplate, Object... params) + { + final String msg = String.format(msgTemplate, params); + operationLog.error(msg); + return new Status(StatusFlag.FATAL_ERROR, msg); + } + + /** + * Creates the command line of the external program to call in order to perform the compression. + */ + protected abstract List<String> createCommandLine(File fileToCompress, File inProgressFile); + + /** + * Returns the file extensions of files that this compression method can compress. + * <p> + * All extensions need to be returned in lower case. + */ + protected abstract List<String> getAcceptedExtensions(); + + /** + * Perform any check necessary to see whether the external program that has been found is suitable for the + * compression task (e.g. program version). + */ + public abstract void check() throws EnvironmentFailureException, ConfigurationFailureException; + + public boolean accept(File pathname) + { + if (pathname.isFile() == false) + { + return false; + } + final String name = pathname.getName().toLowerCase(); + for (String extension : getAcceptedExtensions()) + { + if (name.endsWith(extension)) + { + return true; + } + } + return false; + } + + public Status compress(File fileToCompress) + { + assert fileToCompress != null; + + // Clean up + if (isInProgressFile(fileToCompress)) + { + final boolean ok = fileToCompress.delete(); + if (ok) + { + operationLog.warn(String.format("Clean up: deleting left-over file '%s'", fileToCompress + .getAbsolutePath())); + return Status.OK; + } else + { + return createStatusAndLog("Clean up: Unable to delete left-over file '%s'", fileToCompress + .getAbsolutePath()); + } + } + if (isCompressedFile(fileToCompress)) + { + final File originalFile = tryRemovePrefix(fileToCompress); + assert originalFile != null; + if (originalFile.exists()) + { + final boolean ok = originalFile.delete(); + if (ok == false) + { + return createStatusAndLog("Clean up: Unable to delete uncompressed file '%s'", originalFile); + } + } + if (fileToCompress.renameTo(originalFile)) + { + return Status.OK; + } else + { + return createStatusAndLog("Renaming compressed file '%s' to original name '%s' failed.", + fileToCompress, originalFile); + } + } + final File inProgressFile = prefixInProgress(fileToCompress); + final File compressionFinishedFile = prefixCompressed(fileToCompress); + final boolean runOK = + ProcessExecutionHelper.runAndLog(createCommandLine(fileToCompress, inProgressFile), operationLog, + machineLog); + if (runOK == false) + { + return createStatusAndLog("Unable to compress '%s'.", fileToCompress.getAbsolutePath()); + } + final boolean firstRenameOK = inProgressFile.renameTo(compressionFinishedFile); + if (firstRenameOK == false) + { + return createStatusAndLog("Unable to rename '%s' to '%s'.", inProgressFile.getAbsolutePath(), + compressionFinishedFile.getAbsolutePath()); + } + final boolean removalOfOriginalOK = fileToCompress.delete(); + if (removalOfOriginalOK == false) + { + return createStatusAndLog("Unable to delete original file '%s'", fileToCompress.getAbsolutePath()); + } + final boolean secondRenameOK = compressionFinishedFile.renameTo(fileToCompress); + if (secondRenameOK == false) + { + return createStatusAndLog("Unable to rename '%s' to '%s'.", compressionFinishedFile.getAbsolutePath(), + fileToCompress.getAbsolutePath()); + } + return Status.OK; + } + +} \ No newline at end of file diff --git a/common/source/java/ch/systemsx/cisd/common/compression/tiff/TiffCompressor.java b/common/source/java/ch/systemsx/cisd/common/compression/tiff/TiffCompressor.java new file mode 100644 index 0000000000000000000000000000000000000000..b6cb1f7f24e5c912ed5ec4747b2370e65d797e7f --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/compression/tiff/TiffCompressor.java @@ -0,0 +1,50 @@ +/* + * 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.compression.tiff; + +import java.util.Collection; + +import ch.systemsx.cisd.common.compression.file.Compressor; +import ch.systemsx.cisd.common.compression.file.FailureRecord; + +/** + * The main class for tiff file compression. + * + * @author Bernd Rinn + */ +public class TiffCompressor extends Compressor +{ + + public static void main(String[] args) + { + if (args.length != 1) + { + System.err.println("Syntax: TiffCompressor <directory>"); + System.exit(1); + } + final Collection<FailureRecord> failed = start(args[0], new TiffZipCompressionMethod()); + if (failed.size() > 0) + { + System.err.println("The following files could not bee successfully compressed:"); + } + for (FailureRecord r : failed) + { + System.err.printf("%s (%s)\n", r.getFailedFile().getName(), r.getFailureStatus().getMessage()); + } + } + +} diff --git a/common/source/java/ch/systemsx/cisd/common/compression/tiff/TiffZipCompressionMethod.java b/common/source/java/ch/systemsx/cisd/common/compression/tiff/TiffZipCompressionMethod.java new file mode 100644 index 0000000000000000000000000000000000000000..cef175f430065609ddb9e9844115c3f1ab7b3130 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/compression/tiff/TiffZipCompressionMethod.java @@ -0,0 +1,129 @@ +/* + * 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.compression.tiff; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + + +import ch.systemsx.cisd.common.compression.file.InPlaceCompressionMethod; +import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.logging.LogInitializer; +import ch.systemsx.cisd.common.utilities.ProcessExecutionHelper; +import ch.systemsx.cisd.common.utilities.OSUtilities; +import ch.systemsx.cisd.common.utilities.ProcessResult; + +/** + * A compression method for TIFF files using the ImageMagick <code>convert</code> utility with compression + * <code>Zip</code>. + * + * @author Bernd Rinn + */ +public class TiffZipCompressionMethod extends InPlaceCompressionMethod +{ + + final static File convertExecutable = OSUtilities.findExecutable("convert"); + + private static String getImageMagickVersion(String convertExecutableToCheck) + { + final long TIME_TO_WAIT_FOR_COMPLETION = 2 * 1000; + final ProcessResult result = + ProcessExecutionHelper.run(Arrays.asList(convertExecutableToCheck, "--version"), + TIME_TO_WAIT_FOR_COMPLETION, operationLog, machineLog); + result.log(); + final String versionString = extractImageMagickVersion(result.getProcessOutput().get(0)); + return versionString; + } + + private static String extractImageMagickVersion(String imageMagickVersionLine) + { + if (imageMagickVersionLine.startsWith("Version: ImageMagick") == false) + { + return null; + } else + { + final String[] versionStringParts = imageMagickVersionLine.split("\\s+"); + if (versionStringParts.length < 3) + { + return null; + } + return versionStringParts[2]; + } + } + + @Override + protected List<String> createCommandLine(File fileToCompress, File inProgressFile) + { + assert convertExecutable != null; + assert fileToCompress != null; + assert fileToCompress.isFile(); + assert inProgressFile != null; + assert inProgressFile.exists() == false; + + final List<String> parameters = + Arrays.asList(convertExecutable.getAbsolutePath(), fileToCompress.getAbsolutePath(), "-compress", + "Zip", inProgressFile.getAbsolutePath()); + return parameters; + } + + @Override + protected List<String> getAcceptedExtensions() + { + return Arrays.asList(".tif", ".tiff"); + } + + @Override + public void check() throws EnvironmentFailureException, ConfigurationFailureException + { + if (convertExecutable == null) + { + throw new ConfigurationFailureException("Cannot find executable of the convert utility."); + } + final String imageMagickVersionOrNull = getImageMagickVersion(convertExecutable.getAbsolutePath()); + if (imageMagickVersionOrNull == null) + { + throw new ConfigurationFailureException("Invalid convert utility"); + } + String[] imageMagickVersionParts = imageMagickVersionOrNull.split("\\."); + if (imageMagickVersionParts.length != 3) + { + throw new ConfigurationFailureException("Invalid convert utility"); + } + final int imageMagickMajorVersion = Integer.parseInt(imageMagickVersionParts[0]); + final int imageMagickMinorVersion = Integer.parseInt(imageMagickVersionParts[1]); + if (imageMagickMajorVersion < 6 || imageMagickMinorVersion < 2) + { + throw ConfigurationFailureException.fromTemplate( + "Convert utility is too old (expected: v6.2 or newer, found: v%s)", imageMagickVersionOrNull); + } + if (machineLog.isInfoEnabled()) + { + machineLog.info(String.format("Using convert executable '%s', ImageMagick version %s", convertExecutable, + imageMagickVersionOrNull)); + } + } + + public static void main(String[] args) + { + LogInitializer.init(); + final TiffZipCompressionMethod compressor = new TiffZipCompressionMethod(); + compressor.check(); + } + +} diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/compression/file/CompresionWorkerTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/compression/file/CompresionWorkerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..12a7600b941632cc9f40084aa950a992c80eda45 --- /dev/null +++ b/common/sourceTest/java/ch/systemsx/cisd/common/compression/file/CompresionWorkerTest.java @@ -0,0 +1,319 @@ +/* + * 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.compression.file; + +import static org.testng.AssertJUnit.*; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Queue; + +import org.apache.log4j.Level; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.jmock.api.Invocation; +import org.jmock.lib.action.CustomAction; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.common.exceptions.Status; +import ch.systemsx.cisd.common.exceptions.StatusFlag; +import ch.systemsx.cisd.common.logging.BufferedAppender; + +/** + * Test cases for the {@link CompressionWorker}. + * + * @author Bernd Rinn + */ +public class CompresionWorkerTest +{ + + private Mockery context; + + private Queue<File> queue; + + private Collection<FailureRecord> failed; + + private ICompressionMethod compressionMethod; + + private CompressionWorker worker; + + private BufferedAppender logRecorder; + + @SuppressWarnings("unchecked") + private Queue<File> createFileQueue() + { + return context.mock(Queue.class); + } + + @BeforeMethod + public void setUp() + { + context = new Mockery(); + queue = createFileQueue(); + failed = new ArrayList<FailureRecord>(); + compressionMethod = context.mock(ICompressionMethod.class); + logRecorder = new BufferedAppender("%-5p %c - %m%n", Level.DEBUG); + worker = new CompressionWorker(queue, failed, compressionMethod); + } + + @AfterMethod + public void tearDown() + { + logRecorder.reset(); + // 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 void testCompressionWorkerImmediateExit() + { + context.checking(new Expectations() + { + { + one(queue).poll(); + will(returnValue(null)); + } + }); + worker.run(); + assertTrue(logRecorder.getLogContent().indexOf(CompressionWorker.EXITING_MSG) >= 0); + assertEquals(0, failed.size()); + } + + @Test + public void testCompressionWorkerHappyCase() + { + final File[] files = new File[] + { new File("a"), new File("b"), new File("c") }; + context.checking(new Expectations() + { + { + for (File f : files) + { + one(queue).poll(); + will(returnValue(f)); + one(compressionMethod).compress(f); + will(returnValue(Status.OK)); + } + one(queue).poll(); + will(returnValue(null)); + } + }); + worker.run(); + assertTrue(logRecorder.getLogContent().indexOf(CompressionWorker.EXITING_MSG) >= 0); + assertEquals(0, failed.size()); + } + + @Test + public void testCompressionWorkerWithRetriableFailure() + { + final String faultyFile = "b"; + final Status faultyStatus = new Status(StatusFlag.RETRIABLE_ERROR, "some problem"); + final File[] files = new File[] + { new File("a"), new File(faultyFile), new File("c") }; + context.checking(new Expectations() + { + { + for (File f : files) + { + one(queue).poll(); + will(returnValue(f)); + one(compressionMethod).compress(f); + if (faultyFile.equals(f.getName())) + { + will(returnValue(faultyStatus)); + one(compressionMethod).compress(f); + will(returnValue(Status.OK)); + } else + { + will(returnValue(Status.OK)); + } + } + one(queue).poll(); + will(returnValue(null)); + } + }); + worker.run(); + assertEquals(0, failed.size()); + assertTrue(logRecorder.getLogContent().indexOf(CompressionWorker.EXITING_MSG) >= 0); + } + + @Test + public void testCompressionWorkerWithRetriableFailureFinallyFailed() + { + final String faultyFile = "b"; + final Status faultyStatus = new Status(StatusFlag.RETRIABLE_ERROR, "some problem"); + final File[] files = new File[] + { new File("a"), new File(faultyFile), new File("c") }; + context.checking(new Expectations() + { + { + for (File f : files) + { + one(queue).poll(); + will(returnValue(f)); + if (faultyFile.equals(f.getName())) + { + for (int i = 0; i < CompressionWorker.MAX_RETRY_OF_FAILED_COMPRESSIONS; ++i) + { + one(compressionMethod).compress(f); + will(returnValue(faultyStatus)); + } + } else + { + one(compressionMethod).compress(f); + will(returnValue(Status.OK)); + } + } + one(queue).poll(); + will(returnValue(null)); + } + }); + worker.run(); + assertEquals(1, failed.size()); + final FailureRecord record = failed.iterator().next(); + assertEquals(faultyFile, record.getFailedFile().getName()); + assertEquals(StatusFlag.RETRIABLE_ERROR, record.getFailureStatus().getFlag()); + assertEquals(faultyStatus.getMessage(), record.getFailureStatus().getMessage()); + assertTrue(logRecorder.getLogContent().indexOf(CompressionWorker.EXITING_MSG) >= 0); + } + + @Test + public void testCompressionWorkerWithFatalFailure() + { + final String faultyFile = "b"; + final Status fatalStatus = new Status(StatusFlag.FATAL_ERROR, "some problem"); + final File[] files = new File[] + { new File("a"), new File(faultyFile), new File("c") }; + context.checking(new Expectations() + { + { + for (File f : files) + { + one(queue).poll(); + will(returnValue(f)); + one(compressionMethod).compress(f); + if (faultyFile.equals(f.getName())) + { + will(returnValue(fatalStatus)); + } else + { + will(returnValue(Status.OK)); + } + } + one(queue).poll(); + will(returnValue(null)); + } + }); + worker.run(); + assertEquals(1, failed.size()); + final FailureRecord record = failed.iterator().next(); + assertEquals(faultyFile, record.getFailedFile().getName()); + assertEquals(StatusFlag.FATAL_ERROR, record.getFailureStatus().getFlag()); + assertEquals(fatalStatus.getMessage(), record.getFailureStatus().getMessage()); + assertTrue(logRecorder.getLogContent().indexOf(CompressionWorker.EXITING_MSG) >= 0); + } + + @Test + public void testCompressionWorkerInterrupted() + { + final File[] files = new File[] + { new File("a"), new File("b"), new File("c") }; + context.checking(new Expectations() + { + { + for (File f : files) + { + one(queue).poll(); + will(returnValue(f)); + one(compressionMethod).compress(f); + if (f.getName().equals("c")) + { + will(new CustomAction("Interrupt Thread") + { + public Object invoke(Invocation invocation) throws Throwable + { + Thread.currentThread().interrupt(); + return Status.OK; + } + }); + } else + { + will(returnValue(Status.OK)); + } + } + } + }); + worker.run(); + assertTrue(logRecorder.getLogContent().indexOf(CompressionWorker.INTERRPTED_MSG) >= 0); + } + + private final class FakedException extends RuntimeException + { + + private static final long serialVersionUID = 1L; + } + + @Test + public void testCompressionWorkerCompressorThrowsException() + { + final String faultyFile = "b"; + final File[] files = new File[] + { new File("a"), new File(faultyFile), new File("c") }; + final FakedException ex = new FakedException(); + context.checking(new Expectations() + { + { + for (File f : files) + { + one(queue).poll(); + will(returnValue(f)); + one(compressionMethod).compress(f); + if (f.getName().equals(faultyFile)) + { + will(new CustomAction("Throws Exception") + { + public Object invoke(Invocation invocation) throws Throwable + { + throw ex; + } + }); + } else + { + will(returnValue(Status.OK)); + } + } + one(queue).poll(); + will(returnValue(null)); + } + }); + worker.run(); + assertEquals(1, failed.size()); + final FailureRecord record = failed.iterator().next(); + assertEquals(faultyFile, record.getFailedFile().getName()); + assertEquals(StatusFlag.FATAL_ERROR, record.getFailureStatus().getFlag()); + assertEquals("Exceptional condition: " + FakedException.class.getSimpleName(), record.getFailureStatus() + .getMessage()); + assertEquals(ex, record.tryGetThrowable()); + assertTrue(logRecorder.getLogContent().indexOf( + String.format(CompressionWorker.EXCEPTION_COMPRESSING_MSG_TEMPLATE, faultyFile)) >= 0); + } + +}