Thursday Nov 29, 2007

NetBeans Visual Web - JSF, "Pretty" URLs and Multiple Instances of the Same Page

Since I introduced "Pretty URLs" in the NetBeans Plugin Portal, there have been some strange things happening like issues 119249, 122600, and 122340.  Once again I've been schooled on the JSF life cycle.  So here's what's happening.


When a button or link is clicked on a page and an action is associated on the pages backing bean, a post back occurs.  For the purposes of this discussion, a post back basically means a request is sent from the displayed web page to the server, the page backing bean action method is executed, a null is returned from the method telling the JSF framework not to navigate anywhere, and the page is redisplayed using the JSF Standard Processing Lifecycle.  If you look at the how I used the JSF life cycle in my earlier blog titled, "NetBeans Visual Web Pack - Real World Apps Tip #4 - "Pretty" URLs", I took advantage of the Visual Web application model "init" method.  In the details of implementing "Pretty" URLs, the key piece of information for rebuilding the state of the page in the "init" would logically be put on the SessionBean.  For example, for the Plugin Detail page I need the "currentPlugin".  So when I navigate to the Plugin Detail page I set the "currentPlugin" attribute on the SessionBean and use that for all the details.  So now what happens when someone right-clicks on my "Pretty" URL and chooses to open a different Plugin in another tab?  The Plugin Portal stores the new Plugin as the "currentPlugin" on the SessionBean.  Now on the first plugin, I do some action like click the download button.  Now to the important part!  Remember that a PageBean gets instantiated EVERY time a page is displayed.  This means the PageBean for the first Plugin is instantiated and gets the "currentPlugin" from the SessionBean.  Things are now bad because the second Plugin in the other tab set "currentPlugin".  So you have results like described in the issues above.


The solution for this seemed easy at first, save each page instance state per Plugin.  This turned out to be quite difficult to implement.  Again I realized that the biggest difficulty when using JSF is understanding the Standard Processing Lifecycle.  I somehow needed to save off state from a page with uniqueness and pull that state back.  For example, if a detail page was showing for a particular Plugin and the user opens another instance of the page with a different plugin, we need to have each page somehow maintain which plugin is which for a post back.  Remember we can't use the SessionBean for "currentPlugin" because each instance of the page will overwrite this attribute.  We can't use cookies because these are browser instance wide and not page specific.  The black magic is based on a nice class in the JSF framework called "UIViewRoot".  The "UIViewRoot" is the root of the component tree for all the components on the page.  And here's the best part, "UIViewRoot" is saved across post back calls.  This means on a post back, you can expect values set in components to be there when the page is rerendered.  This implementation does things like keeping form values across post backs to keep users form committing suicide after they've filled out a form for 3 hours only to hit the "submit" button and find out they missed a required field and now all the fields are blank.  The Visual Web application model has the "init" method that happens during the "Restore View" phase.

vw app model

The problem is that the UIViewRoot values don't get stuffed back into the components on the backing bean until "Update Model Values".  This means you can't do something like this,


and expect to get the value you stuffed in there before a button was clicked.  So the trick is to pull the value out of the "UIViewRoot".  Here's the code that shows how I did this on the Plugin Detail page.

\* Process a GET type request.
        HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
        String [] pluginids = request.getParameterValues("pluginid");

        if(null != pluginids && pluginids.length == 1) {
try {
Long pluginid = new Long(Long.parseLong(pluginids[0]));
currentPlugin = getApplicationBean1().getPluginSystem().getPlugin(pluginid);
if(null != currentPlugin) {
          } catch(NumberFormatException nfe) {}
        } else {
          if(null == this.getCurrentPlugin()) {
\* Process a post back.
           FacesContext fc = FacesContext.getCurrentInstance();
UIViewRoot viewRoot = fc.getViewRoot();
if(null != viewRoot) {
StaticText pluginid_text = 
(StaticText) viewRoot.findComponent("form1:center_container:page_border:fixed_contentarea:fixed_contextbox:topPanel:txtPluginID");
if(null != pluginid_text) {
Long pluginid = (Long)pluginid_text.getText();
if(null != pluginid) {
currentPlugin = getApplicationBean1().getPluginSystem().getPlugin(pluginid);
So you can see that for a "GET" style call with the "Pretty" URL we do what did before except we stuff the part of the state we want for the post back into a "Static Text" component.  In the case of the post back, we get a hidden field from the "UIViewRoot" and get its value to use.  So the one last piece is to create a "Static Text" component somewhere on the page and make it invisible.  As you can see from the code you have to fully qualify the name of the "Static Text" component.  In my case it's "txtPluginID".

Well I'm hoping another brick doesn't fall out the other side again. :)  If anyone has a better way to pull this off, please, please post a comment.


David Botterill


« April 2014