Skip to content

Grails 3.0.12: Missing layout in view throws NullPointerException #9600

@mhuebner

Description

@mhuebner

This problem exists since Grails 3.0.12 and apparently only if an ErrorController is used for handling error logic.

I attached a project which reproduces this issue.

Just run it and request

http://localhost:8080/not-mapped-uri

This results in a

ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[grailsDispatcherServlet] - Servlet.service() for servlet grailsDispatcherServlet threw exception
java.lang.NullPointerException: null
    at org.grails.web.sitemesh.GroovyPageLayoutFinder$LayoutCacheKey.hashCode(GroovyPageLayoutFinder.java:262) ~[grails-web-sitemesh-3.0.12.jar:3.0.12]
    at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936) ~[na:1.8.0_45]
    at org.grails.web.sitemesh.GroovyPageLayoutFinder.findLayout(GroovyPageLayoutFinder.java:127) ~[grails-web-sitemesh-3.0.12.jar:3.0.12]
    at org.grails.web.sitemesh.GroovyPageLayoutFinder.findLayout(GroovyPageLayoutFinder.java:94) ~[grails-web-sitemesh-3.0.12.jar:3.0.12]
    at org.grails.web.sitemesh.GrailsLayoutView.renderTemplate(GrailsLayoutView.java:65) ~[grails-web-sitemesh-3.0.12.jar:3.0.12]
    at org.grails.web.servlet.view.AbstractGrailsView.renderWithinGrailsWebRequest(AbstractGrailsView.java:71) ~[grails-web-common-3.0.12.jar:3.0.12]
    at org.grails.web.servlet.view.AbstractGrailsView.renderMergedOutputModel(AbstractGrailsView.java:55) ~[grails-web-common-3.0.12.jar:3.0.12]
    at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:303) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1244) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1027) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:971) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:622) ~[tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) ~[tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) ~[tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) ~[tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:720) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:468) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:391) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:318) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:439) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:305) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:182) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_45]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_45]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_45]
ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost] - Exception Processing ErrorPage[errorCode=404, location=/error]
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:979) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:622) ~[tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) ~[tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) ~[tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) ~[tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:720) ~[tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:468) ~[tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:391) ~[tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:318) ~[tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:439) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:305) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:182) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_45]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_45]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.0.28.jar:8.0.28]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_45]
Caused by: java.lang.NullPointerException: null
    at org.grails.web.sitemesh.GroovyPageLayoutFinder$LayoutCacheKey.hashCode(GroovyPageLayoutFinder.java:262) ~[grails-web-sitemesh-3.0.12.jar:3.0.12]
    at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936) ~[na:1.8.0_45]
    at org.grails.web.sitemesh.GroovyPageLayoutFinder.findLayout(GroovyPageLayoutFinder.java:127) ~[grails-web-sitemesh-3.0.12.jar:3.0.12]
    at org.grails.web.sitemesh.GroovyPageLayoutFinder.findLayout(GroovyPageLayoutFinder.java:94) ~[grails-web-sitemesh-3.0.12.jar:3.0.12]
    at org.grails.web.sitemesh.GrailsLayoutView.renderTemplate(GrailsLayoutView.java:65) ~[grails-web-sitemesh-3.0.12.jar:3.0.12]
    at org.grails.web.servlet.view.AbstractGrailsView.renderWithinGrailsWebRequest(AbstractGrailsView.java:71) ~[grails-web-common-3.0.12.jar:3.0.12]
    at org.grails.web.servlet.view.AbstractGrailsView.renderMergedOutputModel(AbstractGrailsView.java:55) ~[grails-web-common-3.0.12.jar:3.0.12]
    at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:303) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1244) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1027) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:971) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) ~[spring-webmvc-4.1.8.RELEASE.jar:4.1.8.RELEASE]
    ... 24 common frames omitted

The main problem here is that this happens only if in production mode OR groovyPageLayoutFinder.setCacheEnabled(true) (the latter is used to "simulate" production behaviour in Bootstrap.groovy).

The view which should be rendered via ErrorController.notfound contains a
<g:applyLayout name="main">...<g:applyLayout>

You could also remove this line completely which will end up in the same error.

BUT if you add a layout via

<meta name="layout" content="main">

the app behaves as expected.

What I can see when debugging the app is: GroovyPageLayoutFinder.findLayout just resolves page.getProperty("meta.layout").

public Decorator findLayout(HttpServletRequest request, Page page) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Evaluating layout for request: " + request.getRequestURI());
        }
        final Object layoutAttribute = request.getAttribute(LAYOUT_ATTRIBUTE);
        if (request.getAttribute(RENDERING_VIEW_ATTRIBUTE) != null || layoutAttribute != null) {
            String layoutName = layoutAttribute == null ? null : layoutAttribute.toString();

            if (layoutName == null) {
                layoutName = page.getProperty("meta.layout");
            }

            Decorator d = null;

            if (GrailsStringUtils.isBlank(layoutName)) {
                GroovyObject controller = (GroovyObject)request.getAttribute(GrailsApplicationAttributes.CONTROLLER);
                if (controller != null) {
                    String controllerName = (String)controller.getProperty(ControllerDynamicMethods.CONTROLLER_NAME_PROPERTY);
                    String actionUri = (String)controller.getProperty(ControllerDynamicMethods.ACTION_URI_PROPERTY);

                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Found controller in request, location layout for controller [" + controllerName
                                + "] and action [" + actionUri + "]");
                    }

                    LayoutCacheKey cacheKey = null;
                    boolean cachedIsNull = false;

                    if (cacheEnabled) {
                        cacheKey = new LayoutCacheKey(controllerName, actionUri);
                        DecoratorCacheValue cacheValue = layoutDecoratorCache.get(cacheKey);
                        if (cacheValue != null && (!gspReloadEnabled || !cacheValue.isExpired())) {
                            d = cacheValue.getDecorator();
                            if (d == null) {
                                cachedIsNull = true;
                            }
                        }
                    }

                    if (d == null && !cachedIsNull) {
                        d = resolveDecorator(request, controller, controllerName, actionUri);
                        if (cacheEnabled) {
                            layoutDecoratorCache.put(cacheKey, new DecoratorCacheValue(d));
                        }
                    }
                }
                else {
                    d = getApplicationDefaultDecorator(request);
                }
            }
            else {
                d = getNamedDecorator(request, layoutName);
            }

            if (d != null) {
                return d;
            }
        }
        return null;
    }

...and controllerName is also null.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions