2

Many pages in a typical JSF applications are dynamic, meaning that there is a template view that would be used to render every object of a given type. For these pages PrettyFaces rewriting solution works great and effortless. An example is a web application that displays a product, basing on its id, or other unique field. There is typically one view related to such a display, say product.xhtml, and one view parameter, holding the unique field of a product, say name.

With a simple setting we get all requests like /product.xhtml?name=exact-product-name rewritten as, for example, /products/exact-product-name:

The URL mapping:

<url-mapping id="viewProduct">
    <pattern value="/products/#{ name : productBean.name }" />
    <view-id value="/store/product.xhtml" />
    <action> #{ productBean.loadData } </action>
</url-mapping>

The view:

<f:metadata>
    <f:viewParam id="name" name="name" required="true" />
</f:metadata>

The model:

public class ProductBean implements Serializable {

    private ProductService productService;

    private String name;

    private Product product;

    public String loadData() {
        if(!((name == null) || (name.equals(""))) {
            Product product = productService.findByName(name);
            this.product = product;
            return null;
        }
        return "error";
    }

}

However, there are also many pages with static data, that are not templated in a way described above, using view parameters. These pages simply display what was put in them. For example, there may be many articles that were created as separate views (like /pages/articles/article1.xhtml and so on). Using PrettyFaces we would need to create as many URL mapping as the number of such pages. But, in fact this behavior can also be templated in one URL mapping. Unfortunately, this is not supported in current PrettyFaces release.

The proposed enhancement of the PrettyFaces framework is the following:

<url-mapping id="viewArticle">
    <pattern value="/articles/#{ articleName }" />
    <view-id value="/store/#{ articleName }.xhtml" />
</url-mapping>

or, using an ArticleBean (containing, for example, two fields: articleName and articleId, where name is defined in setter of id field as a unique value):

<url-mapping id="viewArticle">
    <pattern value="/articles/#{ articleId : articleBean.articleId }" />
    <view-id value="/store/#{ articleBean.articleName }.xhtml" />
</url-mapping>

or using other predefined dependence based on an EL-expression, which is in turn based on a unique correspondence.

I want to emphasize that this is not going to be a DynaView because there is no uncertainty in the view-id: there is a one-to-one correspondence between a <pattern> and a <view-id>.

What do you think about implementing this feature in PrettyFaces?

skuntsel
  • 11,624
  • 11
  • 44
  • 67
  • Reminds me one thing I've done earlier when creating my JSF-2 CMS, since I've done my custom URL rewriting (based on `Filter` and `ViewHandler`), I've kept an option that when URL comes with a `.*` at the end, I keep it static, ortherwise there is an internal redirect depending on the kind of data (Posts are in MySQL). – Alexandre Lavoie Jun 09 '13 at 08:03

2 Answers2

1

I think Stackoverflow is not the right place to discuss proposals for PrettyFaces. You should have a look at the PrettyFaces Support Forum for that.

There are some options for you to implement something like this. IMHO you could try to do this view DynaView. Even if there is a one-to-one relationship between pattern and view-Id like your wrote. However dynaview has some problems especially with outbound rewriting.

But you should have a look at Rewrite, which is the successor of PrettyFaces. With Rewrite it is very simple to implement such a requirement:

.addRule(Join.path("/articles/{articleName}").to("/store/{articleName}.xhtml"))

Have a look at the configuration examples of Rewrite.

chkal
  • 5,598
  • 21
  • 26
  • It was not meant to be a proposal in the first place. By this question and answer I wanted to share my findings on the issue I faced and show for possible workarounds in the current PrettyFaces release for potential readers. As for the usage of Rewrite, I didn't want to mix them both in one application. – skuntsel Feb 25 '13 at 16:23
  • By the way, thank you for the great job of keeping up the PrettyFaces project! – skuntsel Feb 25 '13 at 16:23
0

As far as the setup of pretty-config.xml doesn’t currently support this feature, there are some workarounds to achieve this functionality. I will describe them below.

A dummy view with <f:event> that handles navigation to the final pages based on a view parameter in a dummy bean.

URL mapping:

<url-mapping id="viewArticle">
    <pattern value="/articles/#{ articleName : articleBean.articleName }" />
    <view-id value="/handle-article-redirection.xhtml" />
</url-mapping>

View handle-article-redirection.xhtml:

<f:metadata>
    <f:viewParam id="articleName" name="articleName" required="true" />
    <f:event type="preRenderView" listener="#{articleBean.handleRedirect}" />
</f:metadata>

Model:

public class ArticleBean {

