grails / grails-core

The Grails Web Application Framework
http://grails.org
Apache License 2.0
2.78k stars 949 forks source link

GRAILS-2484: Struts 1 app will not run under Grails 1.0 via "run-app", works via WAR #6088

Closed graemerocher closed 9 years ago

graemerocher commented 16 years ago

Original Reporter: interole Environment: Grails 1.0 zip distro, likely affects 1.0.1, running on Windows XP, likely on other operating systems as well Version: 1.0 Migrated From: http://jira.grails.org/browse/GRAILS-2484

This error occurred in when trying to bring our struts1 legacy application into the grails environment.

We came up with a hack fix, however to give the Grails team something that is repeatable to work with, we distilled it down to a repeatable example using a simple struts 1 example application that comes with the struts 1 distribution. The example will not work in a run-app situation, it will work in a WAR under tomcat 5.5.x.

This appears due to fact that struts1 uses org.apache.commons.chain.CatalogFactory which stores Catalog's keyed by ClassLoader. Because "run-app" seems to use several ClassLoaders in a heirachy to bootstrap the application, the struts1 chain catalog is keyed by the GrailsClassLoader but when a JSP page or struts action is called the thread's context class loader is "URLClassloader". Due to this when a struts action invokes a call to CatalogFactory.getInstance() the lookup in that method cannot find the "struts" catalog which is keyed under the GrailsClassLoader and not the URLClassLoader.

Steps to reproduce.

a) download the Struts 1.3.8 source distribution

b) create new blank grails 1.0 project called "struts1test" in your location (where you keep your grails projects)

c) go to struts-1.3.8/apps and extract the struts-cookbook-1.3.8.war file into /struts1test/web-app

d) do a "grails install-templates"

e) open for edit the "\struts1test\src\templates\war\web.xml" template file and merge in the /struts1test/web-app/WEB-INF/web.xml struts1 entries that came out of the struts cookbook example war.

f) delete the "classes" under struts1test/web-app/WEB-INF/classes

g) MOVE the "struts1test/web-app/WEB-INF/lib" jar files to /struts1test/lib

h) copy the java source files from the struts example located at "struts-1.3.8\src\apps\cookbook\src\main\java" to /struts1test/src/java

i) delete the /struts1test/web-app/index.gsp" (we want to leave the index.jsp from the example), then do a grails compile

j) do a grails run-app and go to http://localhost:8080/struts1test, you should see the example index page

k) click on one of the "execute" links and you will get the following exception

javax.servlet.ServletException: Cannot find catalog 'struts' at org.apache.struts.chain.ComposableRequestProcessor.init(ComposableRequestProcessor.java:161) at org.apache.struts.action.ActionServlet.getRequestProcessor(ActionServlet.java:620) at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1910) at org.apache.struts.action.ActionServlet.doGet(ActionServlet.java:449) at javax.servlet.http.HttpServlet.service(HttpServlet.java:707) at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1098) at org.codehaus.groovy.grails.web.mapping.filter.UrlMappingsFilter.processFilterChain(UrlMappingsFilter.java:156) at org.codehaus.groovy.grails.web.mapping.filter.UrlMappingsFilter.doFilterInternal(UrlMappingsFilter.java:73) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:75) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1089) at com.opensymphony.module.sitemesh.filter.PageFilter.parsePage(PageFilter.java:119) at org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter.doFilter(GrailsPageFilter.java:75) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1089) at org.codehaus.groovy.grails.web.servlet.filter.GrailsReloadServletFilter.doFilterInternal(GrailsReloadServletFilter.java:98) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:75) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1089) at org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:68) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:75) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1089) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:96) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:75) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:183) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:138) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1089) at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:365) at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216) at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181) at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:712) at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405) at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:139) at org.mortbay.jetty.Server.handle(Server.java:295) at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:503) at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:827) at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:511) at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:210) at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:379) at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:361) at org.mortbay.thread.BoundedThreadPool$PoolThread.run(BoundedThreadPool.java:442)


OUR FIX FOR THIS

See attached zip containing the modified CatalogFactory.java and a commons-chain-1.1.jar file with the compiled "fix" class in it.

To fix this to get the above cookbook example application to run AND our legacy application to run in grails, we had to download the commons-chain-1.1 source, and modify the org.apache.commons.chain.CatalogFactory.getInstance() method (see below outtake). We then re-compile and replace the standard commons-chain JAR with our custom jar in our grails project.

I attached a zip with the source file and a working replacement commons-chain-1.1. jar. However the short of it is that if a call to getInstance() cannot find the struts catalog via the current thread's context class loader, we scan the factories map and see if there is one keyed off of an instance of GrailsClassLoader, if it is we return that one and our app and the cookbook example will work.

Regardless, Struts 1 is pretty common out there. Maybe something can be changed in Grails so that the run-app behavior is the same as a production servlet container with regards to classloaders and other resource locations etc? See http://jira.codehaus.org/browse/GRAILS-2196 with regards to a similar issue where calls to /WEB-INF/web.xml cannot be found during run-app and the container behavior is different from "run-app" vs. a "war" deployment.


OUTAKE OF FIX IN CATALOG FACTORY

public static CatalogFactory getInstance() {

    System.out.println("CatalogFactory.getInstance() called from classloader: " + getClassLoader());
    CatalogFactory factory = null;
    ClassLoader cl = getClassLoader();
    synchronized (factories) {
        factory = (CatalogFactory) factories.get(cl);

        if (factory == null) {
            System.out.println("CatalogFactory.getInstance() FACTORY NOT FOUND! attempting to find one Keyed by grails");

            java.util.Iterator i = factories.keySet().iterator();
            while(i.hasNext()) {
                ClassLoader tmpCl = (ClassLoader)i.next();
                if (tmpCl.getClass().getName().equals("org.codehaus.groovy.grails.compiler.GrailsClassLoader")) {
                    System.out.println("CatalogFactory.getInstance() Found factory stored under GrailsClassLoader, storing and returning");
                    factory = (CatalogFactory)factories.get(tmpCl);
                    factories.put(cl,factory);
                    return factory;
                }

            }

            factory = new CatalogFactoryBase();
            factories.put(cl, factory);
        }
    }
    return factory;

}

private static ClassLoader getClassLoader() {

    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    if (cl == null) {
        cl = CatalogFactory.class.getClassLoader();
    }
    return cl;

}
graemerocher commented 16 years ago

graemerocher said: fixed by struts 1 plugin (http://grails.org/Struts+1+Plugin)