Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Template not found" when WAR deployed in Tomcat #140

Open
rmorrise opened this issue Oct 6, 2017 · 17 comments
Open

"Template not found" when WAR deployed in Tomcat #140

rmorrise opened this issue Oct 6, 2017 · 17 comments

Comments

@rmorrise
Copy link

rmorrise commented Oct 6, 2017

Symptoms:
When a GSON view references a template in another subfolder, the view renders correctly using grails run-app, but fails with a 500 error when the assembled war is deployed to Tomcat:

Steps to reproduce:

  1. Clone the demo project: https://github.com/rmorrise/grails-gson-not-found
  2. grails run-app
  3. Verify that the test JSON is rendered correctly:
    {"message":{"value":"SHOULD BE UPPERCASE"}}
  4. gradlew assemble
  5. Deploy the WAR to Tomcat (9.0.1 used in my test)
  6. Visit the context root in the web browser

Expected behavior:

The test JSON should be rendered:
{"message":{"value":"SHOULD BE UPPERCASE"}}

The resolution of the template path should be consistent, whether using run-app or running from the WAR.

Ideally, the target template should always be resolved relative to the root of the /views folder.

Actual behavior:

The JSON is partially rendered, but displays an internal server error.

{{"message":"Internal server error","error":500}

Stack trace:

Template not found for name message/message. Stacktrace follows:

java.lang.reflect.InvocationTargetException: null
	at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
	at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
	at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
	at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
	at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
	at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: grails.views.ViewRenderException: Error rendering view: Error rendering view: Template not found for name message/message
	at grails.views.AbstractWritableScript.writeTo(AbstractWritableScript.groovy:43)
	at grails.views.mvc.GenericGroovyTemplateView.renderMergedOutputModel(GenericGroovyTemplateView.groovy:73)
	at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:303)
	at grails.views.mvc.renderer.DefaultViewRenderer.render(DefaultViewRenderer.groovy:111)
	at grails.artefact.controller.RestResponder$Trait$Helper.internalRespond(RestResponder.groovy:188)
	at grails.artefact.controller.RestResponder$Trait$Helper.respond(RestResponder.groovy:98)
	at grails.gson.not.found.TestController.show(TestController.groovy:8)
	... 14 common frames omitted
Caused by: grails.views.ViewRenderException: Error rendering view: Template not found for name message/message
	at grails.views.AbstractWritableScript.writeTo(AbstractWritableScript.groovy:43)
	at grails.plugin.json.view.api.internal.DefaultGrailsJsonViewHelper$6.writeTo(DefaultGrailsJsonViewHelper.groovy:786)
	at grails.plugin.json.view.JsonViewWritableScript.json(JsonViewWritableScript.groovy:119)
	at grails.plugin.json.view.JsonViewWritableScript.json(JsonViewWritableScript.groovy:142)
	at grails_gson_not_found_test_show_gson.run(grails_gson_not_found_test_show_gson:7)
	at grails.plugin.json.view.JsonViewWritableScript.doWrite(JsonViewWritableScript.groovy:28)
	at grails.views.AbstractWritableScript.writeTo(AbstractWritableScript.groovy:40)
	... 20 common frames omitted
Caused by: grails.views.ViewException: Template not found for name message/message
	at grails.plugin.json.view.api.internal.DefaultGrailsJsonViewHelper.render(DefaultGrailsJsonViewHelper.groovy:793)
	at grails.plugin.json.view.api.internal.TemplateRenderer.invokeMethod(TemplateRenderer.groovy:44)
	at grails.plugin.json.builder.StreamingJsonBuilder$StreamingJsonDelegate.cloneDelegateAndGetContent(StreamingJsonBuilder.java:793)
	at grails.plugin.json.builder.StreamingJsonBuilder$StreamingJsonDelegate.access$000(StreamingJsonBuilder.java:478)
	at grails.plugin.json.builder.StreamingJsonBuilder.call(StreamingJsonBuilder.java:238)
	at grails.plugin.json.view.JsonViewWritableScript.json(JsonViewWritableScript.groovy:69)
	at grails_gson_not_found_test__test_gson.run(grails_gson_not_found_test__test_gson:7)
	at grails.plugin.json.view.JsonViewWritableScript.doWrite(JsonViewWritableScript.groovy:28)
	at grails.views.AbstractWritableScript.writeTo(AbstractWritableScript.groovy:40)
	... 26 common frames omitted
@rmorrise rmorrise changed the title tmpl.'...' fails to load the same template when deployed in Tomcat "Template not found" when WAR deployed in Tomcat Oct 6, 2017
@jameskleeh
Copy link
Collaborator

jameskleeh commented Oct 6, 2017

I can't reproduce the issue with your sample app. I deployed it both to a standalone tomcat instance and via java -jar. Tomcat 8.5.13

@jameskleeh
Copy link
Collaborator

Also tested on Tomcat 9.0.1 and it worked

@rmorrise
Copy link
Author

rmorrise commented Oct 6, 2017

OK. I was able to rep it consistently using the steps above (copying the war created by gradlew assemble from build/libs/, renaming to ROOT.war, and dropping it in webapps.) I am on win7 x64, with Tomcat installed as a windows service.

I will try to dig into it with the remote debugger and look for clues to what is going on.

Any ideas about what I can do to help reproduce it are welcome.

Here is my environment info:
Tomcat 9.0.1.0
win7 x64
JVM Version: 1.8.0_121-b13
grails 3.3.0

@rmorrise
Copy link
Author

