Tuesday, January 6, 2009

ClassNotFoundException when running Grails with Acegi plugin

Recently, I've been playing around with Groovy on Grails. I have to admit that its kinda hard for me to learn groovy since I already work as a Java programmer for the past 4 years. Anyway, I was trying the Acegi plugin. Everything works fine, until I come to the generate-manager stage. The generate-manager script is to generate controllers and view files for all the acegi domains. After I run the generate-manager script, and I start the server, 2 exceptions are thrown.

[16] commons.DefaultGrailsApplication Class not found attempting to load class RoleController
java.lang.ClassNotFoundException: RoleController
at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
at java.security.AccessController.doPrivileged(Native Method)
at RunApp_groovy$_run_closure2_closure7.doCall(RunApp_groovy:67)
at RunApp_groovy$_run_closure2_closure7.doCall(RunApp_groovy)
at Init_groovy$_run_closure6.doCall(Init_groovy:131)
at RunApp_groovy$_run_closure2.doCall(RunApp_groovy:66)
at RunApp_groovy$_run_closure2.doCall(RunApp_groovy)
at RunApp_groovy$_run_closure1.doCall(RunApp_groovy:57)
at RunApp_groovy$_run_closure1.doCall(RunApp_groovy)
at gant.Gant.dispatch(Gant.groovy:271)
at gant.Gant.this$2$dispatch(Gant.groovy)
at gant.Gant.invokeMethod(Gant.groovy)
at gant.Gant.processTargets(Gant.groovy:436)
at gant.Gant.processArgs(Gant.groovy:372)
[32] commons.DefaultGrailsApplication Class not found attempting to load class UserController
java.lang.ClassNotFoundException: UserController
at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
at java.security.AccessController.doPrivileged(Native Method)
at RunApp_groovy$_run_closure2_closure7.doCall(RunApp_groovy:67)
at RunApp_groovy$_run_closure2_closure7.doCall(RunApp_groovy)
at Init_groovy$_run_closure6.doCall(Init_groovy:131)
at RunApp_groovy$_run_closure2.doCall(RunApp_groovy:66)
at RunApp_groovy$_run_closure2.doCall(RunApp_groovy)
at RunApp_groovy$_run_closure1.doCall(RunApp_groovy:57)
at RunApp_groovy$_run_closure1.doCall(RunApp_groovy)
at gant.Gant.dispatch(Gant.groovy:271)
at gant.Gant.this$2$dispatch(Gant.groovy)
at gant.Gant.invokeMethod(Gant.groovy)
at gant.Gant.processTargets(Gant.groovy:436)
at gant.Gant.processArgs(Gant.groovy:372)

Obviously, the compiled class UserController and RoleController are not found. How come? I checked the grails-app/controllers, both UserController and RoleController are there as groovy code. I always assume that the grails will compile the groovy code and turn them into java class file. Then why is this happening?

After spending hours of errors and trial, I finally figured it out. In UserController.groovy, the class name is actually PersonController, which is so different from Java. In Java, if the class name and the file name is different, a compile error will be thrown, but this is not the case in groovy. So, I checked the class folder, only PersonController and AuthorityController are found. Then, another question is, why does grails choose to load UserController and RoleController, why not PersonController and AuthorityController? If you look at the applicationContext.xml in WEB-INF folder, you will come to this very bean

<bean id="grailsResourceHolder" scope="prototype"
class="org.codehaus.groovy.grails.commons.spring.GrailsResourceHolder">
<property name="resources">
<value>classpath*:**/grails-app/**/*.groovy</value>
</property>
</bean>

What does this bean do? This bean will actually collect the name of all the groovy files under the grails-app folder, and try to class load them. For example, if UserController.groovy is found, then grails will try to load UserController.class, and this is the cause to it.

Therefore, I modified the GenerateManager script under Acegi plugin to make it work. If you are facing the same problem as I did, feel free to copy the script, but kindly leave a message here so that I know that I am not alone.

includeTargets << new File("$acegiPluginDir/scripts/_SecurityTargets.groovy")

pluginTemplatePath = "$templateDir/manager"

target('default': 'Generates view and controller for Spring Security user management') {
loadConfig()

generateControllerAndViews (resourceName='User', targetName=personClassName)
generateControllerAndViews (resourceName='Role', targetName=authorityClassName)
generateControllerAndViews (resourceName='Requestmap', targetName=requestmapClassName)
}

private void generateControllerAndViews(String resourceName, String targetName) {

String path = "$basedir/grails-app/controllers/${targetName}Controller.groovy"
def outFile = new File(path)
if (outFile.exists()) {
Ant.input addProperty: 'overwrite', message: "$outFile.name exists - overwrite? y/n"
if ('y' == Ant.antProject.properties.'overwrite') {
overwrite = true
}
}
else {
overwrite = true
}

println "generating files for $targetName ......."

println "generating file $path"
generateFile "$pluginTemplatePath/controllers/_${resourceName}Controller.groovy", path

path = "$basedir/grails-app/views/${targetName.toLowerCase()}"
String viewPath = "$pluginTemplatePath/views/${resourceName.toLowerCase()}"
println "generating view files - $path/* "
Ant.mkdir dir: path
generateFile "$viewPath/list.gsp", "$path/list.gsp"
generateFile "$viewPath/edit.gsp", "$path/edit.gsp"
generateFile "$viewPath/create.gsp", "$path/create.gsp"
generateFile "$viewPath/show.gsp", "$path/show.gsp"
}

You can find this file under $Your_application/plugins/acegi_$VERSION/scripts
My script might not be the best solution around, so feel free to give me any feedback.

Happy coding :)

2 comments:

Scott Frederick said...

I believe the correct way to do the customization you did is documented here: http://www.grails.org/AcegiSecurity+Plugin+-+Customizing+with+SecurityConfig

熙辰 said...

當一個人內心能容納兩樣相互衝突的東西,這個人便開始變得有價值了。...........................................................................