class loader issues switching to listingClass parameter

258 views
Skip to first unread message

Mike Wood

unread,
Oct 30, 2012, 11:48:21 AM10/30/12
to swagger-sw...@googlegroups.com
Hi,

I needed to move the API Resource getHelp out of my main API class so I can use the standard REST root GET for a readAll type listing. I created a class in the same package as the API itself but am having class-loader issues using the listingClass parameter.

package com.s.g.queuemanager

@Path('/api/job')
@Api(value = "/api/job", description = "Job API", listingPath = "/api/resources/job")
@Consumes(['application/xml','application/json'])
@Produces(['application/xml','application/json'])
class JobCollectionResource {
...
}

-----------------
package com.stationx.genepool.queuemanager

@Path("/api/resources/job")
@Api(value = "/api/job", description = "Operations about Jobs",
     listingPath = "/api/resources/job", listingClass = "com.s.g.queuemanager.JobCollectionResource")
@Produces(["application/json", "application/xml"])
class JobCollectionResourceAPIListing extends JavaHelp {}
-------

Here's the exception:

ERROR errors.GrailsExceptionResolver  - ClassNotFoundException occurred when processing request: [GET] /api/resources/job - parameters:
api_key: special-key
class com.s.g.queuemanager.JobCollectionResource not found. Stacktrace follows:
Message: class com.s.g.queuemanager.JobCollectionResource not found

    Line | Method
->>   46 | loadClass                       in com.wordnik.swagger.core.SwaggerContext$
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|     66 | getHelp                         in com.wordnik.swagger.jaxrs.JavaHelp
|     -1 | getHelp . . . . . . . . . . . . in com.s.g.queuemanager.JobCollectionResourceAPIListing
|     -2 | invoke0                         in sun.reflect.NativeMethodAccessorImpl
|     39 | invoke . . . . . . . . . . . .  in     ''
|     25 | invoke                          in sun.reflect.DelegatingMethodAccessorImpl
|    597 | invoke . . . . . . . . . . . .  in java.lang.reflect.Method
|   1231 | jlrMethodInvoke                 in com.springsource.loaded.ri.ReflectiveInterceptor
|     60 | invoke . . . . . . . . . . . .  in com.sun.jersey.spi.container.JavaMethodInvokerFactory$1

And here's a listing showing the classes do exist (I am using grails but that should not be a factor?) :-

$ ls -l WEB-INF/classes/com/s/g/queuemanager/
...
-rw-r--r--  1 mike  staff   9944 Oct 29 19:07 JobCollectionResource.class
-rw-r--r--  1 mike  staff   6950 Oct 30 06:19 JobCollectionResourceAPIListing.class
-rw-r--r--  1 mike  staff   8184 Oct 29 14:44 JobResource.class
-rw-r--r--  1 mike  staff   8059 Oct 27 07:02 JobResourceService.class

When I tried it with @Singleton in the APIListing class (as I've seen in several examples) I got a different error:

Caused by BeanInstantiationException: Could not instantiate bean class [com.s.g.queuemanager.JobCollectionResourceAPIListing]: Constructor threw exception; nested exception is java.lang.RuntimeException: Can't instantiate singleton com.s.g.queuemanager.JobCollectionResourceAPIListing. Use com.s.g.queuemanager.JobCollectionResourceAPIListing.instance
->>  162 | instantiateClass                in org.springframework.beans.BeanUtils
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    108 | instantiate                     in org.codehaus.groovy.grails.commons.spring.ReloadAwareAutowireCapableBeanFactory$1
|    990 | instantiateBean . . . . . . . . in org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
|    943 | createBeanInstance              in     ''
|    485 | doCreateBean . . . . . . . . .  in     ''
|    130 | doCreateBean                    in org.codehaus.groovy.grails.commons.spring.ReloadAwareAutowireCapableBeanFactory
|    456 | createBean . . . . . . . . . .  in org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
|    313 | doGetBean                       in org.springframework.beans.factory.support.AbstractBeanFactory
|    197 | getBean . . . . . . . . . . . . in     ''
|   1097 | getBean                         in org.springframework.context.support.AbstractApplicationContext
|    235 | getInstance . . . . . . . . . . in com.sun.jersey.spi.spring.container.SpringComponentProviderFactory$SpringManagedComponentProvider
|    150 | getInstance                     in com.sun.jersey.server.impl.component.IoCResourceFactory$PerRequestWrapper
|    238 | getResource . . . . . . . . . . in com.sun.jersey.server.impl.application.WebApplicationContext

Mike Wood

unread,
Oct 30, 2012, 6:27:00 PM10/30/12
to swagger-sw...@googlegroups.com
I added this bit of code to see if it's a classloader issue:

      log.debug Class.forName("com.s.g.queuemanager.JobCollectionResource")

It makes me think these groovy classes are not accessible from the default grails class loader if using Class.forName().


DEBUG core.SwaggerContext  - Class not found in classLoader org.codehaus.groovy.grails.cli.support.GrailsRootLoader@6766afb3
ERROR queuemanager.JobCollectionResourceAPIListing  - Failed to load the class
Message: com.s.g.queuemanager.JobCollectionResource
    Line | Method
->>  156 | findClass                       in org.codehaus.groovy.tools.RootLoader
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    306 | loadClass                       in java.lang.ClassLoader
|    128 | loadClass . . . . . . . . . . . in org.codehaus.groovy.tools.RootLoader
|     43 | loadClass                       in org.codehaus.groovy.grails.cli.support.GrailsRootLoader
|    247 | loadClass . . . . . . . . . . . in java.lang.ClassLoader
|     51 | loadClass                       in org.codehaus.groovy.runtime.callsite.CallSiteClassLoader
|    247 | loadClass . . . . . . . . . . . in java.lang.ClassLoader
|     58 | loadClass                       in org.codehaus.groovy.reflection.ClassLoaderForClassArtifacts
|     -2 | forName0 . . . . . . . . . . .  in java.lang.Class
|    169 | forName                         in     ''
|     -1 | call . . . . . . . . . . . . .  in java_lang_Class$forName$0


I found this post http://grails.1312388.n4.nabble.com/classloading-question-td1348143.html and it talks about src classes not being accessible to libs - only the other way round. Here's a snippet:

-------
I have a java class in src/java which uses some library and pass to this library another src/java class which is dynamically loaded. 

After a small investigation I found out, that: 
1. Grails classloading mechanism looks like this GrailsClassLoader -> UrlClassLoader -> GrailsRootLoader -> AppClassloader (-> = parent) 
2. all src/java classes are loaded by UrlClassLoader, all library classes (at least using jetty, didn't check war+tomcat deployment) are loaded by GrailsRootLoader 
3. Then according to classloading rules in Java2 it makes all src/java classes invisible to libraries, because when trying to dynamically invoke from library class some src/java class, GrailsRootLoader (this classloader is used because library class was loaded by it) first asks its parent AppClassLoader and fails, then tries to load class itself looking in libraries folders and fails again. 

It seems like current archecture allows only src/java classes to use library classes and not vice versa. Does anyone know how to hack around this problem? 

-----

Maybe I'll try exploding the Swagger classes into WEB-INF/classes and see if they can use the same class loader.

tony tam

unread,
Oct 30, 2012, 8:24:08 PM10/30/12
to swagger-sw...@googlegroups.com
Hi Mike,
Gah that's a nasty side effect of Grails.  We can easily run this scenario with Class.forName().

Is this a single-tenant application or do you really need webapp isolation?  You can put the swagger libs in your ./lib folder to see if the parent loader can pick them up.

Post back with any updates and I'll see if I can help

Mike Wood

unread,
Oct 31, 2012, 1:47:46 PM10/31/12
to swagger-sw...@googlegroups.com
The application ultimately gets deployed as a war to AWS so does need to operate as a war. Right now I am putting the swagger libs in ./lib of the main grails folder. grails makes it hard to see where things really go if you are doing run-app. A 'grails war' operation at least builds things as expected. I just tested this and pushed the war to tomcat 6 and It actually also works this way too. I guess the classloader hierarchy is a little different there than 'run-app' on my Mac with IntelliJ IDEA et.al. Not the best for debugging since a war build and push is required but workable. 

I did also try expanding the classes in the swagger-core lib but there's no real place to put that stuff in a grails app. I also took a look a the source for SwaggerContext but I am not a scala person so I'll leave this for the moment! I was going to override the loadClass with some checks for grails and use the grails workarounds for getting the parent class loader[s].

Mike.

Scott Ryan

unread,
Nov 8, 2012, 5:00:33 PM11/8/12
to swagger-sw...@googlegroups.com
This was extremely helpful and I was able to get it running as you described.  I am using jersey via the jaxrs plugin.  I had to place 3 jars in my libs directory. 

scala-library
swagger-core
swagger-jaxrs

I did get one of my resources to display its documentation however I cannot locate the resources.json file.  I have the following setup

http://localhost:8080/app

My resource definition in jaxrs is

/api/*

My services are accessed at

http://localhost:8080/app/api/1.0.0/servicename

the docs are at

http://localhost:8080/app/api/1.0.0/servicenamedoc

I have configured my swagger base URL as http://localhost:8080/app/api

I cannot locate the resources display page.  I tried the following

http://localhost:8080/app/resources.json
http://localhost:8080/app/api/resources.json
http://localhost:8080/app/api/1.0.0/resources.json


Any Ideas

Scott Ryan

tony tam

unread,
Nov 19, 2012, 9:03:15 PM11/19/12
to swagger-sw...@googlegroups.com
Hi Mike, just making sure you were able to get this working with the 1.2.0-SNAPSHOT of swagger-jaxrs.

Tony

tony tam

unread,
Nov 30, 2012, 12:32:41 AM11/30/12
to swagger-sw...@googlegroups.com
I've posted a grails sample app in swagger-core:


Feedback welcome!
Reply all
Reply to author
Forward
0 new messages