Announcement Announcement Module
Collapse
No announcement yet.
Sitemesh with Velocity Decorators Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Sitemesh with Velocity Decorators

    Hi,

    I was using Spring + Sitemesh + Velocity, but my decoratorators were JSPs. Some months ago the Sitemesh project has released the support to Velocity decorators.
    So, I've tested the new Sitemesh Servlet in my Spring based system, but it is not working. The new Sitemesh servlet is cacthing all http requests and don't pass it to Spring Controllers...

    Does someone know how to use Spring + Sitemesh + Velocity decorators?

    Best Regards,

    Franklin Samir
    http://www.portaljava.com.br

  • #2
    It should work just fine. For an example, check out Equinox and install the "spring-velocity" option in the "extras" folder.

    Comment


    • #3
      Hi Matt,

      thank you, your exemple is working for me.
      My way was not workign because I wasn't using a sufix after my controlers path.
      I was using something like that:
      http://localhost:8080/sys/action/user/add

      Now I'm using:
      http://localhost:8080/sys/user/add.add

      Best regards,

      Franklin Samir

      Comment


      • #4
        Matt,

        I found I little problem. The includes in my decorator, are not loading my macros. The decorator.vm and the pages in $body has access to the macro.vm. But the pages inlcuded by the decorator.vm, do not.
        Algo, the included pages recognize the velocity templates. Just the macros are not beeing recognized.
        That is my velocityConfig:
        Code:
        	<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
        		<property name="resourceLoaderPath">
        			<value>/</value>
        		</property>
        		<property name="velocityProperties">
        			<props>
        				<prop key="velocimacro.library">/modules/macros.vm</prop>
        				<prop key="velocimacro.permissions.allow.inline">true</prop>
        				<prop key="velocimacro.permissions.allow.inline.to.replace.global">false</prop>
        				<prop key="velocimacro.permissions.allow.inline.local.scope">false</prop>
        				<prop key="velocimacro.context.localscope">false</prop>
        				<prop key="input.encoding">ISO-8859-1</prop>
        				<prop key="output.encoding">ISO-8859-1</prop>
        				<prop key="directive.foreach.counter.name">count</prop>
        				<prop key="directive.foreach.counter.initial.value">1</prop>
        				<prop key="velocimacro.library.autoreload">true</prop>
        			</props>
        		</property>
        	</bean>
        	<bean id="veloctyViewResolver"
        		class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
        		<property name="cache"><value>false</value></property>
        		<property name="prefix"><value></value></property>
        		<property name="suffix"><value>.htm</value></property>
        		<property name="requestContextAttribute"><value>rc</value></property>
        		<property name="exposeSpringMacroHelpers"><value>true</value></property> 
        		<property name="dateToolAttribute"><value>date</value></property>
        	</bean>
        And that's my decorator.vm:
        Code:
        <TD colspan="3">
        	#parse&#40;"/modules/Main/header.htm"&#41;
           </TD>
           </TR>
           <TR>
        	<TD width="140" valign="top">
        	 #parse&#40;"/modules/Main/menu.htm"&#41;
        	 <br>
        	</TD>
        	<TD valign="top" align="left">							
        	 $body
        	</TD>
        	<TD valign="top" align="left">
        	</TD>
           </TR>
        Do you have a suggestion to fix it?

        I think it is occuring because the included pages aren't passing by any Spring class, they are beeing loaded just by the velocity-sitemesh Servlet, and my macros are defined just in the Spring configs.

        Kind regards,
        Franklin Samir

        Comment


        • #5
          i'm having the same issue (i have a side bar that always displays some summary information about the user that is stored in the SecurityContext created by acegi on authentication) - the problem is that the context is only used to render the template specifed by the View class and then it is thrown away.

          i got around this by sub-classing the VelocityDecoratorServlet like this:

          Code:
            /**
              * @see org.apache.velocity.tools.view.servlet.VelocityViewServlet#handleRequest&#40;javax.servlet.http.HttpServletRequest,
              *      javax.servlet.http.HttpServletResponse,
              *      org.apache.velocity.context.Context&#41;
              */
             @Override public Template handleRequest&#40;HttpServletRequest request,
                HttpServletResponse response, Context context&#41; throws Exception
             &#123;
                Authentication auth = SecurityContextHolder.getContext&#40;&#41;.getAuthentication&#40;&#41;;
                
                if &#40;auth != null&#41;
                &#123;
                   context.put&#40;"user", auth.getPrincipal&#40;&#41;&#41;;
                &#125;
                
                return super.handleRequest&#40;request, response, context&#41;;
             &#125;
          it works, but now i'm having the same problem w/ some of the velocity-tools (ie: NumberTool) - i don't want to have to edit this servlet every time i need to stick something extra into the context so my decorators can use it.

          i'll post back once i've looked into this further - if anyone else has dealt w/ this before, i'd like to hear your suggestions.

          Comment


          • #6
            ok - i'm not really seeing an elegate solution around this problem. sitemesh redispatches the decorator back to the VelocityDecoratorServlet, so there isn't a simple way to carry along any context that was created inside the VelocityView class.

            it appears that the best way to handle this situation is to subclass the VelocityDecoratorServlet and populate the context w/ any values that you want available to all of your decorators.

            however, what i don't like about this is i loose the ability to leverage spring's handling of the locale when creating instances of velocity-tools, etc.

            i could go back and duplicate that code (subclassing the tools to pass in locale, etc), but that seems rather wasteful to me.

            i'm thinking that i will subclass both the VelocityViewResolver and the VelocityView so that i can expose a method off the VelocityViewResolver that will give me the same initial VelocityContext that is used inside the VelocityView class. after that, i'll look up the view resolver inside my subclassed VelocityDecoratorServlet and chain it w/ the one passed via the method input.

            does this seem like an acceptable solution? do the spring folks have another recommendation?

            Comment


            • #7
              after spending some more time investigating this last night, i believe this is the solution we both want: http://www.jroller.com/page/timosinko/20050124

              i'm curious to see just how much of that implementation is still required in the latest version of sitemesh, and will probably look to see how difficult it would be to just add it as native functionality and submit it as a patch.

              i have a pretty good handle on what's going on (the english one the site is a bit bad), so if you have questions, feel free to ask.

              Comment


              • #8
                it seems that the integration is proving to be more difficult then i expected - i can get everything to render, but the output from the applyDecorator method is ending up at the top of the template instead of the the place in the template where the $decorator.applyDecorator reference was found.

                there seems to be some issue w/ the requests and dispatchers that i am unable to track down and unfortunately i don't have the time to continue on trying to track it down.

                i found this in jira at opensymphony: http://jira.opensymphony.com/browse/...ator=printable

                sources are attached (although they need to be cleaned up to string the confluence specifics) that create a custom velocity directive that will provide the applyDecorator functionality.

                from the looks of it, you still have to build a velocity context that exposes data that should be available, so it seems as if my original idea will accomplish the same thing.

                Comment


                • #9
                  b/c i can't get enough of this topic, i figured out a workable solution for this...

                  i created a controller in spring that duplicates all the functionality of the VelocityDecoratorServlet and i just map this pattern:

                  **/*.vm

                  to that controller and define a url mapping for *.vm to be handled by the DispatcherServlet.

                  the only "issue" w/ the solution is that you need resolve against a known template and then override the template location to that of the decorator. the reason for this is that right now, the VelocityViewResolver throws an exception if it can't find the template, instead of returning null so the next resolver can be tried.

                  the code for the controller is below, feel free to use if you'd like.

                  Code:
                  import com.opensymphony.module.sitemesh.Decorator;
                  import com.opensymphony.module.sitemesh.Factory;
                  import com.opensymphony.module.sitemesh.HTMLPage;
                  import com.opensymphony.module.sitemesh.RequestConstants;
                  import com.opensymphony.module.sitemesh.util.OutputConverter;
                  
                  import net.sf.acegisecurity.Authentication;
                  import net.sf.acegisecurity.context.SecurityContextHolder;
                  
                  import org.springframework.mock.web.MockServletConfig;
                  
                  import org.springframework.web.servlet.ModelAndView;
                  import org.springframework.web.servlet.View;
                  import org.springframework.web.servlet.ViewResolver;
                  import org.springframework.web.servlet.mvc.AbstractController;
                  import org.springframework.web.servlet.view.AbstractUrlBasedView;
                  
                  import java.io.IOException;
                  import java.io.StringWriter;
                  
                  import javax.servlet.ServletContext;
                  import javax.servlet.http.HttpServletRequest;
                  import javax.servlet.http.HttpServletResponse;
                  
                  
                  /**
                   * controller to handle processing of sitemesh decorator templates
                   *
                   * @version $Revision$, $Date$
                   */
                  public class DecoratorController extends AbstractController
                  &#123;
                  
                     /**
                      * @see org.springframework.web.servlet.mvc.AbstractController#handleRequestInternal&#40;javax.servlet.http.HttpServletRequest,
                      *      javax.servlet.http.HttpServletResponse&#41;
                      */
                     @Override protected ModelAndView handleRequestInternal&#40;
                        HttpServletRequest request, HttpServletResponse response&#41; throws Exception
                     &#123;
                        ModelAndView mav = new ModelAndView&#40;&#41;;
                  
                        addRequired&#40;request, response, mav&#41;;
                  
                        HTMLPage htmlPage =
                           &#40;HTMLPage&#41; request.getAttribute&#40;RequestConstants.PAGE&#41;;
                  
                        String template = null;
                  
                        if &#40;htmlPage == null&#41;
                        &#123;
                           addNullPage&#40;mav&#41;;
                           template = request.getServletPath&#40;&#41;;
                        &#125;
                        else
                        &#123;
                           addPage&#40;mav, htmlPage&#41;;
                  
                           Factory factory = Factory.getInstance&#40;new Config&#40;getServletContext&#40;&#41;&#41;&#41;;
                           Decorator decorator =
                              factory.getDecoratorMapper&#40;&#41;.getDecorator&#40;request, htmlPage&#41;;
                  
                           template = decorator.getPage&#40;&#41;;
                        &#125;
                  
                        mav.setView&#40;resolveView&#40;template&#41;&#41;;
                  
                        return mav;
                     &#125;
                  
                     /**
                      * add "null" page attributes
                      *
                      * <p>these attributes are added if a &#123;@link
                      * com.opensymphony.module.sitemesh.Page&#125; object was not found in the
                      * request</p>
                      */
                     private void addNullPage&#40;ModelAndView mav&#41;
                     &#123;
                        mav.addObject&#40;"title", "Title?"&#41;;
                        mav.addObject&#40;"body", "<p>Body?</p>"&#41;;
                        mav.addObject&#40;"head", "<!-- head -->"&#41;;
                     &#125;
                  
                     /**
                      * add page attributes
                      *
                      * <p>these attributes are added if a &#123;@link
                      * com.opensymphony.module.sitemesh.Page&#125; object was found in the request</p>
                      */
                     private void addPage&#40;ModelAndView mav, HTMLPage htmlPage&#41; throws IOException
                     &#123;
                        mav.addObject&#40;"title", OutputConverter.convert&#40;htmlPage.getTitle&#40;&#41;&#41;&#41;;
                  
                        &#123;
                           StringWriter buffer = new StringWriter&#40;&#41;;
                           htmlPage.writeBody&#40;OutputConverter.getWriter&#40;buffer&#41;&#41;;
                           mav.addObject&#40;"body", buffer.toString&#40;&#41;&#41;;
                        &#125;
                  
                        &#123;
                           StringWriter buffer = new StringWriter&#40;&#41;;
                           htmlPage.writeHead&#40;OutputConverter.getWriter&#40;buffer&#41;&#41;;
                           mav.addObject&#40;"head", buffer.toString&#40;&#41;&#41;;
                        &#125;
                  
                        mav.addObject&#40;"page", htmlPage&#41;;
                     &#125;
                  
                     /**
                      * add required attributes
                      *
                      * <p>this attributes should exist reguardless of whether or not a &#123;@link
                      * com.opensymphony.module.sitemesh.Page&#125; object was found in the request</p>
                      */
                     private void addRequired&#40;HttpServletRequest request,
                        HttpServletResponse response, ModelAndView mav&#41;
                     &#123;
                        // add base path, request and response
                        mav.addObject&#40;"base", request.getContextPath&#40;&#41;&#41;;
                        mav.addObject&#40;"req", request&#41;;
                        mav.addObject&#40;"res", response&#41;;
                     &#125;
                  
                     /**
                      * resolve the view
                      */
                     private View resolveView&#40;String template&#41; throws Exception
                     &#123;
                        ViewResolver viewResolver =
                           &#40;ViewResolver&#41; getApplicationContext&#40;&#41;.getBean&#40;"viewResolver"&#41;;
                  
                        /*
                         * XXX&#58; if the view resolver can't find the template, it throws an
                         * exception, so we lookup a dummy template that we know exists, and then
                         * override the url w/ that of the decorator's location.
                         */
                        View view = viewResolver.resolveViewName&#40;"dummy", null&#41;;
                        &#40;&#40;AbstractUrlBasedView&#41; view&#41;.setUrl&#40;template&#41;;
                  
                        return view;
                     &#125;
                  
                     /**
                      * allows direct use of the &#123;@link javax.servlet.ServletContext&#125; when looking
                      * up a &#123;@link com.opensymphony.module.sitemesh.Factory&#125; instance
                      *
                      * @version $Revision$, $Date$
                      */
                     static private class Config extends com.opensymphony.module.sitemesh.Config
                     &#123;
                  
                        /** servlet context */
                        private ServletContext ctx;
                  
                        /**
                         * create a new Config object.
                         *
                         * @param ctx ServletContext object
                         */
                        public Config&#40;ServletContext ctx&#41;
                        &#123;
                           super&#40;new MockServletConfig&#40;ctx&#41;&#41;;
                           this.ctx = ctx;
                        &#125;
                  
                        /**
                         * @see com.opensymphony.module.sitemesh.Config#getServletContext&#40;&#41;
                         */
                        @Override public ServletContext getServletContext&#40;&#41;
                        &#123;
                           return this.ctx;
                        &#125;
                     &#125;
                  &#125;

                  Comment


                  • #10
                    Someone just posted this on the mailing list; not sure if there's anything there useful for you: http://wiki.apache.org/jakarta-veloc...yAndSpringTips

                    Comment


                    • #11
                      My Freemarker Solution

                      I had the same problem, but with Freemarker instead of Velocity. Basically I wanted the decorating markup (i.e. header, footer, and navigation sidebar) to contain not just hard-coded HTML but also things like:
                      • * the username (for display in the logout link)
                        * various labels looked up from resource files
                        * an "onload" JavaScript command that depends on which decorated page is being displayed (i.e. on the login screen I want the username field to have default focus, whereas on some edit screen, I want the first text field to have default focus)
                      I ended up doing this (having used Velocity a lot, I'm sure you Velocity users could apply the same approach):

                      1. Created a shared Freemarker macro (in my Macros.ftl file) that converts all data required by the decorators (see above list) into HTML meta tags, like this (I've shown only two such tags, for brevity):

                      Code:
                      <#macro metadata>
                      	<meta name="navigation.about" content="<@spring.message code="navigation.about"/>"/>
                      	<meta name="version" content="<@spring.message code="version"/>"/>
                      </#macro>

                      2. Called this macro from a one-liner at the top of each Freemarker template

                      Code:
                      <#import "/spring.ftl" as spring />
                      <#import "/Macros.ftl" as macros />  <#-- my macros file -->
                      <@macros.metadata/> <#-- calls the macro from step 1 -->
                      <meta name="on.load" content="document.forms&#91;0&#93;.$&#123;usernameFieldName&#125;.focus&#40;&#41;;"/>
                      ... rest of template goes here ...
                      3. Created a second macro that extracts the value from a named META tag:

                      Code:
                      <#macro meta name>
                      	<#assign key="meta." + name/>
                      	<#assign value=page.properties&#91;key&#93;?default&#40;""&#41;/>$&#123;value&#125;</#macro>
                      4. And finally, in the decorating template, I call this macro to display the extracted data:

                      Code:
                      <#-- Main decorator file -->
                      <#import "Macros.ftl" as macros />	<#-- Allows use of my macros -->
                      <html>
                      	<head>
                      		<title><@macros.meta name="product"/></title>
                      	</head>
                      	<body onload="<@macros.meta name="on.load"/>">
                      <!-- show navigation links -->
                      <a href="about.htm"><@macros.meta name="navigation.about"/></a>
                      <!-- other navigation links go here -->
                                  $&#123;body&#125;
                              </body>
                      </html>
                      This is a non-ideal solution, in that the pages being decorated have to know about the pages that are decorating them (otherwise why would they have all those meta tags in them), but it does mean the decorating pages don't need access to the same model data or resource files that the decorated pages do.

                      Hope this helps, and if anyone has a more elegant solution, please let me know or just reply here!

                      Cheers,

                      Andrew

                      Comment


                      • #12
                        And what's wrong with letting decorating pages to have access to the same data model that the decorated pages have?

                        It seems that the problem is actually that decorators do not have any access to the data model because they are being processed by separate sitemesh filter, not in the normal fashion. That is where the voodoo dances begin. Can't figure why would anyone want to use SiteMesh in the first place.

                        The most natural way IMHO would be to use custom ViewResolver which would render the decorated page first in the page.body variable, and then, using the same data model, render the decorator (now that we have page.body rendered). The results should be sent to output.

                        Example:
                        decorator.ftl:

                        Code:
                        <html>
                        <head>My Site - $&#123;title&#125;</head>
                        <body>
                         // ... header and menu goes here
                          $&#123;page.body&#125;
                        //... footer and copyrights go here
                        </body>
                        </html>
                        page.ftl:
                        Code:
                        <#set title="Page Title" />
                        
                        Some page content here
                        We just have to figure the best way to specify which decorator for which page should be used.
                        This example also demonstrates that the page title can be defined right in the page.ftl, which particularly seems to be more natural location for this rather than some external xml configuration.

                        This way of things also provides natural and uniform support for i18n - I usually pass locale object in the data model, also a special messageSourceHelper bean which is a simple wrapper around Spring's MessageSource, and define a macro:
                        Code:
                        <#macro _&#40;key&#41;>
                         messageSourceHelper.get&#40;key, locale&#41;
                        </#macro>
                        Then I use it throughout the template:
                        Code:
                        <title><@_&#40;"page.home.title"&#41;/></title>
                        This way anyone could use all benefits of FreeMarker (or Velocity), feel free to access the data model and don't bother with some weird workarounds when it's that simple. Perhaps I'm missing something important that makes it seem like a simple stuff.

                        I'll probably give it a try and implement such a custom ViewResolver and won't even try to use SiteMesh when it makes such simple things difficult.
                        Should it work out fine, I'll post it here with more detailed samples. (I might be wrong that it involves just custom ViewResolver, perhaps it will take some custom Renderer as well - will have to dig in the Spring source code)

                        Comment


                        • #13
                          Sounds Promising!

                          > use custom ViewResolver...
                          Sounds like a good idea! Please do post here again once you get it working - I'd like to know the details of how you merge the page and decorator templates.

                          > I usually pass locale object in the data model, also a special messageSourceHelper bean which is a simple wrapper around Spring's MessageSource...
                          AFAIK this is already done for you. I have my FreeMarker view resolver defined like this:

                          Code:
                          <bean id="freeMarkerViewResolver"
                              class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"
                            >
                              <property name="suffix">
                                <value>.ftl</value>
                              </property>
                              <!-- This enables usage of Spring macros &#40;e.g. <@spring.message ...&#41; in templates -->
                              <property name="exposeSpringMacroHelpers">
                                <value>true</value>
                              </property>
                              <!-- This exposes all session attributes to the templates &#40;i.e. they are
                                merged with the custom model data&#41; -->
                              <property name="exposeSessionAttributes">
                                <value>true</value>
                              </property>
                            </bean>
                          This means my templates can get I18N messages from my resource bundles simply by calling the "message" macro already provided in spring.ftl:
                          Code:
                          <p>Here is some translated text&#58; <@spring.message code="my.message.code"/></p>
                          There's also a "messageText" macro in spring.ftl that allows you to specify the default text if the given message code couldn't be resolved.

                          HTH,

                          Andrew

                          Comment


                          • #14
                            Will post soon

                            Thanks for the tip on exposing standard Spring macros!
                            I certainly prefer to use standard ways when there are any good ones.

                            I have been busy until recently, but I've been able to create a custom view resolver which achieves the decorator <-> decorated pattern very easily and nicely.

                            Will polish it a little and test how it works with i18n, and post it here for community review.

                            Comment

                            Working...
                            X