Regression with classloading in the Jetty plugin with Gradle 1.0-milestone-6?

Thanks. I will take a look at this.

This is now GRADLE-1960.

Thanks Adam for looking into it, I created a JIRA account for the Gradle issue tracker and will provide whatever additional information I can to help resolve this.

Looks like spring-orm doesn’t handle the case where the resources and classes of the application are loaded from separate directories (from Gradle 1.0-milestone-4 onwards, we generate the resources and classes to separate output directories).

A workaround:

sourceSets.main.output.resourcesDir = sourceSets.main.output.classesDir

Make sure you do a clean after making this change.

Thanks Adam,

Sorry you ended up debugging a problem that isn’t actually related to Gradle, I had only assumed it might be a regression because it manifested itself when I moved to the M6 release. Your workaround does allow the web application to initialize and bootstrap through Spring, all of my entities are resolved successfully.

The only remaining problem I have is that some of the dependencies are not being added to the classpath with jettyRun. When I build a web archive (‘gradle war’) I get the following collection added:

WEB-INF/lib/spring-webmvc-3.0.6.RELEASE.jar
WEB-INF/lib/spring-orm-3.0.6.RELEASE.jar
WEB-INF/lib/spring-data-jpa-1.0.1.RELEASE.jar
WEB-INF/lib/spring-security-core-3.0.6.RELEASE.jar
WEB-INF/lib/spring-security-config-3.0.6.RELEASE.jar
WEB-INF/lib/spring-security-web-3.0.6.RELEASE.jar
WEB-INF/lib/javax.servlet-api-3.0.1.jar
WEB-INF/lib/javax.inject-1.jar
WEB-INF/lib/hibernate-jpa-2.0-api-1.0.1.Final.jar
WEB-INF/lib/hibernate-entitymanager-3.6.8.Final.jar
WEB-INF/lib/logback-classic-0.9.30.jar
WEB-INF/lib/thymeleaf-spring3-1.1.2.jar
WEB-INF/lib/jackson-mapper-asl-1.9.2.jar
WEB-INF/lib/bonecp-0.7.1.RELEASE.jar
WEB-INF/lib/hsqldb-2.2.6.jar
WEB-INF/lib/spring-asm-3.0.6.RELEASE.jar
WEB-INF/lib/spring-core-3.0.6.RELEASE.jar
WEB-INF/lib/spring-beans-3.0.6.RELEASE.jar
WEB-INF/lib/spring-expression-3.0.6.RELEASE.jar
WEB-INF/lib/aopalliance-1.0.jar
WEB-INF/lib/spring-aop-3.0.6.RELEASE.jar
WEB-INF/lib/spring-context-3.0.6.RELEASE.jar
WEB-INF/lib/spring-context-support-3.0.6.RELEASE.jar
WEB-INF/lib/spring-web-3.0.6.RELEASE.jar
WEB-INF/lib/spring-tx-3.0.6.RELEASE.jar
WEB-INF/lib/spring-jdbc-3.0.6.RELEASE.jar
WEB-INF/lib/spring-data-commons-core-1.1.0.RELEASE.jar
WEB-INF/lib/slf4j-api-1.6.2.jar
WEB-INF/lib/jcl-over-slf4j-1.6.1.jar
WEB-INF/lib/aspectjrt-1.6.8.jar
WEB-INF/lib/aspectjweaver-1.6.8.jar
WEB-INF/lib/antlr-2.7.6.jar
WEB-INF/lib/commons-collections-3.1.jar
WEB-INF/lib/dom4j-1.6.1.jar
WEB-INF/lib/hibernate-commons-annotations-3.2.0.Final.jar
WEB-INF/lib/jta-1.1.jar
WEB-INF/lib/hibernate-core-3.6.8.Final.jar
WEB-INF/lib/asm-3.1.jar
WEB-INF/lib/cglib-2.2.jar
WEB-INF/lib/javassist-3.12.0.GA.jar
WEB-INF/lib/logback-core-0.9.30.jar
WEB-INF/lib/ognl-3.0.jar
WEB-INF/lib/javassist-3.14.0-GA.jar
WEB-INF/lib/thymeleaf-1.1.2.jar
WEB-INF/lib/jackson-core-asl-1.9.2.jar
WEB-INF/lib/guava-r08.jar

