Tiles
  1. Tiles
  2. TILES-544

Stack overflow due to rendering loop when including a resource resulting in another tiles view.

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Major Major
    • Resolution: Invalid
    • Affects Version/s: 2.2.2
    • Fix Version/s: None
    • Component/s: None
    • Labels:
      None
    • Environment:

      Spring MVC 3.1.1

      Description

      I get a stackoverflow because of a rendering loop when including an call to a controller with a view is also a tile. This is a dup of TILES-418, but since that one was closed and I couldn't attach my stack trace, I'll open this new one.

      To me this is very critical. I just upgraded an application from Spring 2.0.7 and old struts-tiles to Spring 3.1.1 and tiles 2.2.2 and the structure worked with struts-tiles. It's kind of a homebrew portlet thingy where we iterate over url:s pointing to controllers given by a cms.

      The included controller is called correctly and returns a model and view where the view points to a tile definition. But when Spring gives over the rendering to tiles, the loop begins.

      It doesn't matter if I include the url with:

      <tiles:insertTemplate name="$

      {entry.url}

      " />
      or
      <c:import url="$

      {entry.url }

      " />

      the same error occurs.

        Activity

        Hide
        Nicolas Le Bas added a comment -

        Setting the status as "invalid" since the solution involves changing the code in spring, not in tiles.

        Show
        Nicolas Le Bas added a comment - Setting the status as "invalid" since the solution involves changing the code in spring, not in tiles.
        Hide
        Nicolas Le Bas added a comment -

        it really seems to be upside down?

        Yes, it seems so when you look at it from this problem's point of view. But actually the AttributeContext is something that aggregates all attributes values from the definition, the overriden values in the <insert*> tag, and the cascaded attributes from previously processed definitions. So it's in the right order.

        I got the following error [...] It's from the container.endContext(context) call.

        try:

        container.endContext(request, response)

        You just use the same arguments as startContext. The are some examples here.

        Actually what I had in mind is:

        protected void renderMergedOutputModel(
        			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        	ServletContext servletContext = getServletContext();
        	TilesContainer container = ServletUtil.getContainer(servletContext);
        	container.startContext(request, response);
        	try {
        		super.renderMergedOutputModel(model, request, response);
        	} finally {
        		container.endContext(request, response);
        	}
        }
        
        Show
        Nicolas Le Bas added a comment - it really seems to be upside down? Yes, it seems so when you look at it from this problem's point of view. But actually the AttributeContext is something that aggregates all attributes values from the definition, the overriden values in the <insert*> tag, and the cascaded attributes from previously processed definitions. So it's in the right order. I got the following error [...] It's from the container.endContext(context) call. try: container.endContext(request, response) You just use the same arguments as startContext . The are some examples here . Actually what I had in mind is: protected void renderMergedOutputModel( Map< String , Object > model, HttpServletRequest request, HttpServletResponse response) throws Exception { ServletContext servletContext = getServletContext(); TilesContainer container = ServletUtil.getContainer(servletContext); container.startContext(request, response); try { super .renderMergedOutputModel(model, request, response); } finally { container.endContext(request, response); } }
        Hide
        Viktor Hedefalk added a comment -

        I have a org.apache.tiles.context.ChainedTilesRequestContextFactory@110643ed

        with two factories inside:

        [org.apache.tiles.servlet.context.ServletTilesRequestContextFactory@4f1bd595, org.apache.tiles.jsp.context.JspTilesRequestContextFactory@2c6351c6]

        The first wants a req and resp pair:

        public TilesRequestContext createRequestContext(TilesApplicationContext context,
                                                            Object... requestItems) {
                if (requestItems.length == 2
                        && requestItems[0] instanceof HttpServletRequest
                        && requestItems[1] instanceof HttpServletResponse) {
                    return new ServletTilesRequestContext(context,
                        (HttpServletRequest) requestItems[0],
                        (HttpServletResponse) requestItems[1]);
                }
        
                return null;
            }
        

        and the other wants a PageContext:

         public TilesRequestContext createRequestContext(
                    TilesApplicationContext context, Object... requestItems) {
                if (requestItems.length == 1 && requestItems[0] instanceof PageContext) {
                    PageContext pageContext = (PageContext) requestItems[0];
                    ServletRequest request = pageContext.getRequest();
                    ServletResponse response = pageContext.getResponse();
                    TilesRequestContext enclosedRequest;
                    if (parent != null) {
                        enclosedRequest = parent.createRequestContext(context, request,
                                response);
                    } else {
                        enclosedRequest = new ServletTilesRequestContext(context,
                                (HttpServletRequest) request,
                                (HttpServletResponse) response);
                    }
                    return new JspTilesRequestContext(enclosedRequest, pageContext);
                }
        
                return null;
            }
        

        None match my [org.apache.tiles.BasicAttributeContext@0] that I got from container.startContext(request, response);

        Any help highly appreciated!

        Thanks,
        Viktor

        Show
        Viktor Hedefalk added a comment - I have a org.apache.tiles.context.ChainedTilesRequestContextFactory@110643ed with two factories inside: [org.apache.tiles.servlet.context.ServletTilesRequestContextFactory@4f1bd595, org.apache.tiles.jsp.context.JspTilesRequestContextFactory@2c6351c6] The first wants a req and resp pair: public TilesRequestContext createRequestContext(TilesApplicationContext context, Object ... requestItems) { if (requestItems.length == 2 && requestItems[0] instanceof HttpServletRequest && requestItems[1] instanceof HttpServletResponse) { return new ServletTilesRequestContext(context, (HttpServletRequest) requestItems[0], (HttpServletResponse) requestItems[1]); } return null ; } and the other wants a PageContext: public TilesRequestContext createRequestContext( TilesApplicationContext context, Object ... requestItems) { if (requestItems.length == 1 && requestItems[0] instanceof PageContext) { PageContext pageContext = (PageContext) requestItems[0]; ServletRequest request = pageContext.getRequest(); ServletResponse response = pageContext.getResponse(); TilesRequestContext enclosedRequest; if (parent != null ) { enclosedRequest = parent.createRequestContext(context, request, response); } else { enclosedRequest = new ServletTilesRequestContext(context, (HttpServletRequest) request, (HttpServletResponse) response); } return new JspTilesRequestContext(enclosedRequest, pageContext); } return null ; } None match my [org.apache.tiles.BasicAttributeContext@0] that I got from container.startContext(request, response); Any help highly appreciated! Thanks, Viktor
        Hide
        Viktor Hedefalk added a comment -

        I made the following change:

        AttributeContext context = container.startContext(request, response);
        
                exposeModelAsRequestAttributes(model, request);
                JstlUtils.exposeLocalizationContext(new RequestContext(request, servletContext));
        
                if (!response.isCommitted()) {
                 ...
                 }
                try {
                    container.render(getUrl(), request, response);
                } finally {
                    container.endContext(context);
                }
        

        but then I got the following error:

        ava.lang.IllegalArgumentException: Cannot find a factory to create the request context
        	at org.apache.tiles.context.ChainedTilesRequestContextFactory.createRequestContext(ChainedTilesRequestContextFactory.java:137)
        	at org.apache.tiles.impl.BasicTilesContainer.getRequestContext(BasicTilesContainer.java:541)
        	at org.apache.tiles.impl.BasicTilesContainer.endContext(BasicTilesContainer.java:170)
        	at se.sj.web.tiles.TilesView.renderMergedOutputModel(TilesView.java:110)
        	at se.sj.web.tiles.TilesView.render(TilesView.java:70)
        	at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1180)
        

        It's from the container.endContext(context) call.

        Show
        Viktor Hedefalk added a comment - I made the following change: AttributeContext context = container.startContext(request, response); exposeModelAsRequestAttributes(model, request); JstlUtils.exposeLocalizationContext( new RequestContext(request, servletContext)); if (!response.isCommitted()) { ... } try { container.render(getUrl(), request, response); } finally { container.endContext(context); } but then I got the following error: ava.lang.IllegalArgumentException: Cannot find a factory to create the request context at org.apache.tiles.context.ChainedTilesRequestContextFactory.createRequestContext(ChainedTilesRequestContextFactory.java:137) at org.apache.tiles.impl.BasicTilesContainer.getRequestContext(BasicTilesContainer.java:541) at org.apache.tiles.impl.BasicTilesContainer.endContext(BasicTilesContainer.java:170) at se.sj.web.tiles.TilesView.renderMergedOutputModel(TilesView.java:110) at se.sj.web.tiles.TilesView.render(TilesView.java:70) at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1180) It's from the container.endContext(context) call.
        Hide
        Viktor Hedefalk added a comment -

        The TilesView I have in Spring 3.1.1 doesn't really look like the code you posted. I have this method:

        protected void renderMergedOutputModel(
        			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        
        		ServletContext servletContext = getServletContext();
        		TilesContainer container = ServletUtil.getContainer(servletContext);
        		if (container == null) {
        			throw new ServletException("Tiles container is not initialized. " +
        					"Have you added a TilesConfigurer to your web application context?");
        		}
        
        		exposeModelAsRequestAttributes(model, request);
        		JstlUtils.exposeLocalizationContext(new RequestContext(request, servletContext));
        
        		if (!response.isCommitted()) {
        			// Tiles is going to use a forward, but some web containers (e.g. OC4J 10.1.3)
        			// do not properly expose the Servlet 2.4 forward request attributes... However,
        			// must not do this on Servlet 2.5 or above, mainly for GlassFish compatibility.
        			if (this.exposeForwardAttributes) {
        				try {
        					WebUtils.exposeForwardRequestAttributes(request);
        				}
        				catch (Exception ex) {
        					// Servlet container rejected to set internal attributes, e.g. on TriFork.
        					this.exposeForwardAttributes = false;
        				}
        			}
        		}
        
        		container.render(getUrl(), request, response);
        	}
        
        

        But it's ok to just surround the container.render() call with start- and endContext()?

        Thanks,
        Viktor

        Show
        Viktor Hedefalk added a comment - The TilesView I have in Spring 3.1.1 doesn't really look like the code you posted. I have this method: protected void renderMergedOutputModel( Map< String , Object > model, HttpServletRequest request, HttpServletResponse response) throws Exception { ServletContext servletContext = getServletContext(); TilesContainer container = ServletUtil.getContainer(servletContext); if (container == null ) { throw new ServletException( "Tiles container is not initialized. " + "Have you added a TilesConfigurer to your web application context?" ); } exposeModelAsRequestAttributes(model, request); JstlUtils.exposeLocalizationContext( new RequestContext(request, servletContext)); if (!response.isCommitted()) { // Tiles is going to use a forward, but some web containers (e.g. OC4J 10.1.3) // do not properly expose the Servlet 2.4 forward request attributes... However, // must not do this on Servlet 2.5 or above, mainly for GlassFish compatibility. if ( this .exposeForwardAttributes) { try { WebUtils.exposeForwardRequestAttributes(request); } catch (Exception ex) { // Servlet container rejected to set internal attributes, e.g. on TriFork. this .exposeForwardAttributes = false ; } } } container.render(getUrl(), request, response); } But it's ok to just surround the container.render() call with start- and endContext()? Thanks, Viktor
        Hide
        Viktor Hedefalk added a comment -

        Hi Nicolas,

        thanks for your reply!

        I understand that my "solution" breaks the intended purpose and is not feasible, but it at least points to the problem. I think the inheritance thing looks upside down and that your second comment seems reasonable. I tried something like that but for some reason couldn't get it to compile before. I feel like trying out your second comment, but you say that it's wrong? Reading the doc of inherit:

        /**

        • Inherits the attribute context, inheriting, i.e. copying if not present,
        • the attributes.
          */

        it really seems to be upside down?

        Your last comment seems easier though since I had to do some serious hacking of Spring to be able to override the method in BasicTilesContainer - TilesView would be a lot simpler.

        Now why is spring not doing it? Probably because you're not supposed to pass control back to the controller after you've started rendering the view; that breaks the MVC paradigm and Spring is not designed for that.

        About the MVC thing - I definitely have no philosophical standpoint on MVC, I never really connected to it in the first place But it seems a lot harder to compose MVC if I can't to that. Note that I'm not passing back control to the original controller though, but to the controllers of the included resources.

        If I couldn't do that, what would then be needed is for the surrounding controller to delegate to the other controllers directly, letting them fill out some backing bean structure that then the surrounding view could traverse and delegate to the included views. That breaks up the composition in two different places. THAT I would consider broken. Maybe there's a cleaner solution I'm missing?

        Basically I think it sounds weird that Spring is not designed for this. To use JSP:s as views must then be considered extreeeemely dangerous.

        Isn't any kind of mashup a breach of that strict idea? An <img> tag in the view pointing to a dynamically rendered image would be too, only you have different http-requests to protect you… Maybe I'm misinterpreting…

        Thanks!

        Show
        Viktor Hedefalk added a comment - Hi Nicolas, thanks for your reply! I understand that my "solution" breaks the intended purpose and is not feasible, but it at least points to the problem. I think the inheritance thing looks upside down and that your second comment seems reasonable. I tried something like that but for some reason couldn't get it to compile before. I feel like trying out your second comment, but you say that it's wrong? Reading the doc of inherit: /** Inherits the attribute context, inheriting, i.e. copying if not present, the attributes. */ it really seems to be upside down? Your last comment seems easier though since I had to do some serious hacking of Spring to be able to override the method in BasicTilesContainer - TilesView would be a lot simpler. Now why is spring not doing it? Probably because you're not supposed to pass control back to the controller after you've started rendering the view; that breaks the MVC paradigm and Spring is not designed for that. About the MVC thing - I definitely have no philosophical standpoint on MVC, I never really connected to it in the first place But it seems a lot harder to compose MVC if I can't to that. Note that I'm not passing back control to the original controller though, but to the controllers of the included resources. If I couldn't do that, what would then be needed is for the surrounding controller to delegate to the other controllers directly, letting them fill out some backing bean structure that then the surrounding view could traverse and delegate to the included views. That breaks up the composition in two different places. THAT I would consider broken. Maybe there's a cleaner solution I'm missing? Basically I think it sounds weird that Spring is not designed for this. To use JSP:s as views must then be considered extreeeemely dangerous. Isn't any kind of mashup a breach of that strict idea? An <img> tag in the view pointing to a dynamically rendered image would be too, only you have different http-requests to protect you… Maybe I'm misinterpreting… Thanks!
        Hide
        Nicolas Le Bas added a comment -

        Well, forget my previous comment, that's not it. I was hasty and naive.

        Actually, the problem lies in org.springframework.web.servlet.view.tiles2.TilesView, which is not designed for your use case and doesn't call the proper API.

        Whenever you call TilesContainer.render(definition, ...) in an "included" situation, you should wrap the call in startContext/endContext like this:

        TilesContainer container = TilesAccess.getContainer(getServletContext());
        AttributeContext context = container.startContext(request, response);
        try {
          // here you may override the definition's attributes on a 
          // per-request basis by calling 
          // context.putAttribute(name, attribute, cascade);
        
          container.render(definitionName, request, response);
        } finally {
          container.endContext(request, response);
        }
        

        You may do it in non-included situations, too, it's just optional.

        Now why is spring not doing it? Probably because you're not supposed to pass control back to the controller after you've started rendering the view; that breaks the MVC paradigm and Spring is not designed for that.

        You'll probably just have to create your own TilesView by inheriting from Spring's in order to add the missing API calls.

        Show
        Nicolas Le Bas added a comment - Well, forget my previous comment, that's not it. I was hasty and naive. Actually, the problem lies in org.springframework.web.servlet.view.tiles2.TilesView, which is not designed for your use case and doesn't call the proper API. Whenever you call TilesContainer.render(definition, ...) in an "included" situation, you should wrap the call in startContext/endContext like this: TilesContainer container = TilesAccess.getContainer(getServletContext()); AttributeContext context = container.startContext(request, response); try { // here you may override the definition's attributes on a // per-request basis by calling // context.putAttribute(name, attribute, cascade); container.render(definitionName, request, response); } finally { container.endContext(request, response); } You may do it in non-included situations, too, it's just optional. Now why is spring not doing it? Probably because you're not supposed to pass control back to the controller after you've started rendering the view; that breaks the MVC paradigm and Spring is not designed for that. You'll probably just have to create your own TilesView by inheriting from Spring's in order to add the missing API calls.
        Hide
        Nicolas Le Bas added a comment -

        Oh wait... something strikes me!
        Please try and replace

                BasicAttributeContext subContext = new BasicAttributeContext(originalContext);
                subContext.inherit(definition);
        

        with

         
                BasicAttributeContext subContext = new BasicAttributeContext(definition);
                subContext.inherit(originalContext);
        
        Show
        Nicolas Le Bas added a comment - Oh wait... something strikes me! Please try and replace BasicAttributeContext subContext = new BasicAttributeContext(originalContext); subContext.inherit(definition); with BasicAttributeContext subContext = new BasicAttributeContext(definition); subContext.inherit(originalContext);
        Hide
        Nicolas Le Bas added a comment -

        This change disables cascaded attributes. Possibly it works around a bug somewhere, but it's not a clean solution.

        when the TilesView calls the TilesContainer to render, the inheritance thingy above makes the templateAttribute of the subContext be the template of the SURROUNDING tile definition. The old one that made the include that is.

        You're right, this shouldn't happen.

        Could you provide us with more information:

        • the arguments to <tiles:insertTemplate/> in publishablePageRender.jsp.
        • the tiles definition that the spring controller refers to.
        Show
        Nicolas Le Bas added a comment - This change disables cascaded attributes. Possibly it works around a bug somewhere, but it's not a clean solution. when the TilesView calls the TilesContainer to render, the inheritance thingy above makes the templateAttribute of the subContext be the template of the SURROUNDING tile definition. The old one that made the include that is. You're right, this shouldn't happen. Could you provide us with more information: the arguments to <tiles:insertTemplate/> in publishablePageRender.jsp. the tiles definition that the spring controller refers to.
        Hide
        Viktor Hedefalk added a comment -

        This naive change made the problems dissappear:

            @Override
            protected void render(final TilesRequestContext request, final Definition definition) {
                BasicAttributeContext basicAttributeContext = new BasicAttributeContext(definition);
                pushContext(basicAttributeContext, request);
                try {
                    render(request, basicAttributeContext);
                } finally {
                    popContext(request);
                }
            }
        

        I have very little idea what this means though. The whole context stack seems meaningless with my change, but my application seems to work again at least.

        Show
        Viktor Hedefalk added a comment - This naive change made the problems dissappear: @Override protected void render( final TilesRequestContext request, final Definition definition) { BasicAttributeContext basicAttributeContext = new BasicAttributeContext(definition); pushContext(basicAttributeContext, request); try { render(request, basicAttributeContext); } finally { popContext(request); } } I have very little idea what this means though. The whole context stack seems meaningless with my change, but my application seems to work again at least.
        Hide
        Viktor Hedefalk added a comment - - edited

        It seems that this code in BasicTilesContainer:

         protected void render(TilesRequestContext request, Definition definition) {
                AttributeContext originalContext = getAttributeContext(request);
                BasicAttributeContext subContext = new BasicAttributeContext(originalContext);
                subContext.inherit(definition);
        
                pushContext(subContext, request);
        
                try {
                    render(request, subContext);
                } finally {
                    popContext(request);
                }
            }
        

        is related to the problem.

        What happens is this:

        • I hava a main "portal" controller+view with a tile definition.
        • This portal references other controllers dynamically
        • This is done with insertTemplate, c:import or jsp:include pointing to a controller with a view referencing another tile definition.-
        • The inserted controller correctly does it's thing resulting in a model and view.
        • The view is correctly resolved to its tile definition
        • the view has a templateAttribute correctly set.
        • But then, when the TilesView calls the TilesContainer to render, the inheritance thingy above makes the templateAttribute of the subContext be the template of the SURROUNDING tile definition. The old one that made the include that is. So then I guess this surrounding view is called over and over again by itself and I hit a stackoverflowerror.
        Show
        Viktor Hedefalk added a comment - - edited It seems that this code in BasicTilesContainer: protected void render(TilesRequestContext request, Definition definition) { AttributeContext originalContext = getAttributeContext(request); BasicAttributeContext subContext = new BasicAttributeContext(originalContext); subContext.inherit(definition); pushContext(subContext, request); try { render(request, subContext); } finally { popContext(request); } } is related to the problem. What happens is this: I hava a main "portal" controller+view with a tile definition. This portal references other controllers dynamically This is done with insertTemplate, c:import or jsp:include pointing to a controller with a view referencing another tile definition.- The inserted controller correctly does it's thing resulting in a model and view. The view is correctly resolved to its tile definition the view has a templateAttribute correctly set. But then, when the TilesView calls the TilesContainer to render, the inheritance thingy above makes the templateAttribute of the subContext be the template of the SURROUNDING tile definition. The old one that made the include that is. So then I guess this surrounding view is called over and over again by itself and I hit a stackoverflowerror.
        Hide
        Viktor Hedefalk added a comment -

        stacktrace

        Show
        Viktor Hedefalk added a comment - stacktrace

          People

          • Assignee:
            Unassigned
            Reporter:
            Viktor Hedefalk
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development