/* * $Id: GroovyClassResolver.java 548 2006-01-19 23:59:30Z joco01 $ * $Revision: 548 $ $Date: 2006-01-19 15:59:30 -0800 (Thu, 19 Jan 2006) $ * * ============================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package wicket.contrib.groovy; import java.io.InputStream; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.groovy.control.CompilationFailedException; import wicket.Application; import wicket.WicketRuntimeException; import wicket.application.DefaultClassResolver; import wicket.application.IClassResolver; import wicket.util.concurrent.ConcurrentReaderHashMap; import wicket.util.listener.IChangeListener; import wicket.util.resource.IResourceStream; import wicket.util.watch.ModificationWatcher; /** * Extends the default Page Factory to allow for Groovy based classes. * Modifications to groovy files are tracked and files are reloaded if modified. * * @author Juergen Donnerstag */ public class GroovyClassResolver implements IClassResolver { /** Logging */ private static final Log log = LogFactory.getLog(GroovyClassResolver.class); /** * Caching map of class name to groovy class; not sure if GroovyClassLoader * does it as well */ private final Map classCache = new ConcurrentReaderHashMap(); /** Default class resolver */ private final IClassResolver defaultClassResolver = new DefaultClassResolver(); /** Application */ private final Application application; /** * Constructor * * @param application * Application */ public GroovyClassResolver(final Application application) { this.application = application; } /** * Resolve the class for the given classname. First try standard java * classes, then groovy files. Groovy file name must be * <classname>.groovy. * * @param classname * The object's class name * @return The class * @see wicket.application.IClassResolver#resolveClass(String) */ public Class resolveClass(final String classname) { try { return defaultClassResolver.resolveClass(classname); } catch (WicketRuntimeException ex) { ; // default resolver failed. Try the groovy specific loader next } // If definition already loaded, ... Class groovyPageClass = (Class) classCache.get(classname); if (groovyPageClass != null) { return groovyPageClass; } // Else, try Groovy. final IResourceStream resource = application.getResourceSettings() .getResourceStreamLocator().locate(getClass(), classname.replace('.', '/'), null, null, ".groovy"); if (resource != null) { try { // Load the groovy file, get the Class and watch for changes groovyPageClass = loadGroovyFileAndWatchForChanges(classname, resource); if (groovyPageClass != null) { return groovyPageClass; } } catch (WicketRuntimeException ex) { throw new WicketRuntimeException("Unable to load class with name: " + classname, ex); } } else { throw new WicketRuntimeException("File not found: " + resource); } throw new WicketRuntimeException("Unable to load class with name: " + classname); } /** * Load a Groovy file, create a Class object and put it into the cache * * @param classname * The class name to be created by the Groovy filename * @param resource * The Groovy resource * @return the Class object created by the groovy resouce */ private final Class loadGroovyFile(String classname, final IResourceStream resource) { // Ensure that we use the correct classloader so that we can find // classes in an application server. ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) { cl = GroovyClassResolver.class.getClassLoader(); } final GroovyWarClassLoader groovyClassLoader = new GroovyWarClassLoader(cl); Class clazz = null; try { final InputStream in = resource.getInputStream(); if (in != null) { clazz = groovyClassLoader.parseClass(in); if (clazz != null) { // this is necessary because with groovy the filename can be // different from the class definition included. if (false == classname.equals(clazz.getName())) { log.warn("Though it is possible, the Groovy file name and " + "the java class name defined in that file SHOULD " + "match and follow the java rules"); } classname = clazz.getName(); } } else { log.warn("Groovy file not found: " + resource); } } catch (CompilationFailedException e) { throw new WicketRuntimeException("Error parsing groovy file: " + resource, e); } catch (Throwable e) { throw new WicketRuntimeException("Error while reading groovy file: " + resource, e); } finally { if (clazz == null) { // Groovy file not found; error while compiling etc.. // Remove it from cache classCache.remove(classname); } else { // Put the new class definition into the cache classCache.put(classname, clazz); } } return clazz; } /** * Load the groovy file and watch for changes. If changes to the groovy * happens, than reload the file. * * @param classname * @param resource * @return Loaded class */ private Class loadGroovyFileAndWatchForChanges(final String classname, final IResourceStream resource) { // Watch file in the future final ModificationWatcher watcher = application.getResourceSettings() .getResourceWatcher(true); if (watcher != null) { watcher.add(resource, new IChangeListener() { public void onChange() { try { log.info("Reloading groovy file from " + resource); // Reload file and update cache final Class clazz = loadGroovyFile(classname, resource); log.debug("Groovy file contained definition for class: " + clazz.getName()); } catch (Exception e) { log.error("Unable to load groovyy file: " + resource, e); } } }); } log.info("Loading groovy file from " + resource); return loadGroovyFile(classname, resource); } }