9 Replies Latest reply on Nov 25, 2006 2:42 PM by gavin.king

    Searchengine friendly links

    spambob

      I would like to have URLs that look like static links although they are dynamic links.

      I.e: something like 'www.example.com/products/12345.html' where 12345 is the product id. That is all requests for '/products/xyz.html' get mapped to '/products/showProduct.seam?productId=xyz'.

      Normally i would do that with a servlet filter but i was wondering if there's some seam built-in magic to do that automagically?!

      I.e. by configuring something in pages.xml like:

      <page view-id="/products/#{productId}.html" target="/products/showProduct.seam" />

      Which sets a request scoped variable 'productId' so i can reference it in the JSF page.

      Is something like this possible? If yes: how? If not what do you think about it and whats the current best practice for it?

        • 1. Re: Searchengine friendly links
          gavin.king

          No, its not supported by JSF. You need to do URL rewriting (a servlet filter would work).

          • 2. Re: Searchengine friendly links
            spambob

            Hi Gavin,

            now I wrote me a little filter which extracts request scoped variables from a URL and forwards to some target page.

            I.e. from a URL "http://www.example.com/products/12345.xhtml" a request variable 'productId' with value '12345' is extracted and the request is forwarded to /showProduct.xhtml.
            The product reference I use in showProduct.xhtml via EL is created by a @Factory annotated method that uses a 'productId' attribute of the same class to know which product to load.

            This works fine when the productId is injected in the class with the @In annotation but not when I use the @RequestParameter annotation - which I would prefer because I'm not sure if the @In solution would be threadsafe - because the 'productId' is in the attribute map and not in the parameter map of the request. Further there's no method to add an additional parameter.

            So the possible solutions I can think of are:

            1. add a wrapper around every request in the filter - I don't like that because it would be quite an overhead just to set these parameters

            2. use @In(scope=EVENT) - I'm not sure if there would be any impacts on performance / scalability

            3. we get a @RequestAttribute annotation (like the @RequestParameter but for the attribute map of the request)

            4. It is somehow possible to pass parameters to a factory method - i like that one but found no examples for it, is it possible?

            So: how would you solve that and would there be any difference between 2nd and 3rd?

            • 3. Re: Searchengine friendly links
              gavin.king

              Setting a request attribute is exactly the same as Contexts.getEventContext().set(...). There are no negative performance implications of that. I would go for that approach.

              • 4. Re: Searchengine friendly links
                spambob

                Thank you for your answer. I'm just not sure if I understood the concept of thread safety in Seam or when exactly stuff is in- & outjected.

                So would a component like this:

                @Name("productService")
                @Scope(STATELESS)
                public class ProductServiceBean {
                
                 @In private EntityManager em;
                 @In private String productId;
                
                 @Factory("product")
                 public Product getProductById() {
                 return em.find(Product.class, productId);
                 }
                }

                be thread safe ('productId' is my request attribute and 'product' is used to reference the product in the jsf page)?

                • 5. Re: Searchengine friendly links
                  gavin.king

                  Of course. I really don't understand what you are fussing about here. Request state is by definition scoped to a single thread.

                  • 6. Re: Searchengine friendly links

                    I really think this is a feature that a lot of people want. I'd like to be able to simply write something like /products/1234, or at worst /products.seam/1234.

                    What do you think of something to complement @RequestParameter? Maybe @UrlParameter that takes a regexp to match against the end of the URL?

                    @UrlParamter("/([0-9]*)$")
                    Long productId;



                    • 7. Re: Searchengine friendly links
                      spambob

                      Now I don't understand it too - a classic case of 'I don't see the forest cause of all the trees'. Thanks for your clarification & patience!

                      EDIT: i just saw normans post while editing mine - so i post my solution, feel free to integrate / modify it:

                      The filterclass:

                      public class UrlRewriteFilter implements Filter {
                      
                       private FilterConfig filterConfig;
                       private String[] variableNames;
                       private String[] delimiters;
                       private String targetUrl;
                      
                       public void init(FilterConfig filterConfig) throws ServletException {
                       this.filterConfig = filterConfig;
                       this.targetUrl = filterConfig.getInitParameter("target");
                       String pattern = "#\\{[^}]+}";
                       String configString = filterConfig.getInitParameter("pattern");
                       Pattern p = Pattern.compile(pattern);
                       Matcher m = p.matcher(configString);
                      
                       List<String> variableNames = new ArrayList<String>();
                       while(m.find()) {
                       variableNames.add(configString.substring(m.start()+2, m.end()-1));
                       }
                       this.variableNames = variableNames.toArray(new String[variableNames.size()]);
                       this.delimiters = configString.split(pattern);
                       }
                      
                       public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                       HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
                       String request = httpRequest.getRequestURI();
                      
                       int start;
                       int end = request.indexOf(delimiters[0]);
                       for(int i=0; i<variableNames.length; i++) {
                      
                       start = end + delimiters[ i ].length();
                       end = request.indexOf(delimiters[i+1], start);
                       servletRequest.setAttribute(variableNames, request.substring(start, end));
                       }
                       filterConfig.getServletContext().getRequestDispatcher(targetUrl).forward(servletRequest, servletResponse);
                       }
                      
                       public void destroy() {;
                      
                       }


                      The config in web.xml:
                      <filter>
                       <filter-name>UrlRewriteFilter</filter-name>
                       <filter-class>com.example.UrlRewriteFilter</filter-class>
                       <init-param>
                       <param-name>pattern</param-name>
                       <param-value>/products/#{categoryId}/#{productId}.xhtml</param-value>
                       </init-param>
                       <init-param>
                       <param-name>target</param-name>
                       <param-value>/showProduct.xhtml</param-value>
                       </init-param>
                       </filter>
                      
                       <filter-mapping>
                       <filter-name>UrlRewriteFilter</filter-name>
                       <url-pattern>/products/*</url-pattern>
                       </filter-mapping>


                      The bean that loads the product is the one above and the jsf is trivial.

                      With a configuration like this a URL like
                      http://www.example.com/products/cars/123.xhtml
                      is mapped to
                      http://www.example.com/showProducts.xhtml?categoryId=cars&productId=123
                      Note that the request variables are attributes not parameteres therefore @RequestParameter doesn't work.

                      • 8. Re: Searchengine friendly links
                        spambob

                        The board somehow screwed my code. The line

                        servletRequest.setAttribute(variableNames, request.substring(start, end));


                        should be
                        servletRequest.setAttribute(variableNames[ i ], request.substring(start, end));


                        • 9. Re: Searchengine friendly links
                          gavin.king

                          Norman, I think that's very reasonable. I would see this as an extension of both @RequestParameter and page parameters. So basically we (Seam) would provide a servlet filter that could do the rewriting for you, and extract some extra strings from the end of the URL. Right?