- Added some kind of resource abstraction layer
- Reimplemented file monitoring using the concurrency package
- Started with the integration of Spring scripting extensions

git-svn-id: https://svn.apache.org/repos/asf/myfaces/extensions/scripting/branches/1_1@916362 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/compiler/Compiler.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/compiler/Compiler.java
index b41b610..33653da 100644
--- a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/compiler/Compiler.java
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/compiler/Compiler.java
@@ -18,12 +18,11 @@
  */
 package org.apache.myfaces.extensions.scripting.compiler;
 
-import org.apache.myfaces.extensions.scripting.compiler.CompilationException;
-
 import java.io.File;
 
 /**
  * <p>An abstract compiler interface that enables you to compile one particular file at a time.</p>
+ * 
  */
 public interface Compiler {
 
@@ -38,7 +37,7 @@
      * @param file        the file of the class you want to compile
      * @param classLoader the class loader for dependent classes
      * @return the compilation result, i.e. the compiler output, a list of errors and a list of warnings
-     * @throws CompilationException if a severe error occured while trying to compile a file
+     * @throws CompilationException if a severe error occurred while trying to compile a file
      */
     public CompilationResult compile(File sourcePath, File targetPath, File file, ClassLoader classLoader)
             throws CompilationException;
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/compiler/GroovyCompiler.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/compiler/GroovyCompiler.java
index a0546ed..d84349d 100644
--- a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/compiler/GroovyCompiler.java
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/compiler/GroovyCompiler.java
@@ -32,11 +32,10 @@
 import java.io.File;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.lang.*;
-import java.util.List;
 
 /**
  * <p>A compiler implementation that can be used to compile Groovy source files.</p>
+ * 
  */
 public class GroovyCompiler implements Compiler {
 
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/compiler/support/CompilationErrorHandler.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/compiler/support/CompilationErrorHandler.java
new file mode 100644
index 0000000..63e5190
--- /dev/null
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/compiler/support/CompilationErrorHandler.java
@@ -0,0 +1,110 @@
+package org.apache.myfaces.extensions.scripting.compiler.support;
+
+import org.apache.myfaces.extensions.scripting.compiler.CompilationException;
+import org.apache.myfaces.extensions.scripting.compiler.CompilationResult;
+import org.apache.myfaces.extensions.scripting.compiler.Compiler;
+
+import java.io.File;
+
+/**
+ *
+ */
+public abstract class CompilationErrorHandler implements Compiler {
+
+    /** The compiler that this error handler is delegating to. */
+    private Compiler compiler;
+
+    // ------------------------------------------ Constructors
+
+    /**
+     * <p>Constructs a new instance of this class using the given compiler as delegate. That
+     * means that this object behaves like a compiler by delegating all compile requests to
+     * the given compiler, but additionally it enables you to handle compilation errors and
+     * exceptions.</p>
+     *
+     * @param compiler the compiler that this error handler is delegating to
+     */
+    public CompilationErrorHandler(Compiler compiler) {
+        if (compiler == null) {
+            throw new IllegalArgumentException(
+                    "The given compiler must not be null.");
+        }
+
+        this.compiler = compiler;
+    }
+
+    // ------------------------------------------ Compiler methods
+
+
+    /**
+     * 
+     *
+     * @param sourcePath  the path to the source directory
+     * @param targetPath  the path to the target directory
+     * @param file        the file of the class you want to compile
+     * @param classLoader the class loader for dependent classes
+     * @return the compilation result, i.e. the compiler output, a list of errors and a list of warnings
+     * @throws CompilationException
+     *          if a severe error occurred while trying to compile a file
+     */
+    public CompilationResult compile(File sourcePath, File targetPath, File file, ClassLoader classLoader) throws CompilationException {
+        try {
+            CompilationResult result = compiler.compile(sourcePath, targetPath, file, classLoader);
+            if (result.hasErrors()) {
+                result = handleCompilationError(result);
+            }
+            
+            return result;
+        } catch (CompilationException ex) {
+            return handleCompilationException(ex);
+        }
+    }
+
+    /**
+     * 
+     *
+     * @param sourcePath  the path to the source directory
+     * @param targetPath  the path to the target directory
+     * @param file        the relative file name of the class you want to compile
+     * @param classLoader the class loader for dependent classes
+     * @return the compilation result, i.e. the compiler output, a list of errors and a list of warnings
+     * @throws CompilationException
+     *          if a severe error occurred while trying to compile a file
+     */
+    public CompilationResult compile(File sourcePath, File targetPath, String file, ClassLoader classLoader) throws CompilationException {
+        try {
+            CompilationResult result = compiler.compile(sourcePath, targetPath, file, classLoader);
+            if (result.hasErrors()) {
+                result = handleCompilationError(result);
+            }
+
+            return result;
+        } catch (CompilationException ex) {
+            return handleCompilationException(ex);
+        }
+    }
+
+    // ------------------------------------------ Template methods
+
+    /**
+     * <p></p>
+     *
+     * @param result
+     * @return
+     */
+    protected abstract CompilationResult handleCompilationError(CompilationResult result);
+
+    /**
+     * <p>Tries to handle the given compilation exception somehow (e.g. log the cause of the compilation
+     * error, ..). If, however, it's not able to handle the exception, it just rethrows it again.</p>
+     *
+     * @param ex the exception that occurred while trying to compile a file
+     *
+     * @return the compilation result after handling the compilation exception
+     *
+     * @throws CompilationException if this method cannot handle the given exception
+     */
+    protected abstract CompilationResult handleCompilationException(CompilationException ex)
+            throws CompilationException;
+
+}
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/loader/ClassLoaderUtils.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/loader/ClassLoaderUtils.java
index fe85226..8fe26f5 100644
--- a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/loader/ClassLoaderUtils.java
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/loader/ClassLoaderUtils.java
@@ -123,7 +123,7 @@
                 }
             } else {
                 if (logger.isWarnEnabled()) {
-                    logger.warn("Resolving the classpath of the classloader '" + parent + "' - One of its parent class"
+                    logger.warn("Resolving the classpath of the class loader '" + parent + "' - One of its parent class"
                             + " loaders is no URLClassLoader '" + classLoader + "', which means it's possible that"
                             + " some classpath entries aren't in the final outcome of this method call.");
                 }
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/loader/DependencyAwareReloadingClassLoader.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/loader/DependencyAwareReloadingClassLoader.java
index e7a7f13..87d33d0 100644
--- a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/loader/DependencyAwareReloadingClassLoader.java
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/loader/DependencyAwareReloadingClassLoader.java
@@ -136,5 +136,15 @@
             reloadClass(dependentClassName);
         }
     }