    private ArticleService articleService;

    private String articleName;

    private String articleUrl;

    public void handleRedirect() {
        if(!((articleName == null) || (articleName.equals(""))) {
            String url = articleName;
            //String url = articleService.getUrlForArticleName(articleName);
            //articleUrl = url;
            FacesContext.getCurrentInstance().getExternalContext().redirect("/" + url + ".xhtml");
            return null;
        }
        FacesContext.getCurrentInstance().getExternalContext().redirect("/home.xhtml");
    }

}

A meaningful view with a dynamic <ui:include> that imports the necessary page content as a snippet, basing on the bean value / view parameter.

URL mapping:

<url-mapping id="viewArticle">
    <pattern value="/articles/#{ articleName : articleBean.articleName }" />
    <view-id value="/article.xhtml" />
</url-mapping>

View article.xhtml:

<f:metadata>
    <f:viewParam id="articleName" name="articleName" required="true" />
</f:metadata>
<h:head></h:head>
<h:body>
    <ui:include src="/#{articleBean.articleUrl}.xhtml" />
</h:body>

Model:

public class ArticleBean {

    private ArticleService articleService;

    private String articleName;

    private String articleUrl;

    public void setArticleName(String articleName) {
        this.articleName = articleName;
        if((!(articleName == null)) || (articleName.equals("")) {
            articleUrl = articleName;
            //articleUrl = articleService.getUrlForArticleName(articleName);
        } else {
             articleUrl = null;
        }
    }

}

A DynaView URL mapping with a method that returns a proper outcome.

URL mapping:

<url-mapping id="viewArticle">
    <pattern value="/articles/#{ articleName : articleBean.articleName }" />
    <view-id value="#{articleBean.getViewPath}" />
</url-mapping>

No extra view needed.

Model:

public class ArticleBean {

    private ArticleService articleService;

    private String articleName;

    private String articleUrl;

    public String getViewPath() {
        this.articleName = articleName;
        if(!((articleName == null) || (articleName.equals(""))) {
            articleUrl = articleName;
            //articleUrl = articleService.getUrlForArticleName(articleName);
            return articleUrl;
        }
        return "error";
    }

}

A template view that loads the page data from the database, hence there will be no separate views for those pages.

URL mapping:

<url-mapping id="viewArticle">
    <pattern value="/articles/#{ articleName : articleBean.articleName }" />
    <view-id value="/article.xhtml" />
    <action> #{ articleBean.loadData } </action>
</url-mapping>

View article.xhtml:

<f:metadata>
    <f:viewParam id="articleName" name="articleName" required="true" />
</f:metadata>
<h:head></h:head>
<h:body>
    <h:panelGroup>
        #{articleBean.content}
    <h:panelGroup>
</h:body>

Model:

public class ArticleBean {

    private ArticleService articleService;

    private String articleName;

    private String articleUrl;

    private String content;

    public void loadData() {
        if(!((articleName == null) || (articleName.equals(""))) {
            articleUrl = articleName;
            //articleUrl = articleService.getUrlForArticleName(articleName);
            content = articleService.getContentForArticleName(articleName);
        } else {
             articleUrl = null;
             content = null;
        }
    }

}

Write a custom WebFilter or a NavigationHandler.

What is the best alternative, well, it depends. All of them have their pros and cons.

skuntsel
  • 11,624
  • 11
  • 44
  • 67