rmorrise commented Oct 6, 2017

I noticed this code in ResolvableGroovyTemplateEngine, which seems relevant. Still in the process of debugging.

if (Environment.isDevelopmentEnvironmentAvailable()) {
            template = attemptResolvePath(path)
            if (template == null) {
                template = attemptResolveClass(path)
            }
        } else {
            template = attemptResolveClass(path)
            if (template == null) {
                template = attemptResolvePath(path)
            }
        }

@jameskleeh
Copy link
Collaborator

@rmorrise That shouldn't have any impact, as it attempts the other if the first didn't work

@rmorrise
Copy link
Author

rmorrise commented Oct 6, 2017

In my environment, when the app is run from Tomcat, the baseDir property on the GenericGroovyTemplateResolver is getting set to:

.\grails-app\views

This path is not correct, since the JVM's current working directory is the Tomcat home directory. Grails is looking for the template at the following absolute path:

D:\Program Files\Apache Software Foundation\Tomcat 9.0_Tomcat9-QnA\.\grails-app\views\test\..\message\_message.gson

I am not sure what's different about your setup compared to mine, but I also see this issue on our RHEL web server, so I don't think it's a Windows-specific issue.

@rmorrise
Copy link
Author

rmorrise commented Oct 6, 2017

I am debugging the GenericGroovyTemplateResolver.resolveTemplate() method

@rmorrise
Copy link
Author

rmorrise commented Oct 6, 2017

I tried to work around this by setting the base dir:

-Dbase.dir=D:\Program Files\Apache Software Foundation\Tomcat 9.0_Tomcat9-QnA\webapps\ROOT

I verified that the setting was applied using the debugger, but it didn't fix the issue. I think it's because the structure of the exploded WAR does not match the view folder structure in the source code. All of the views are flattened out into WEB-INF/classes.

I'm guessing this is why class-based resolution is tried first in non-dev environments... path-based resolution does not work anymore.

@rmorrise
Copy link
Author

rmorrise commented Oct 6, 2017

It seems that when the app goes to non-dev mode, it switches from path-based resolution to class-based resolution. The class-based resolution's behavior does not match the path-based resolution's behavior in the case of a relative path that backs up with ".." like the one given in the sample project.

Here is the actual class compiled into the WAR:
grails_gson_not_found_message__message_gson.class

Here is the path the class-based resolver is looking for:
grails_gson_not_found_test____message__message_gson

@rmorrise
Copy link
Author

rmorrise commented Oct 6, 2017

@jameskleeh When you get the time, can you check in your debugger and see where your app's behavior is diverging from mine? Can you verify the paths given above?

The path it is trying to load is:
/test/../message/_message.gson

A good place to put a breakpoint is:
GenericGroovyTemplateResolver#resolveTemplateName(), ln 67 - first line of this method.
Breakpoint condition: scope == "grails-gson-not-found"

@rmorrise
Copy link
Author

rmorrise commented Oct 6, 2017

I think this problem can be fixed by calling:

java.nio.file.Paths.get(path).normalize().toString()

...somewhere in the process of template resolution. I'm not sure what the most appropriate place is. It could be done at the beginning of ResolvableGroovyTemplateEngine.resolveTemplate() method, for example.

@rmorrise
Copy link
Author

rmorrise commented Oct 6, 2017

If using NIO is not desirable, java.net.URI can also be used:
new URI(path).normalize().getPath()

It seems to me like the only workaround right now is to eliminate the call to tmpl.'...' and duplicate the rendering code in the target template.

@jameskleeh
Copy link
Collaborator

@rmorrise Does using absolute paths in tmpl.'...' work for you?

@rmorrise
Copy link
Author

rmorrise commented Oct 6, 2017

@jameskleeh Aha! Yes, it does. I am able to get the desired behavior by changing the client code in the view to:
tmpl.'/message/message'
Thank you!

This fixes my actual issue in the app. I still think adding the normalize() would be a good idea.

@sbglasius
Copy link

We are seeing something similar with .gsp files, but this is more random. Actually so random, that two nodes running the same war shows different behavior. It's hard to even make reproducible project. It happens with <g:render template="..."/> and often happens with relative paths like <g:render template="b/c"> where c is in found here: views/a/b/_c.gsp and often with nested templates

@ameltonyan
Copy link

ameltonyan commented Oct 15, 2019

Hello, I recently encountered this problem when calling the templateEngine.resolveTemplate(...) method from JsonViewTemplateEngine (go here for more details http://views.grails.org/latest/#_the_jsontemplateengine), ex.

@Autowired
JsonViewTemplateEngine templateEngine

void myMethod() {
        Template t = templateEngine.resolveTemplate('/book/show')
        def writable = t.make(book: new Book(title:"The Stand"))
        def sw = new StringWriter()
        writable.writeTo(sw)
        ...
}

As a solution, I override the json view template path url to solve the problem.

I found two options for overriding urls:

  1. In application.groovy, you can override the path with the following key:
    grails.views.json.templatePath = ... // your path

Or

  1. In resources.groovy you can override the json view configuration like this:
beans = {
    
    jsonViewConfiguration(JsonViewConfiguration) {
        templatePath = ...  // your path
    }

}

I chose the second option and to resolve the path I read it from the classpath, something like this:

templatePath = Paths.get(new ClassPathResource("book/").getURI()).getParent().toString()

And that solved the problem.

@luismunizsparkers
Copy link

I just encountered the same issue in grails 4.1.4, and the workaround to use absolute paths to resolve templates does wotk.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants