From dc2db7a1846efdad33ae027b0bf9a0af9235e6fb Mon Sep 17 00:00:00 2001 From: brinn <brinn> Date: Mon, 11 Jun 2007 18:08:52 +0000 Subject: [PATCH] LMS-17 refactor: - move the DirectoryScanningTimerTask to common in order to be able to use it in the etlserver - change the DirectoryScanningTimerTask in such a way that suppressing .is_finished_xxx files has to be done explicitely by using a NamePrefixFileFilter SVN: 453 --- .../ch/systemsx/cisd/common/Constants.java | 30 ++ .../utilities/DirectoryScanningTimerTask.java | 250 ++++++++++++ .../common/utilities/IntraFSPathMover.java | 74 ++++ .../utilities/NamePrefixFileFilter.java | 51 +++ .../DirectoryScanningTimerTaskTest.java | 371 ++++++++++++++++++ .../utilities/NamePrefixFileFilterTest.java | 62 +++ .../StoringUncaughtExceptionHandler.java | 91 +++++ 7 files changed, 929 insertions(+) create mode 100644 common/source/java/ch/systemsx/cisd/common/Constants.java create mode 100644 common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java create mode 100644 common/source/java/ch/systemsx/cisd/common/utilities/IntraFSPathMover.java create mode 100644 common/source/java/ch/systemsx/cisd/common/utilities/NamePrefixFileFilter.java create mode 100644 common/sourceTest/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTaskTest.java create mode 100644 common/sourceTest/java/ch/systemsx/cisd/common/utilities/NamePrefixFileFilterTest.java create mode 100644 common/sourceTest/java/ch/systemsx/cisd/common/utilities/StoringUncaughtExceptionHandler.java diff --git a/common/source/java/ch/systemsx/cisd/common/Constants.java b/common/source/java/ch/systemsx/cisd/common/Constants.java new file mode 100644 index 00000000000..ddfb5d628e1 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/Constants.java @@ -0,0 +1,30 @@ +/* + * 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; + +/** + * Constants common to more than one CISD project. + * + * @author Bernd Rinn + */ +public class Constants +{ + + /** The prefix of marker files that indicate that the processing of some path is finished. */ + public static final String IS_FINISHED_PREFIX = ".is_finished_"; + +} diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java b/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java new file mode 100644 index 00000000000..2a0fb7b4cdc --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java @@ -0,0 +1,250 @@ +/* + * 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.utilities; + +import java.io.File; +import java.io.FileFilter; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; +import java.util.TimerTask; + +import org.apache.log4j.Logger; + +import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; + +/** + * A {@link TimerTask} that scans a source directory for entries that are accepted by some {@link FileFilter} and + * handles the accepted entries by some {@link IPathHandler}. It maintains a list of faulty paths that failed to be + * handled OK in the past. Clearing the list will make the class to retry handling the paths. + * <p> + * The class should be constructed in the start-up phase and as part of the system's self-test in order to reveal + * problems with incorrect paths timely. + * + * @author Bernd Rinn + */ +public final class DirectoryScanningTimerTask extends TimerTask implements ISelfTestable +{ + + public static final String FAULTY_PATH_FILENAME = ".faulty_paths"; + + private static final Logger operationLog = + LogFactory.getLogger(LogCategory.OPERATION, DirectoryScanningTimerTask.class); + + private static final Logger notificationLog = + LogFactory.getLogger(LogCategory.NOTIFY, DirectoryScanningTimerTask.class); + + private static final IFromStringConverter<File> FILE_CONVERTER = new IFromStringConverter<File>() + { + public File fromString(String value) + { + return new File(value); + } + }; + + private final IPathHandler handler; + + private final File sourceDirectory; + + private boolean errorReadingDirectory; + + private final FileFilter filter; + + private final Set<File> faultyPaths; + + private final File faultyPathsFile; + + private long faultyPathsLastChanged; + + /** + * A handler for paths. The paths are supposed to go away when they have been handled successfully. + */ + public interface IPathHandler + { + /** + * Handles the <var>path</var>. + * + * @return <code>true</code> if the <var>path</var> has been handled correctly and <code>false</code> + * otherwise. + */ + public boolean handle(File path); + } + + /** + * Creates a <var>DirectoryScanningTimerTask</var>. + * + * @param sourceDirectory The directory to scan for entries. + * @param filter The file filter that picks the entries to handle. + * @param handler The handler that is used for treating the matching paths. + */ + public DirectoryScanningTimerTask(File sourceDirectory, FileFilter filter, IPathHandler handler) + { + assert sourceDirectory != null; + assert filter != null; + assert handler != null; + + this.sourceDirectory = sourceDirectory; + this.filter = filter; + this.handler = handler; + this.faultyPaths = new HashSet<File>(); + this.faultyPathsFile = new File(sourceDirectory, FAULTY_PATH_FILENAME); + faultyPathsFile.delete(); + } + + /** + * Handles all entries in {@link #sourceDirectory} that are picked by the {@link #filter} by some {@link #handler}. + */ + @Override + public void run() + { + if (operationLog.isTraceEnabled()) + { + operationLog.trace("Start scanning directory."); + } + checkForFaultyPathsFileChanged(); + final File[] paths = listFiles(); + if (paths == null) // Means: error reading directory listing + { + return; + } + // Sort in order of "oldest first" in order to move older items before newer items. This becomes important when + // doing online quality control of measurements. + Arrays.sort(paths, new Comparator<File>() + { + public int compare(File o1, File o2) + { + return (int) (o1.lastModified() - o2.lastModified()); + } + }); + for (File path : paths) + { + if (faultyPathsFile.equals(path)) // Never touch the faultyPathsFile. + { + continue; + } + handle(path); + } + if (operationLog.isTraceEnabled()) + { + operationLog.trace("Finished scanning directory."); + } + } + + private void checkForFaultyPathsFileChanged() + { + if (faultyPathsFile.exists()) + { + if (faultyPathsFile.lastModified() > faultyPathsLastChanged) // Handles manual manipulation. + { + faultyPaths.clear(); + CollectionIO.readCollection(faultyPathsFile, faultyPaths, FILE_CONVERTER); + faultyPathsLastChanged = faultyPathsFile.lastModified(); + if (operationLog.isInfoEnabled()) + { + operationLog.info(String.format("Reread faulty paths file (%s), new set contains %d entries", + faultyPathsFile.getPath(), faultyPaths.size())); + } + } + } else + // Handles manual removal. + { + faultyPaths.clear(); + } + } + + private File[] listFiles() + { + File[] paths = null; + RuntimeException ex = null; + try + { + paths = sourceDirectory.listFiles(filter); + } catch (RuntimeException e) + { + ex = e; + } + if (paths == null) + { + if (errorReadingDirectory == false) + { + logFailureInDirectoryListing(ex); + errorReadingDirectory = true; // Avoid mailbox flooding. + } + } else + { + errorReadingDirectory = false; + } + return paths; + } + + private void logFailureInDirectoryListing(RuntimeException ex) + { + if (ex == null) + { + if (sourceDirectory.isFile()) + { + notificationLog.error(String + .format("Failed to get listing of directory '%s' (path is file instead of directory).", + sourceDirectory)); + } else + { + notificationLog.error(String.format("Failed to get listing of directory '%s' (path not found).", + sourceDirectory)); + } + } else + { + notificationLog.error(String.format("Failed to get listing of directory '%s'.", sourceDirectory), ex); + } + } + + private void handle(File path) + { + if (faultyPaths.contains(path)) + { // Guard: skip faulty paths. + return; + } + + final boolean handledOK = handler.handle(path); + if (path.exists()) + { + if (handledOK) + { + operationLog.warn(String.format("Handler %s reports path '%s' be handled OK, but path still exists.", + handler.getClass().getSimpleName(), path)); + } + faultyPaths.add(path); + CollectionIO.writeIterable(faultyPathsFile, faultyPaths); + faultyPathsLastChanged = faultyPathsFile.lastModified(); + } + } + + public void check() throws ConfigurationFailureException + { + if (operationLog.isDebugEnabled()) + { + operationLog.debug("Checking source directory '" + sourceDirectory.getAbsolutePath() + "'."); + } + final String errorMessage = FileUtilities.checkDirectoryFullyAccessible(sourceDirectory, "source"); + if (errorMessage != null) + { + throw new ConfigurationFailureException(errorMessage); + } + } +} diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/IntraFSPathMover.java b/common/source/java/ch/systemsx/cisd/common/utilities/IntraFSPathMover.java new file mode 100644 index 00000000000..1df5264dd2c --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/utilities/IntraFSPathMover.java @@ -0,0 +1,74 @@ +/* + * 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.utilities; + +import java.io.File; + +import org.apache.log4j.Logger; + +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; + +/** + * A {@link DirectoryScanningTimerTask.IPathHandler} that moves paths out of the way within one file system by calling + * {@link File#renameTo(File)}.. + * + * @author Bernd Rinn + */ +public class IntraFSPathMover implements DirectoryScanningTimerTask.IPathHandler +{ + + private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, IntraFSPathMover.class); + + private static final Logger notificationLog = LogFactory.getLogger(LogCategory.NOTIFY, IntraFSPathMover.class); + + private final File destinationDirectory; + + /** + * Creates a <var>PathMover</var>. + * + * @param destinationDirectory The directory to move paths to. + */ + public IntraFSPathMover(File destinationDirectory) + { + assert destinationDirectory != null; + assert FileUtilities.checkDirectoryFullyAccessible(destinationDirectory, "destination") == null : FileUtilities + .checkDirectoryFullyAccessible(destinationDirectory, "destination"); + + this.destinationDirectory = destinationDirectory; + } + + public boolean handle(File path) + { + assert path != null; + assert destinationDirectory != null; + + if (operationLog.isInfoEnabled()) + { + operationLog + .info(String.format("Moving path '%s' to '%s'", path.getPath(), destinationDirectory.getPath())); + } + boolean movedOK = path.renameTo(new File(destinationDirectory, path.getName())); + if (movedOK == false) + { + notificationLog.error(String.format("Moving path '%s' to directory '%s' failed.", path, + destinationDirectory)); + } + return movedOK; + } + +} diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/NamePrefixFileFilter.java b/common/source/java/ch/systemsx/cisd/common/utilities/NamePrefixFileFilter.java new file mode 100644 index 00000000000..f7ee6291719 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/utilities/NamePrefixFileFilter.java @@ -0,0 +1,51 @@ +/* + * 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.utilities; + +import java.io.File; +import java.io.FileFilter; + +/** + * A {@link FileFilter} that checks whether the name part of the <var>pathname</var> starts (or does not start) with + * the <var>prefix</var> specified. + * + * @author Bernd Rinn + */ +public class NamePrefixFileFilter implements FileFilter +{ + + private final String prefix; + + private boolean shouldStartWithPrefix; + + public NamePrefixFileFilter(String prefix, boolean shouldStartWithPrefix) + { + assert prefix != null; + + this.prefix = prefix; + this.shouldStartWithPrefix = shouldStartWithPrefix; + } + + public boolean accept(File pathname) + { + assert pathname != null; + + final boolean startsWithPrefix = pathname.getName().startsWith(prefix); + return startsWithPrefix == shouldStartWithPrefix; + } + +} diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTaskTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTaskTest.java new file mode 100644 index 00000000000..f48013d7a2a --- /dev/null +++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTaskTest.java @@ -0,0 +1,371 @@ +/* + * 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.utilities; + +import static org.testng.AssertJUnit.assertEquals; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogInitializer; +import ch.systemsx.cisd.common.logging.LogMonitoringAppender; +import ch.systemsx.cisd.common.utilities.CollectionIO; +import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask; +import ch.systemsx.cisd.common.utilities.FileUtilities; +import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask.IPathHandler; +import ch.systemsx.cisd.common.utilities.StoringUncaughtExceptionHandler; + +/** + * Test cases for the {@link DirectoryScanningTimerTask}. + * + * @author Bernd Rinn + */ +public class DirectoryScanningTimerTaskTest +{ + + private static final File unitTestRootDirectory = new File("targets" + File.separator + "unit-test-wd"); + + private static final File workingDirectory = new File(unitTestRootDirectory, "DirectoryScanningTimerTaskTest"); + + private final StoringUncaughtExceptionHandler exceptionHandler = new StoringUncaughtExceptionHandler(); + + private final static FileFilter ALWAYS_FALSE_FILE_FILTER = new FileFilter() + { + public boolean accept(File pathname) + { + return false; + } + }; + + private final static FileFilter ALWAYS_TRUE_FILE_FILTER = new FileFilter() + { + public boolean accept(File pathname) + { + return true; + } + }; + + private final static String EXCEPTION_THROWING_FILE_FILTER_MESSAGE = "Exception throwing file filter does its job."; + + private final static FileFilter EXCEPTION_THROWING_FILE_FILTER = new FileFilter() + { + public boolean accept(File pathname) + { + throw new RuntimeException(EXCEPTION_THROWING_FILE_FILTER_MESSAGE); + } + }; + + private final MockPathHandler mockPathHandler = new MockPathHandler(); + + /** + * A mock implementation that stores the handled paths. + */ + public static class MockPathHandler implements IPathHandler + { + + final List<File> handledPaths = new ArrayList<File>(); + + public void clear() + { + handledPaths.clear(); + } + + public boolean handle(File path) + { + handledPaths.add(path); + path.delete(); + return true; + } + + } + + @BeforeClass + public void init() + { + LogInitializer.init(); + unitTestRootDirectory.mkdirs(); + assert unitTestRootDirectory.isDirectory(); + Thread.setDefaultUncaughtExceptionHandler(exceptionHandler); + } + + @BeforeMethod + public void setUp() + { + FileUtilities.deleteRecursively(workingDirectory); + workingDirectory.mkdirs(); + workingDirectory.deleteOnExit(); + mockPathHandler.clear(); + } + + @AfterMethod + public void checkException() + { + exceptionHandler.checkAndRethrowException(); + } + + @Test(expectedExceptions = + { ConfigurationFailureException.class }) + public void testFailedConstructionNonExistent() + { + final File nonExistentFile = new File(unitTestRootDirectory, "non-existent"); + nonExistentFile.delete(); + final DirectoryScanningTimerTask task = + new DirectoryScanningTimerTask(nonExistentFile, ALWAYS_TRUE_FILE_FILTER, mockPathHandler); + task.check(); + } + + @Test(expectedExceptions = + { ConfigurationFailureException.class }) + public void testFailedConstructionFileInsteadOfDirectory() throws IOException + { + final File file = new File(unitTestRootDirectory, "existent_file"); + file.delete(); + file.deleteOnExit(); + file.createNewFile(); + final DirectoryScanningTimerTask task = new DirectoryScanningTimerTask(file, ALWAYS_TRUE_FILE_FILTER, mockPathHandler); + task.check(); + } + + @Test(groups = + { "requires_unix" }, expectedExceptions = + { ConfigurationFailureException.class }) + public void testFailedConstructionReadOnly() throws IOException, InterruptedException + { + final File readOnlyDirectory = new File(unitTestRootDirectory, "read_only_directory"); + readOnlyDirectory.delete(); + readOnlyDirectory.mkdir(); + readOnlyDirectory.deleteOnExit(); + assert readOnlyDirectory.setReadOnly(); + + try + { + // Here we should get an AssertationError + final DirectoryScanningTimerTask task = new DirectoryScanningTimerTask(readOnlyDirectory, ALWAYS_TRUE_FILE_FILTER, mockPathHandler); + task.check(); + } finally + { + // Unfortunately, with JDK 5 there is no portable way to set a file or directory read/write, once + // it has been set read-only, thus this test 'requires_unix' for the time being. + Runtime.getRuntime().exec(String.format("/bin/chmod u+w %s", readOnlyDirectory.getPath())).waitFor(); + if (readOnlyDirectory.canWrite() == false) + { + // Can't use assert here since we expect an AssertationError + throw new IllegalStateException(); + } + } + } + + @Test + public void testFaultyPathsDeletion() + { + final File faultyPaths = new File(workingDirectory, DirectoryScanningTimerTask.FAULTY_PATH_FILENAME); + CollectionIO.writeIterable(faultyPaths, Collections.singleton("some_path")); + new DirectoryScanningTimerTask(workingDirectory, ALWAYS_TRUE_FILE_FILTER, mockPathHandler); + assert faultyPaths.length() == 0; + } + + @Test + public void testProcessOK() throws IOException + { + final File someFile = new File(workingDirectory, "some_file"); + someFile.createNewFile(); + someFile.deleteOnExit(); + final DirectoryScanningTimerTask scanner = + new DirectoryScanningTimerTask(workingDirectory, ALWAYS_TRUE_FILE_FILTER, mockPathHandler); + assertEquals(0, mockPathHandler.handledPaths.size()); + scanner.run(); + assertEquals(1, mockPathHandler.handledPaths.size()); + assertEquals(someFile, mockPathHandler.handledPaths.get(0)); + } + + @Test + public void testFileFilterUsed() throws IOException + { + final File someFile = new File(workingDirectory, "some_file"); + someFile.createNewFile(); + someFile.deleteOnExit(); + final DirectoryScanningTimerTask scanner = + new DirectoryScanningTimerTask(workingDirectory, ALWAYS_FALSE_FILE_FILTER, mockPathHandler); + assertEquals(0, mockPathHandler.handledPaths.size()); + scanner.run(); + assertEquals(0, mockPathHandler.handledPaths.size()); + } + + @Test + public void testManipulateFaultyPaths() throws IOException + { + final File faultyPaths = new File(workingDirectory, DirectoryScanningTimerTask.FAULTY_PATH_FILENAME); + final File someFile = new File(workingDirectory, "some_file"); + someFile.createNewFile(); + someFile.deleteOnExit(); + assert someFile.exists(); + final DirectoryScanningTimerTask scanner = + new DirectoryScanningTimerTask(workingDirectory, ALWAYS_TRUE_FILE_FILTER, mockPathHandler); + CollectionIO.writeIterable(faultyPaths, Collections.singleton(someFile)); + scanner.run(); + assertEquals(0, mockPathHandler.handledPaths.size()); + } + + @Test + public void testFaultyPaths() throws IOException + { + final File faultyPaths = new File(workingDirectory, DirectoryScanningTimerTask.FAULTY_PATH_FILENAME); + final File someFile = new File(workingDirectory, "some_file"); + final MockPathHandler myPathHandler = new MockPathHandler() + { + boolean firstTime = true; + + @Override + public boolean handle(File path) + { + if (firstTime) + { + firstTime = false; + return false; + } + return super.handle(path); + } + }; + someFile.createNewFile(); + assert someFile.exists(); + someFile.deleteOnExit(); + final DirectoryScanningTimerTask scanner = + new DirectoryScanningTimerTask(workingDirectory, ALWAYS_TRUE_FILE_FILTER, myPathHandler); + + // See whether faulty_paths settings works. + scanner.run(); + assertEquals(0, mockPathHandler.handledPaths.size()); + List<String> faulty = CollectionIO.readList(faultyPaths); + assertEquals(1, faulty.size()); + assertEquals(someFile.getPath(), faulty.get(0)); + // See whether fault_paths resetting works. + assert faultyPaths.delete(); + myPathHandler.clear(); // Isn't necessary, just for expressing intention. + // See whether faulty_paths settings works. + scanner.run(); + assertEquals(1, myPathHandler.handledPaths.size()); + assertEquals(someFile, myPathHandler.handledPaths.get(0)); + } + + @Test + public void testPathOrder() throws IOException + { + final File dir = new File(workingDirectory, "testPathOrder"); + final File f1 = new File(dir, "1"); + final File f2 = new File(dir, "2"); + final File f3 = new File(dir, "3"); + final File f4 = new File(dir, "4"); + final long now = System.currentTimeMillis(); + dir.mkdir(); + dir.deleteOnExit(); + f1.createNewFile(); + f1.deleteOnExit(); + f2.createNewFile(); + f2.deleteOnExit(); + f3.createNewFile(); + f3.deleteOnExit(); + f4.createNewFile(); + f4.deleteOnExit(); + // Order should be: 2, 4, 3, 1 + f2.setLastModified(now - 10000); + f4.setLastModified(now - 5000); + f3.setLastModified(now - 1000); + f1.setLastModified(now); + final DirectoryScanningTimerTask scanner = + new DirectoryScanningTimerTask(dir, ALWAYS_TRUE_FILE_FILTER, mockPathHandler); + scanner.run(); + assertEquals(f2, mockPathHandler.handledPaths.get(0)); + assertEquals(f4, mockPathHandler.handledPaths.get(1)); + assertEquals(f3, mockPathHandler.handledPaths.get(2)); + assertEquals(f1, mockPathHandler.handledPaths.get(3)); + assertEquals(4, mockPathHandler.handledPaths.size()); + } + + @Test + public void testMissingDirectory() + { + final File dir = new File(workingDirectory, "testMissingDirectory"); + dir.mkdir(); + LogMonitoringAppender appender = + LogMonitoringAppender.addAppender(LogCategory.NOTIFY, "Failed to get listing of directory"); + // The directory needs to exist when the scanner is created, otherwise the self-test will fail. + final DirectoryScanningTimerTask scanner = + new DirectoryScanningTimerTask(dir, ALWAYS_TRUE_FILE_FILTER, mockPathHandler); + dir.delete(); + assert dir.exists() == false; + scanner.run(); + assert appender.hasLogHappened(); + LogMonitoringAppender.removeAppender(appender); + } + + @Test + public void testDirectoryIsFile() throws IOException + { + final File dir = new File(workingDirectory, "testMissingDirectory"); + dir.mkdir(); + LogMonitoringAppender appender = + LogMonitoringAppender.addAppender(LogCategory.NOTIFY, "Failed to get listing of directory"); + // The directory needs to exist when the scanner is created, otherwise the self-test will fail. + final DirectoryScanningTimerTask scanner = + new DirectoryScanningTimerTask(dir, ALWAYS_TRUE_FILE_FILTER, mockPathHandler); + dir.delete(); + dir.createNewFile(); + dir.deleteOnExit(); + assert dir.isFile(); + scanner.run(); + assert appender.hasLogHappened(); + dir.delete(); + LogMonitoringAppender.removeAppender(appender); + } + + @Test + public void testFailingFileFilter() throws IOException + { + final File dir = new File(workingDirectory, "testMissingDirectory"); + dir.mkdir(); + dir.deleteOnExit(); + final File file = new File(dir, "some.file"); + file.createNewFile(); + file.deleteOnExit(); + LogMonitoringAppender appender1 = + LogMonitoringAppender.addAppender(LogCategory.NOTIFY, "Failed to get listing of directory"); + LogMonitoringAppender appender2 = + LogMonitoringAppender.addAppender(LogCategory.NOTIFY, EXCEPTION_THROWING_FILE_FILTER_MESSAGE); + // The directory needs to exist when the scanner is created, otherwise the self-test will fail. + final DirectoryScanningTimerTask scanner = + new DirectoryScanningTimerTask(dir, EXCEPTION_THROWING_FILE_FILTER, mockPathHandler); + scanner.run(); + assert appender1.hasLogHappened(); + assert appender2.hasLogHappened(); + file.delete(); + dir.delete(); + LogMonitoringAppender.removeAppender(appender1); + LogMonitoringAppender.removeAppender(appender2); + } + +} diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/NamePrefixFileFilterTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/NamePrefixFileFilterTest.java new file mode 100644 index 00000000000..453d5e3a2b6 --- /dev/null +++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/NamePrefixFileFilterTest.java @@ -0,0 +1,62 @@ +/* + * 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.utilities; + +import java.io.File; +import java.io.FileFilter; + +import org.testng.annotations.Test; + +/** + * Test cases for {@link NamePrefixFileFilter}. + * + * @author Bernd Rinn + */ +public class NamePrefixFileFilterTest +{ + + private static final String PREFIX = "PReFiX"; + + @Test + public void testNamePrefixShouldOccurAndFound() + { + final FileFilter filter = new NamePrefixFileFilter(PREFIX, true); + assert filter.accept(new File(PREFIX + "andSomePostFix.txt").getAbsoluteFile()); + } + + @Test + public void testNamePrefixShouldOccurButNotFound() + { + final FileFilter filter = new NamePrefixFileFilter(PREFIX, true); + assert filter.accept(new File("somefilewithouttheprefix.dat").getAbsoluteFile()) == false; + } + + @Test + public void testNamePrefixShouldNotOccurAndNotFound() + { + final FileFilter filter = new NamePrefixFileFilter(PREFIX, false); + assert filter.accept(new File("somefilewithouttheprefix.dat").getAbsoluteFile()); + } + + @Test + public void testNamePrefixShouldNotOccurButFound() + { + final FileFilter filter = new NamePrefixFileFilter(PREFIX, false); + assert filter.accept(new File(PREFIX + "andSomePostFix.txt").getAbsoluteFile()) == false; + } + +} diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/StoringUncaughtExceptionHandler.java b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/StoringUncaughtExceptionHandler.java new file mode 100644 index 00000000000..a274fe50fbc --- /dev/null +++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/StoringUncaughtExceptionHandler.java @@ -0,0 +1,91 @@ +/* + * 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.utilities; + +import java.lang.Thread.UncaughtExceptionHandler; + +/** + * An exception handler that stores the first occurring exception for later investigation. Needs to be activated by + * + * <pre> + * StoringUncaughtExceptionHandler exceptionHandler = new StoringUncaughtExceptionHandler(); + * Thread.setDefaultUncaughtExceptionHandler(exceptionHandler); + * </pre> + * + * @author Bernd Rinn + */ +public class StoringUncaughtExceptionHandler implements UncaughtExceptionHandler +{ + + private Throwable throwable; + + private String threadName; + + public void uncaughtException(Thread t, Throwable e) + { + if (throwable == null) // Only store the first throwable + { + throwable = e; + threadName = t.getName(); + } + } + + /** Resets the handler. Any stored exception will be lost. */ + public void reset() + { + throwable = null; + threadName = null; + } + + /** + * @return <code>true</code> if an exception or error has occurred, <code>false</code> otherwise. + */ + public boolean hasExceptionOccurred() + { + return (throwable != null); + } + + /** + * @return The throwable, if any has been occurred, or <code>null</code> otherwise. + */ + public Throwable getThrowable() + { + return throwable; + } + + /** + * @return The name of the thread where the exception or error has occurred, or <code>null</code>, if no + * exception or error has occurred. + */ + public String getThreadName() + { + return threadName; + } + + /** + * Checks whether an exception or error has occurred and, if yes, throws a new {@link RuntimeException} with the + * caught exception as cause in the current thread. + */ + public void checkAndRethrowException() + { + if (hasExceptionOccurred()) + { + throw new RuntimeException(String.format("An exception occurred in thread %s.", getThreadName()), + getThrowable()); + } + } + +} \ No newline at end of file -- GitLab