пятница, 6 августа 2010 г.

Wicket and Groovy integration.

Perfect idea to integrate wicket and groovy. From one side the wicket way - strong separation between code and markup, from other side groovy as code behind the markup. And to get extra flexibility i can use the wicket auto components with groovy behind ! Cool !!!

Nice try for wicket 1.3 and "old" groovy 1.6 you can find here http://bigheadco.blogspot.com/2007/03/wicket-and-groovy.html BTW wicket-contrib-groovy was moved to attic in svn repository.

So for wicket 1.4 and groovy 1.7.4 need only two classes from stuff above GroovyWarClassLoader and GroovyClassResolver. The rest is obsolete, because groovy 1.7 support anonymous inner classes and nested classes. Original GroovyWarClassLoader and GroovyClassResolver need to be modified for wicket 1.4.

 
/*
* $Id: GroovyWarClassLoader.java 524 2006-01-08 09:30:40Z jdonnerstag $
* $Revision: 524 $ $Date: 2006-01-08 01:30:40 -0800 (Sun, 08 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 groovy.lang.GroovyClassLoader;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.control.CompilationFailedException;


/**
* I had some issues with the GroovyClassLoader (1.0.7-beta) which failed to
* load a groovy files contained in the same war file. E.g.
*
* <pre>
* wicket-examples.war!/web-inf/classes/groovy/A.groovy
* wicket-examples.war!/web-inf/classes/groovy/B.groovy
*
* public class A {
* public A() {
* B b = new B()
* }
* }
* </pre>
*
* GroovyClassLoader did not load Groovy class B.
*
* @author Juergen Donnerstag
*/

public class GroovyWarClassLoader extends GroovyClassLoader
{
/** Logger */
final private static Log log = LogFactory.getLog(GroovyWarClassLoader.class);

/**
* Constructor
*
* @param loader
* The servlet's class loader
*/

public GroovyWarClassLoader(ClassLoader loader)
{
super(loader);
}

/**
* First try parent's implementation. If it fails, it'll throw a
* ClassCastException. Catch it and than apply additional means to load the
* class.
*
* @param name
* Class name incl. package
* @return The Class loaded, if found
* @throws ClassNotFoundException,
* if class could not be loaded
*/

protected Class findClass(final String name) throws ClassNotFoundException
{
try
{
// Try Groovies default implementation first
return super.findClass(name);
}
catch (ClassNotFoundException ex)
{
log.debug("class name: " + name);

// classname => filename
//String filename = Strings.replaceAll(name, ".", "/") + ".groovy";
String filename = name.replace('.','/') + ".groovy";

// File exists?
final URL url = getResource(filename);
if (url != null)
{
try
{
// Get Groovy to parse the file and create the Class
final InputStream in = url.openStream();
if (in != null)
{
Class clazz = parseClass(in);
if (clazz != null)
{
return clazz;
}
}
else
{
log.warn("Groovy file not found: " + filename);
}
}
catch (CompilationFailedException e)
{
throw new ClassNotFoundException("Error parsing groovy file: "
+ filename, e);
}
catch (IOException e)
{
throw new ClassNotFoundException("Error reading groovy file: "
+ filename, e);
}
catch (Throwable e)
{
throw new ClassNotFoundException("Error while reading groovy file: "
+ filename, e);
}
}

throw ex;
}
}
}


----------------------------------------------------------------------------------------------------

 
/*
* $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.io.IOException;
import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.control.CompilationFailedException;

import org.apache.wicket.Application;
import org.apache.wicket.application.IClassResolver;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.application.DefaultClassResolver;
import org.apache.wicket.util.listener.IChangeListener;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.watch.IModificationWatcher;

import java.util.concurrent.ConcurrentHashMap;
import java.net.URL;

/**
* 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();
private final Map classCache = new ConcurrentHashMap();

/** 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;
}

/**
*
* @see org.apache.wicket.application.IClassResolver#getResources(java.lang.String)
*/

public Iterator<URL> getResources(String name)
{
HashSet<URL> loadedFiles = new HashSet<URL>();
try
{
// Try the classloader for the wicket jar/bundle
Enumeration<URL> resources = Application.class.getClassLoader().getResources(name);
loadResources(resources, loadedFiles);

// Try the classloader for the user's application jar/bundle
resources = Application.get().getClass().getClassLoader().getResources(name);
loadResources(resources, loadedFiles);

// Try the context class loader
resources = Thread.currentThread().getContextClassLoader().getResources(name);
loadResources(resources, loadedFiles);
}
catch (IOException e)
{
throw new WicketRuntimeException(e);
}

return loadedFiles.iterator();
}

/**
*
* @param resources
* @param loadedFiles
*/

private void loadResources(Enumeration<URL> resources, Set<URL> loadedFiles)
{
if (resources != null)
{
while (resources.hasMoreElements())
{
final URL url = resources.nextElement();
if (!loadedFiles.contains(url))
{
loadedFiles.add(url);
}
}
}
}




/**
* Resolve the class for the given classname. First try standard java
* classes, then groovy files. Groovy file name must be
* &lt;classname&gt;.groovy.
*
* @param classname
* The object's class name
* @return The class
* @see IClassResolver#resolveClass(String)
*/

public Class resolveClass(final String classname) throws ClassNotFoundException
{
try
{
return defaultClassResolver.resolveClass(classname);
}
catch (WicketRuntimeException ex)
{
; // default resolver failed. Try the groovy specific loader next
}
catch (ClassNotFoundException 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 IModificationWatcher 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);
}
}