+
+    /**
+     * <p>Creates and returns new instance of a reloading class loader which is basically a clone of this one.</p>
+     *
+     */
+    @Override
+    protected ReloadingClassLoader cloneClassLoader(ClassLoader parentClassLoader, File compilationDirectory) {
+        return new DependencyAwareReloadingClassLoader(
+                scanner, registry, parentClassLoader, compilationDirectory);
+    }
     
 }
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/loader/ReloadingClassLoader.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/loader/ReloadingClassLoader.java
index f2b4c38..a5a0b9e 100644
--- a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/loader/ReloadingClassLoader.java
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/loader/ReloadingClassLoader.java
@@ -284,10 +284,9 @@
      * @param parentClassLoader the parent ClassLoader to use
      * @return a copy of the current reloading class loader
      */
-    @SuppressWarnings("unused")
     public ReloadingClassLoader cloneWithParentClassLoader(ClassLoader parentClassLoader) {
         ReloadingClassLoader classLoader =
-                new ReloadingClassLoader(parentClassLoader, compilationDirectory);
+                cloneClassLoader(parentClassLoader, compilationDirectory);
 
         // Note that we don't have to create "deep copies" as the class loaders in the map
         // are immutable anyway (they are only supposed to load a single class) and additionally
@@ -302,6 +301,14 @@
     // ------------------------------------------ Utility methods
 
     /**
+     * <p>Creates and returns new instance of a reloading class loader which is basically a clone of this one.</p>
+     *
+     */
+    protected ReloadingClassLoader cloneClassLoader(ClassLoader parentClassLoader, File compilationDirectory) {
+        return new ReloadingClassLoader(parentClassLoader, compilationDirectory);
+    }
+
+    /**
      * <p>Resolves and returns a File handle that represents the class file of
      * the given class on the file system. However, note that this method only
      * returns <code>null</code> if an error occured while resolving the class
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/Resource.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/Resource.java
new file mode 100644
index 0000000..3c4f45b
--- /dev/null
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/Resource.java
@@ -0,0 +1,28 @@
+package org.apache.myfaces.extensions.scripting.monitor.resources;
+
+import java.io.File;
+
+/**
+ * 
+ *
+ */
+public interface Resource {
+
+    /**
+     * <p>Returns a reference to this resource on the file system,
+     * i.e it returns a reference to a java.io.File object.</p>
+     *
+     * @return a reference to this resource on the file system
+     */
+    public File getFile();
+
+    /**
+     * <p>Returns the time that the resource denoted by this reference was last modified.</p>
+     *
+     * @return  A <code>long</code> value representing the time the file was
+     *          last modified or <code>0L</code> if the file does not exist
+     *          or if an I/O error occurs
+     */
+    public long lastModified();
+
+}
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/ResourceMonitor.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/ResourceMonitor.java
new file mode 100644
index 0000000..ddecfe9
--- /dev/null
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/ResourceMonitor.java
@@ -0,0 +1,11 @@
+package org.apache.myfaces.extensions.scripting.monitor.resources;
+
+/**
+ * 
+ *
+ */
+public interface ResourceMonitor {
+
+    public boolean resourceModified(Resource resource);
+
+}
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/ResourceResolver.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/ResourceResolver.java
new file mode 100644
index 0000000..4c8927c
--- /dev/null
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/ResourceResolver.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.extensions.scripting.monitor.resources;
+
+/**
+ * 
+ *
+ */
+public interface ResourceResolver {
+
+    /**
+     * <p></p>
+     *
+     * @param resourceHandler the callback handler to use for handling found resources 
+     */
+    public void resolveResources(ResourceCallback resourceHandler);
+
+    /**
+     * <p>A callback interface that you can use to handle the resources
+     * found by a resource resolver.</p>
+     */
+    public interface ResourceCallback {
+
+        /**
+         * <p>Callback method that will be invoked by a resource resolver
+         * once it finds another resource that should be handled.</p>
+         * 
+         * @param resource the resource encountered by the resource resolver
+         */
+        public boolean handle(Resource resource);
+
+    }
+
+}
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/FileSystemResource.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/FileSystemResource.java
new file mode 100644
index 0000000..72ccd9f
--- /dev/null
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/FileSystemResource.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.extensions.scripting.monitor.resources.file;
+
+import org.apache.myfaces.extensions.scripting.monitor.resources.Resource;
+
+import java.io.File;
+
+/**
+ * 
+ */
+public class FileSystemResource implements Resource {
+
+    private File file;
+
+    // ------------------------------------------ Constructors
+
+    public FileSystemResource(File file) {
+        if (file == null) {
+            throw new IllegalArgumentException(
+                    "The given file must not be null.");
+        }
+
+        this.file = file;
+    }
+
+    // ------------------------------------------ Resource methods
+
+    /**
+     * <p>Returns a reference to this resource on the file system,
+     * i.e it returns a reference to a java.io.File object.</p>
+     *
+     * @return a reference to this resource on the file system
+     */
+    public File getFile() {
+        return file;
+    }
+
+    /**
+     * <p>Returns the time that the resource denoted by this reference was last modified.</p>
+     *
+     * @return A <code>long</code> value representing the time the file was
+     *         last modified or <code>0L</code> if the file does not exist
+     *         or if an I/O error occurs
+     */
+    public long lastModified() {
+        return getFile().lastModified();
+    }
+
+    // ------------------------------------------ Object methods
+
+
+    /**
+     * <p>Returns a hash code value for the object. This method is
+     * supported for the benefit of hash tables such as those provided by
+     * <code>java.util.Hashtable</code>.</p>
+     *
+     * @return a hash code value for this object
+     *
+     * @see java.io.File#hashCode()
+     * @see java.util.Hashtable
+     */
+    @Override
+    public int hashCode() {
+        return getFile().hashCode();
+    }
+
+    /**
+     * <p>Indicates whether some other object is "equal to" this one.</p>
+     *
+     * @param obj the reference object with which to compare.
+     * @return <code>true</code> if this object is the same as the obj
+     *         argument; <code>false</code> otherwise.
+     * @see #hashCode()
+     * @see java.util.Hashtable
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof FileSystemResource) {
+            FileSystemResource resource = (FileSystemResource) obj;
+            return getFile().equals(resource.getFile());
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * <p>Returns a string representation of the object.</p>
+     *
+     * @return a string representation of the object.
+     */
+    @Override
+    public String toString() {
+        return String.format("FileSystemResource[file: '%s']", file);
+    }
+}
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/FileSystemResourceResolver.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/FileSystemResourceResolver.java
new file mode 100644
index 0000000..ba34eca
--- /dev/null
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/FileSystemResourceResolver.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.extensions.scripting.monitor.resources.file;
+
+import org.apache.myfaces.extensions.scripting.monitor.resources.ResourceResolver;
+
+import java.io.File;
+
+/**
+ * 
+ */
+public class FileSystemResourceResolver implements ResourceResolver {
+
+    private File rootDirectory;
+
+    // ------------------------------------------ Constructors
+
+    public FileSystemResourceResolver(File rootDirectory) {
+        if (rootDirectory == null) {
+            throw new IllegalArgumentException(
+                    "The given root directory must not be null.");
+        }
+
+        this.rootDirectory = rootDirectory;
+    }
+
+    // ------------------------------------------ ResourceResolver methods
+
+    public void resolveResources(ResourceCallback resourceHandler) {
+        resolveResources(resourceHandler, rootDirectory);
+    }
+
+    private boolean resolveResources(ResourceCallback resourceHandler, File currentDirectory) {
+        File[] files = currentDirectory.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    boolean shallContinue = resolveResources(resourceHandler, file);
+                    if (!shallContinue) {
+                        return false;
+                    }
+                } else {
+                    if (matches(file)) {
+                        boolean shallContinue = resourceHandler.handle(new FileSystemResource(file));
+                        if (!shallContinue) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+
+        return true;
+    }
+
+    // ------------------------------------------ Utility methods
+
+    /**
+     * <p>Template method that enables subclasses to filter files, which means depending on the
+     * boolean value that this method returns, the given file will be processed or not (which
+     * again means, it will be forwarded to the resource handler).</p>
+     * 
+     * @param file the file you may want to filter
+     * 
+     * @return <code>true</code> if this file should be processed, <code>false</code> otherwise
+     */
+    protected boolean matches(File file) {
+        return true;
+    }
+    
+}
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/SuffixFileSystemResourceResolver.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/SuffixFileSystemResourceResolver.java
new file mode 100644
index 0000000..39cfa44
--- /dev/null
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/SuffixFileSystemResourceResolver.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.extensions.scripting.monitor.resources.file;
+
+import java.io.File;
+
+/**
+ *
+ */
+public class SuffixFileSystemResourceResolver extends FileSystemResourceResolver {
+
+    /**
+     * The file suffix that a file must have such that
+     * this resource resolver takes it into consideration.
+     */
+    private String fileSuffix;
+
+    // ------------------------------------------ Constructors
+
+    /**
+     * <p>Constructs a new suffix-based FileSystemResourceResolver using the given root directory
+     * and the given file suffix. Note that this suffix determines whether this resource resolver
+     * will take any file into consideration or not, it depends on whether it ends with the given
+     * suffix. Even though you can use any suffix you want, you'll probably only use it for file
+     * types, like for example, ".java", ".class", or ".groovy".</p>
+     *
+     * @param rootDirectory the root directory, i.e. the directory where to start looking for files
+     * @param fileSuffix the file suffix that a file must have
+     */
+    public SuffixFileSystemResourceResolver(File rootDirectory, String fileSuffix) {
+        super(rootDirectory);
+
+        if (fileSuffix == null || fileSuffix.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "The given file suffix must not be null.");
+        }
+
+        this.fileSuffix = fileSuffix;
+    }
+
+    // ------------------------------------------ FileSystemResourceResolver methods
+
+    /**
+     * <p>Checks whether the given file ends with the file suffix that
+     * has been supplied during construction.</p>
+     *
+     * @param file the file that the resource resolver wants to check
+     * 
+     * @return <code>true</code> if the given file ends with the desired
+     *          suffix, <code>false</code> otherwise
+     */
+    @Override
+    protected boolean matches(File file) {
+        return file.getName().endsWith(fileSuffix);
+    }
+    
+}
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/tasks/ConcurrencyUtilsScheduler.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/tasks/ConcurrencyUtilsScheduler.java
new file mode 100644
index 0000000..65ff1d2
--- /dev/null
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/tasks/ConcurrencyUtilsScheduler.java
@@ -0,0 +1,46 @@
+package org.apache.myfaces.extensions.scripting.monitor.tasks;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+/**
+ *
+ */
+public class ConcurrencyUtilsScheduler implements Scheduler {
+
+    private ScheduledExecutorService scheduler;
+
+    private long timeout;
+
+    // ------------------------------------------ Constructors
+
+    public ConcurrencyUtilsScheduler(long timeout) {
+        if (timeout < 0) {
+            throw new IllegalArgumentException(
+                    "The given timeout must not be negative.");
+        }
+
+        this.scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
+            public Thread newThread(Runnable r) {
+                Thread thread = new Thread(r);
+                thread.setDaemon(true);
+                return thread;
+            }
+        });
+        this.timeout = timeout;
+    }
+
+    // ------------------------------------------ Public methods
+
+    public void schedule(Runnable runnable) {
+        scheduler.scheduleAtFixedRate(
+                runnable, timeout, timeout, TimeUnit.MILLISECONDS);
+    }
+
+    public void shutdown() {
+        scheduler.shutdownNow();
+    }
+
+}
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/tasks/FileMonitoringTask.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/tasks/FileMonitoringTask.java
new file mode 100644
index 0000000..c59efe6
--- /dev/null
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/tasks/FileMonitoringTask.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.extensions.scripting.monitor.tasks;
+
+import org.apache.myfaces.extensions.scripting.monitor.resources.Resource;
+import org.apache.myfaces.extensions.scripting.monitor.resources.ResourceMonitor;
+import org.apache.myfaces.extensions.scripting.monitor.resources.ResourceResolver;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * 
+ *
+ */
+public class FileMonitoringTask implements Runnable {
+
+    /**
+     * Contains the timestamps of previously found resources.
+     */
+    private final Map<Resource, Long> timestamps = new HashMap<Resource, Long>();
+
+    /**
+     * Contains the resource monitors along with the according resource resolvers
+     * identifying the set of resources that those monitors are monitoring.
+     */
+    private final Map<ResourceResolver, ResourceMonitor> monitors =
+                        new HashMap<ResourceResolver, ResourceMonitor>();
+
+    /**
+     * The timestamp of the startup of this monitoring task. Thanks to this timestamp
+     * we're able to determine whether a file has been added at runtime as that's only
+     * the case if its timestamp is more recent than this one and we haven't seen it
+     * so far (i.e. there's no entry in the timestamps map).
+     */
+    private long startup;
+
+    // ------------------------------------------ Constructors
+
+    /**
+     * <p>Constructs a new object of this class, which means it just keeps track of
+     * the time at which this monitoring task has been initialized.</p>
+     */
+    public FileMonitoringTask() {
+        this.startup = System.currentTimeMillis();
+    }
+
+    // ------------------------------------------ Public methods
+
+    public void registerResourceMonitor(ResourceResolver resolver, ResourceMonitor monitor) {
+        if (resolver == null || monitor == null) {
+            throw new IllegalArgumentException(
+                "Neither the given resolver nor the given monitor must be null, at least one of both was though.");
+        }
+
+        synchronized (this.monitors) {
+            monitors.put(resolver, monitor);
+        }
+    }
+
+    // ------------------------------------------ Runnable methods
+    
+    public void run() {
+        Iterator<Map.Entry<ResourceResolver, ResourceMonitor>> monitors;
+        synchronized (this.monitors) {
+            monitors = new HashMap<ResourceResolver, ResourceMonitor>(this.monitors).entrySet().iterator();
+        }
+
+        while (monitors.hasNext()) {
+            final Map.Entry<ResourceResolver, ResourceMonitor> entry = monitors.next();
+
+            ResourceResolver resourceResolver = entry.getKey();
+            resourceResolver.resolveResources(
+                new ResourceResolver.ResourceCallback() {
+                    public boolean handle(Resource resource) {
+                        // If we're either dealing with a file that we haven't seen so far or we're
+                        // dealing with an updated version of a file that we've already seen ..
+                        Long lastModified = timestamps.get(resource);
+                        if ((lastModified == null && startup < resource.lastModified())
+                                || (lastModified != null && lastModified < resource.lastModified())) {
+                            timestamps.put(resource, resource.lastModified());
+
+                            // .. notify the monitor about it and tell the calling method
+                            // whether the given monitor wants to continue the scan.
+                            ResourceMonitor monitor = entry.getValue();
+                            return monitor.resourceModified(resource);
+                        }
+
+                        // If it's any other file (i.e. a file that hasn't been modified), continue searching.
+                        return true;
+                    }
+                }
+            );
+        }
+    }
+
+}
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/tasks/Scheduler.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/tasks/Scheduler.java
new file mode 100644
index 0000000..cbc75df
--- /dev/null
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/monitor/tasks/Scheduler.java
@@ -0,0 +1,12 @@
+package org.apache.myfaces.extensions.scripting.monitor.tasks;
+
+/**
+ *
+ */
+public interface Scheduler {
+
+    public void schedule(Runnable command);
+    
+    public void shutdown();
+
+}
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/springframework/beans/factory/RefreshableBeanAttribute.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/springframework/beans/factory/RefreshableBeanAttribute.java
new file mode 100644
index 0000000..f849658
--- /dev/null
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/springframework/beans/factory/RefreshableBeanAttribute.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.extensions.scripting.springframework.beans.factory;
+
+import org.springframework.core.Conventions;
+
+/**
+ * <p>An attribute that will be attached to those bean definitions that are refreshable, i.e. this
+ * class contains some of the required meta data that you'll have to access if you want to decide
+ * whether a bean definition has to be refreshed or not. Note that this attribute, however, is
+ * only capable of determining whether a bean definition has to be refreshed, sometimes you'll
+ * actually have to refresh bean instances as well.</p>
+ *
+ */
+public class RefreshableBeanAttribute {
+
+    /**
+     * <p>The name of the bean definition meta data attribute that refers to an instance
+     * of this class, i.e. use this attribute name if you want to obtain all information
+     * regarding the last time that the bean definition has been refreshed.</p>
+     */
+    public static final String REFRESHABLE_BEAN_ATTRIBUTE =
+            Conventions.getQualifiedAttributeName(RefreshableBeanAttribute.class, "refreshableBeanAttribute");
+
+    /**
+     * <p>The timestamp in milliseconds of the last time that the bean
+     * definition that this attribute belongs to has been requested to
+     * refresh itself.</p>
+     */
+    private long refreshRequested;
+
+    /**
+     * <p>The timestamp in milliseconds of the last time that the bean
+     * definition that this attribute belongs to has been actually
+     * refreshed.</p>
+     */
+    private long refreshExecuted;
+
+    /**
+     * <p>By calling this method the user is able to request another refresh. Note that
+     * this doesn't cause the bean factory to refresh the bean definition immediately,
+     * but rather it just signals a request. The bean definition will be refreshed once
+     * the bean factory has to deal with the next bean request (i.e. a call to
+     * getBean()).</p>
+     */
+    public void requestRefresh() {
+        refreshRequested = System.currentTimeMillis();
+    }
+
+    /**
+     * <p>Returns the timestamp in milliseconds of the last time that a refresh operation
+     * has been requested.</p>
+     *
+     * @return the timestamp in milliseconds of the last refresh request
+     */
+    public long getRequestedRefreshDate() {
+        return refreshRequested;
+    }
+
+    /**
+     * <p>By calling this method the user indicates that the according bean definition
+     * has just been refreshed, which means that the method #{@link #requiresRefresh()}
+     * will return <code>false</code> until the user requests the next refresh.</p>
+     */
+    public void executedRefresh() {
+        refreshExecuted = System.currentTimeMillis();
+    }
+
+    /**
+     * <p>Returns the timestamp in milliseconds of the last time that a refresh operation
+     * has been executed.</p>
+     *
+     * @return the timestamp in milliseconds of the last executed refresh operation
+     */
+    public long getExecutedRefreshDate() {
+        return refreshExecuted;
+    }
+
+    /**
+     * <p>Determines whether a refresh is required, i.e. whether the user has requested
+     * another refresh operation by calling {@link #requestRefresh()} recently. Note that
+     * a call to this method only determines whether the bean definition on its own has
+     * to be refreshed (i.e. it doesn't even consider a particular bean instance).</p>
+     *
+     * @return whether a refresh call is required
+     */
+    public boolean requiresRefresh() {
+        return getExecutedRefreshDate() < getRequestedRefreshDate();
+    }
+
+}
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/springframework/beans/factory/RefreshableBeanFactory.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/springframework/beans/factory/RefreshableBeanFactory.java
new file mode 100644
index 0000000..689d67e
--- /dev/null
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/springframework/beans/factory/RefreshableBeanFactory.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.extensions.scripting.springframework.beans.factory;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+
+/**
+ *
+ */
+public interface RefreshableBeanFactory extends ConfigurableListableBeanFactory {
+
+    /**
+     * <p>Requests a refresh operation for the definition of the bean with the given name. However,
+     * note that this doesn't necessarily mean that the bean definition gets refreshed immediately.
+     * Calling this method just ensures that the bean factory uses a refreshed bean definition by
+     * the next time you access the bean with the given name again.</p>
+     *
+     * @param name the name of the bean to refresh
+     *
+     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException if there is no bean with the given name
+     * @throws org.springframework.beans.factory.BeanDefinitionStoreException  in case of an invalid bean definition
+     */
+    public void refreshBeanDefinition(String name) throws BeansException;
+
+    /**
+     * <p>Requests a refresh operation for definitions of beans with the given type. Again this
+     * doesn't necessarily mean that those bean definitions get refreshed immediately as calling
+     * this method just ensures that the bean factory uses a refreshed bean definition the next
+     * time you access one of those beans again.</p>
+     *
+     * @param className the type that you want to refresh beans for
+     * @param includeNonSingletons whether to include prototype or scoped beans too
+	 * or just singletons (also applies to FactoryBeans)
+	 * @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
+	 * <i>objects created by FactoryBeans</i> (or by factory methods with a
+	 * "factory-bean" reference) for the type check
+     *
+     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException if there is no bean with the given name
+     * @throws org.springframework.beans.factory.BeanDefinitionStoreException  in case of an invalid bean definition
+     */
+    public void refreshBeanDefinitionsOfType(String className, boolean includeNonSingletons, boolean allowEagerInit)
+                throws BeansException;
+
+}
diff --git a/core/core/src/main/java/org/apache/myfaces/extensions/scripting/springframework/monitor/resources/ResourceAdapter.java b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/springframework/monitor/resources/ResourceAdapter.java
new file mode 100644
index 0000000..3f0174c
--- /dev/null
+++ b/core/core/src/main/java/org/apache/myfaces/extensions/scripting/springframework/monitor/resources/ResourceAdapter.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.extensions.scripting.springframework.monitor.resources;
+
+import org.apache.myfaces.extensions.scripting.monitor.resources.Resource;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * <p>A resource implementation that basically just adapts Spring resources such that
+ * you can use Spring resource objects within MyFaces Scripting as well. In doing so,
+ * you're able to use its pattern based resource resolving mechanisms as well.</p>
+ *
+ * @author Bernhard Huemer
+ */
+public class ResourceAdapter implements Resource {
+
+    /** The underlying Spring resource object that we're delegating to. */
+    private org.springframework.core.io.Resource resource;
+
+    // ------------------------------------------ Constructors
+
+    /**
+     * <p>Constructs a new resource adapter using the underlying Spring resource object
+     * that this adapter is delegating to afterwards.</p>
+     *
+     * @param resource the underlying Spring resource object
+     */
+    public ResourceAdapter(org.springframework.core.io.Resource resource) {
+        if (resource == null) {
+            throw new IllegalArgumentException(
+                    "The given resource object must not be null.");
+        }
+
+        this.resource = resource;
+    }
+
+    // ------------------------------------------ Resource methods
+
+    /**
+     * <p>Returns a reference to this resource on the file system,
+     * i.e it returns a reference to a java.io.File object.</p>
+     *
+     * @return a reference to this resource on the file system
+     */
+    public File getFile() {
+        try {
+            return resource.getFile();
+        } catch (IOException ex) {
+            // This shouldn't happen as we're actually only dealing with Spring resource
+            // implementations that don't throw this exception, hence we rethrow this
+            // exception unchecked.
+            throw new IllegalStateException(
+                    "The Spring resource object isn't supposed to throw an IOException!", ex);
+        }
+    }
+
+    /**
+     * <p>Returns the time that the resource denoted by this reference was last modified.</p>
+     *
+     * @return A <code>long</code> value representing the time the file was
+     *         last modified or <code>0L</code> if the file does not exist
+     *         or if an I/O error occurs
+     */
+    public long lastModified() {
+        try {
+            return resource.lastModified();
+        } catch (IOException ex) {
+            return 0L;
+        }
+    }
+    
+}
diff --git a/core/core/src/main/java/org/apache/myfaces/scripting/core/dependencyScan/ClassScanUtils.java b/core/core/src/main/java/org/apache/myfaces/scripting/core/dependencyScan/ClassScanUtils.java
index 83b1be3..b25756c 100644
--- a/core/core/src/main/java/org/apache/myfaces/scripting/core/dependencyScan/ClassScanUtils.java
+++ b/core/core/src/main/java/org/apache/myfaces/scripting/core/dependencyScan/ClassScanUtils.java
@@ -18,8 +18,6 @@
  */
 package org.apache.myfaces.scripting.core.dependencyScan;
 
