- 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;
+ }
+ }
+ }
+
+}