The war package works perfectly using jetty-runner. When I run the project with jettyRun and jettyRunWar I get exceptions when viewing a page about:

java.lang.NoClassDefFoundError: org/apache/xerces/parsers/DOMParser
 at java.lang.ClassLoader.defineClass1(Native Method)
 at java.lang.ClassLoader.defineClass(ClassLoader.java:634)
 at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
 at java.net.URLClassLoader.defineClass(URLClassLoader.java:277)
 at java.net.URLClassLoader.access$000(URLClassLoader.java:73)
 at java.net.URLClassLoader$1.run(URLClassLoader.java:212)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:205)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:321)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:266)
 at org.gradle.util.MultiParentClassLoader.loadClass(MultiParentClassLoader.java:46)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:314)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:314)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:266)
 at org.mortbay.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:401)
 at org.mortbay.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:363)

Why would I get exceptions running through the Gradle Jetty tasks but not directly with a web server like Jetty?

After some more digging it seems that the root of the problem is in the templating engine I’m using (Thymeleaf). On line 167 there’s a method called ‘computeIsNekoInClassPath()’:

http://thymeleaf.svn.sourceforge.net/viewvc/thymeleaf/trunk/thymeleaf/src/main/java/org/thymeleaf/TemplateParser.java?revision=840&view=markup

It uses the method call, ‘Thread.currentThread().getContextClassLoader().loadClass(“org.cyberneko.html.parsers.DOMParser”)’ to check if NekoHTML is on the classpath (which in turn depends on Xerces). According to the stacktrace this uses ‘org.gradle.util.MultiParentClassLoader.loadClass(MultiParentClassLoader.java:46)’ which I believe causes Thymeleaf to think the dependency is on the classpath when it’s not.

Does Gradle use Xerces? If so, is there some sort of Classloader leakage going on that causes Thymeleaf to think that the dependency is available?

I have created another minimal project example that shows the reproducible classloading bug that occurs using Gradle M6 but works fine in M3.

https://dl.dropbox.com/s/2kmvxxgv9w8vrtd/thymeleaf-project.zip

I think it’s definitely a problem with: ‘org.gradle.util.MultiParentClassLoader.loadClass’ classloading and the method call ‘Thread.currentThread().getContextClassLoader().loadClass(“org.cyberneko.html.parsers.DOMParser”)’ which returns ‘true’ when it should be returning ‘false’.

Hope this makes it easy to test. A workaround for this would be great, thanks for all the help so far.

According to the Spring folks, Spring only acts as a mediator here. The JPA spec demands that everything is resolvable from a persistence unit root URL. Spring scans the class path for META-INF/persistence.xml and passes the corresponding resource URL to the JPA persistence provider. In our case this would be build/resources, which explains why the entity classes aren’t found.

There are other ways to bootstrap the persistence unit that might not have this limitation (e.g. Spring’s packagesToScan). For standard JPA boot strapping via persistence.xml it may not be possible to come up with a solution that supports separate classes and resources directories though.

Since this is an important use case (and there might be other similar ones), I think we should adapt to the situation. I can think of two possible solutions:

  1. Always put a Jar on the test class path (instead of directories) 2. Put everything into build/classes again

IMHO I think that returning to putting everything in the build/classes folder again is a good idea.

Originally this problem looked like one issue, the entities not being resolved at runtime with the jettyRun task. After implementing the workaround suggested by Adam I encountered another problem with the Gradle 1.0M6 release. I’ve broken that issue off into another forum post:

http://forums.gradle.org/gradle/topics/gradle_classloader_problems_with_jetty_plugin

So be aware that some of this conversation crosses the streams (http://www.youtube.com/watch?v=jyaLZHiJJnE) :wink:

I am having a very similar problem of JPA not an entity issue but not related to Jetty task but related to running my JUnit tests on code that uses spring-data-jpa.

This all works fine in Maven 3 but in Gradle 1.0 milestone 7 it fails.

Currently this is a show stopper for us :frowning:

This bug is pretty high on the list for us to fix. In the meantime, did you see the workaround above?

I did the below, and the JPA test now work so having the resources in the same folder as classes solves the problem but now when I build the jar I get all the resources twice how can I stop this? Sorry newby just started with Gradle tonight

sourceSets {

main {

output.resourcesDir = “build/classes/main”

}

test{

output.resourcesDir = “build/classes/test”

} }

Do you mean what I have done below?