-import org.objectweb.asm.Type;
-
 import java.util.Collection;
 import java.util.Set;
 
diff --git a/core/core/src/test/java/org/apache/myfaces/extensions/scripting/loader/dependencies/registry/DefaultDependencyRegistryTest.java b/core/core/src/test/java/org/apache/myfaces/extensions/scripting/loader/dependencies/registry/DefaultDependencyRegistryTest.java
index 3251ca5..eab05e0 100644
--- a/core/core/src/test/java/org/apache/myfaces/extensions/scripting/loader/dependencies/registry/DefaultDependencyRegistryTest.java
+++ b/core/core/src/test/java/org/apache/myfaces/extensions/scripting/loader/dependencies/registry/DefaultDependencyRegistryTest.java
@@ -1,6 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 package org.apache.myfaces.extensions.scripting.loader.dependencies.registry;
 
-import junit.framework.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 /**
  * <p>Test class for
@@ -8,7 +29,7 @@
  *
  * @author Bernhard Huemer
  */
-public class DefaultDependencyRegistryTest extends TestCase {
+public class DefaultDependencyRegistryTest {
 
     // ------------------------------------------ Test methods
 
@@ -16,9 +37,11 @@
      * <p>Tests whether the registry stores dependencies and dependent classes correctly. Just
      * consider this test case as some kind of example of what I mean with "dependent classes"
      * and "dependencies".</p>
-     * 
+     *
+     * @throws Exception if an unexpected error occurs
      */
-    public void testRegisterDependencies() {
+    @Test
+    public void testRegisterDependencies() throws Exception {
         DefaultDependencyRegistry registry = new DefaultDependencyRegistry();
         registry.registerDependency("com.foo.Bar", "com.foo.Bla");
         registry.registerDependency("com.foo.Bar", "com.foo.Blubb");
diff --git a/core/core/src/test/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/FileSystemResourceResolverTest.java b/core/core/src/test/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/FileSystemResourceResolverTest.java
new file mode 100644
index 0000000..76cea18
--- /dev/null
+++ b/core/core/src/test/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/FileSystemResourceResolverTest.java
@@ -0,0 +1,59 @@
+package org.apache.myfaces.extensions.scripting.monitor.resources.file;
+
+import org.apache.myfaces.extensions.scripting.monitor.resources.Resource;
+import org.apache.myfaces.extensions.scripting.monitor.resources.ResourceResolver;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.junit.Assert.fail;
+
+/**
+ * <p>Test class for
+ * <code>org.apache.myfaces.extensions.scripting.monitor.resources.file.FileSystemResourceResolver</code>.</p>
+ */
+public class FileSystemResourceResolverTest {
+
+    // ------------------------------------------ Test methods
+
+    /**
+     * <p>Tests whether the resource resolver stops looking for further resources
+     * if one callback tells it to do so.</p>
+     *
+     * @throws Exception if an unexpected error occurs
+     */
+    @Test
+    public void testBreakScanning() throws Exception {
+        ResourceResolver resourceResolver = new FileSystemResourceResolver(
+                new File(getClass().getResource(".").getFile()));
+        resourceResolver.resolveResources(new BreakResourceCallback());
+    }
+
+    // ------------------------------------------ Utility classes
+
+    private class BreakResourceCallback implements ResourceResolver.ResourceCallback {
+
+        /** Flag that tells us whether this callback has already been called before. */
+        boolean shallContinue = true;
+
+        /**
+         * <p>Callback method that will be invoked by a resource resolver
+         * once it finds another resource that should be handled.</p>
+         *
+         * @param resource the resource encountered by the resource resolver
+         */
+        public boolean handle(Resource resource) {
+            if (!shallContinue) {
+                fail("The resource resolver called this callback even though it was told " +
+                        "to stop looking for further resources previously.");
+            } else {
+                // Tell the resource resolver to stop looking for further resources
+                // by returning false.
+                shallContinue = false;
+            }
+
+            return shallContinue;
+        }
+    }
+
+}
diff --git a/core/core/src/test/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/SuffixFileSystemResourceResolverTest.java b/core/core/src/test/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/SuffixFileSystemResourceResolverTest.java
new file mode 100644
index 0000000..65df2c9
--- /dev/null
+++ b/core/core/src/test/java/org/apache/myfaces/extensions/scripting/monitor/resources/file/SuffixFileSystemResourceResolverTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.extensions.scripting.monitor.resources.file;
+
+import org.apache.myfaces.extensions.scripting.monitor.resources.Resource;
+import org.apache.myfaces.extensions.scripting.monitor.resources.ResourceResolver;
+import org.apache.myfaces.extensions.scripting.monitor.resources.file.SuffixFileSystemResourceResolver;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * <p>Test class for
+ * <code>org.apache.myfaces.extensions.scripting.monitor.resources.file.SuffixFileSystemResourceResolver</code>.</p>
+ */
+public class SuffixFileSystemResourceResolverTest {
+
+    // ------------------------------------------ Test methods
+
+    /**
+     * <p>Tests whether the resource resolver only issues callbacks if it encountered a file that
+     * ends with the given file suffix.</p>
+     * 
+     * @throws Exception if an unexpected error occurs
+     */
+    @Test
+    public void testScanClassFiles() throws Exception {
+        ResourceResolver resourceResolver = new SuffixFileSystemResourceResolver(
+                new File(getClass().getResource(".").getFile()), ".class");
+        resourceResolver.resolveResources(new ResourceResolver.ResourceCallback() {
+            public boolean handle(Resource resource) {
+                assertTrue(String.format("The file [%s] doesn't end with '.class'.", resource.getFile()),
+                        resource.getFile().getName().endsWith(".class"));
+                return true;
+            }
+        });
+    }
+
+    /**
+     * <p>Tests whether the resource resolver also issues callbacks if there are no such
+     * files in the root directory. Note, the root directory is not empty, it's just the
+     * case that we've used an arbitrary file suffix.</p>
+     *
+     * @throws Exception if an unexpected error occurs
+     */
+    @Test
+    public void testScanNonExistingFiles() throws Exception {
+        ResourceResolver resourceResolver = new SuffixFileSystemResourceResolver(
+                new File(getClass().getResource(".").getFile()), ".foobar123??");
+        resourceResolver.resolveResources(new ResourceResolver.ResourceCallback() {
+            public boolean handle(Resource resource) {
+                fail("There is no file that ends with '.foobar123??' so " +
+                        "this callback shouldn't have been called.");
+                return true;
+            }
+        });
+    }
+
+}
diff --git a/core/core/src/test/java/org/apache/myfaces/extensions/scripting/monitor/tasks/ConcurrencyUtilsSchedulerTest.java b/core/core/src/test/java/org/apache/myfaces/extensions/scripting/monitor/tasks/ConcurrencyUtilsSchedulerTest.java
new file mode 100644
index 0000000..11ca1c9
--- /dev/null
+++ b/core/core/src/test/java/org/apache/myfaces/extensions/scripting/monitor/tasks/ConcurrencyUtilsSchedulerTest.java
@@ -0,0 +1,97 @@
+package org.apache.myfaces.extensions.scripting.monitor.tasks;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * <p>Test class for
+ * <code>org.apache.myfaces.extensions.scripting.monitor.tasks.ConcurrencyUtilsScheduler</code>.</p>
+ */
+public class ConcurrencyUtilsSchedulerTest {
+
+    /** The scheduler instance that we want to test */
+    private Scheduler scheduler;
+
+    // ------------------------------------------ Test lifecycle methods
+
+    /**
+     * <p>Initializes the scheduler instance that we want to test.</p>
+     *
+     * @throws Exception if an unexpected error occurs
+     */
+    @Before
+    public void setUp() throws Exception {
+        scheduler = new ConcurrencyUtilsScheduler(100);
+    }
+
+    /**
+     * <p>Shuts down the scheduler instance that we wanted to test.</p>
+     *
+     * @throws Exception if an unexpected error occurs
+     */
+    @After
+    public void tearDown() throws Exception {
+        scheduler.shutdown();
+    }
+
+    // ------------------------------------------ Test methods
+
+    @Test
+    public void testScheduleTask() throws Exception {
+        final AtomicInteger counter = new AtomicInteger(0);
+
+        scheduler.schedule(new Runnable() {
+            public void run() {
+                counter.incrementAndGet();
+            }
+        });
+
+        Thread.sleep(1000);
+
+        assertTrue("The runnable task hasn't been called often enough.",
+                counter.intValue() >= 10);
+
+        scheduler.shutdown();
+    }
+
+    @Test
+    public void testScheduleTimespan() throws Exception {
+        Scheduler scheduler = new ConcurrencyUtilsScheduler(100);
+        scheduler.schedule(new TimespanTest());
+
+        Thread.sleep(1000);
+
+        scheduler.shutdown();
+    }
+
+    // ------------------------------------------ Utility classes
+
+    private class TimespanTest implements Runnable {
+
+        /** The last time that the runnable method has been called, or this object has been initialized. */
+        private long timestamp = System.currentTimeMillis();
+
+        /**
+         * <p>Callback that will be called by the scheduler.</p>
+         */
+        public void run() {
+            long current = System.currentTimeMillis();
+
+            // Test whether the timespan that has passed is between 90 and 110 milliseconds
+            long timespan = current - timestamp;
+            if (timespan < 90 || timespan > 110) {
+                fail(String.format("The timespan that has passed is " +
+                        "longer or shorter than expected: %s ms", timespan));
+            } else {
+                timestamp = current;
+            }
+        }
+    }
+
+}