Monday, November 12, 2007

JSF : hacking redirect and request scope managed bean

Strting with MyFaces project and Maven 2 is very easy :

mvn archetype:create
-DarchetypeGroupId=org.apache.myfaces.maven
-DarchetypeArtifactId=maven-archetype-myfaces
-DarchetypeVersion=1.0-SNAPSHOT
-DgroupId=com.mycoolcompany.web
-DartifactId=jsfRedirect

When project is created, you can run web app (jetty is used here) :

mvn -U -PjettyConfig jetty:run
Now we have a skeleton of application. Let's try to understand how to deal with redirect.

Let's check what happens when form is submitted. Current config is
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/page2.jsp</to-view-id>
</navigation-case>

First request :

GET /jsfRedirect/helloWorld.jsf HTTP/1.1
Accept: */*
Accept-Language: ru
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; .NET CLR 1.1.4322; .NET CLR 2.0.40903)
Host: localhost:8080
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: JSESSIONID=zm2pcktnv9vl

Response :

HTTP/1.1 200 OK
Content-Type: text/html; charset=ISO-8859-5
Content-Language: ru
Content-Length: 7742
Server: Jetty(6.1.6rc1)
There is nothing special here.

Form submit:

POST /jsfRedirect/helloWorld.jsf HTTP/1.1
Accept: */*
Referer: http://localhost:8080/jsfRedirect/helloWorld.jsf
Accept-Language: ru
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; .NET CLR 1.1.4322; .NET CLR 2.0.40903)
Host: localhost:8080
Content-Length: 3892
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: JSESSIONID=zm2pcktnv9vl
Posted data is the following:

form:input1 testString
form:button1 press me
autoScroll 0,0
form_SUBMIT 1
form:_idcl
form:_link_hidden_
javax.faces.ViewState ___very__loong__

___very__loong__ is really very long string and I don’t want to post it, you can check it by yourself if you want.

As we can see, our submitted value were sent as form:input1 POST parameter. Please notice that at helloWorld.jsp we have assigned id="form" to h:form and id="input1" to h:inputText.

I guess org.apache.myfaces.AUTO_SCROLL is responsible for autoScroll param value.
To see it in action take a look here.

Responce is:

HTTP/1.1 200 OK
Content-Language: ru
Content-Type: text/html; charset=ISO-8859-5
Content-Length: 6781
Server: Jetty(6.1.6rc1)


....

And now let’s see what will change when redirect is used.

<navigation-case>
    <from-outcome>success</from-outcome>
<to-view-id>/page2.jsp</to-view-id>
<redirect/>
</navigation-case>

In this case form submission looks the same as before, but response is different, as expected :

HTTP/1.1 302 Found
Location: http://localhost:8080/jsfRedirect/page2.jsf
Content-Length: 0
Server: Jetty(6.1.6rc1)

"Location" http header means redirect for browser i.e. GET request. In this case the value of request scoped managed bean will be lost between requests (POST ant then GET), since HTTP is a stateless protocol.

How to avoid data loss ? Ohh.. it is a long story. To save data between requests I need to save managed bean in session. I can get access to http session object using faces context:


public class HelloWorldBacking {
....
public String sendSessionHack(){
FacesContext facesContext =
FacesContext.getCurrentInstance();
Map session =
facesContext.getExternalContext().getSessionMap();
session.put("myBackingBean",this);
return "successSessionHack";
}
}
Ok, but how to get this value back in view ? It is a long story...

Maybe "there is no one way to do it", but I see only one - PhaseListener.
If you know any other, please give me a sign! Code listed below will act at RESTORE_VIEW phase and then check if there is a session param with name myBackingBean, and finally assign
this value to managed bean via programmatically operations with JSF EL.

package org.apache.myfaces.blank.listener;

import javax.faces.event.PhaseListener;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;

import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.ApplicationFactory;
import javax.faces.el.ValueBinding;

import org.apache.myfaces.blank.HelloWorldBacking;

import java.util.Map;

public class MyPhaseListener implements PhaseListener
{
public PhaseId getPhaseId()
{
return PhaseId.RESTORE_VIEW;
}

public void beforePhase(PhaseEvent e)
{
FacesContext facesContext =
FacesContext.getCurrentInstance();
Map session =
facesContext.getExternalContext().getSessionMap();
HelloWorldBacking backingBean =
(HelloWorldBacking)session.get("myBackingBean");

if(backingBean!=null)
{
ApplicationFactory appFactory =
(ApplicationFactory) FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY);
Application application =
appFactory.getApplication();
ValueBinding valueBinding =
application.createValueBinding("#{helloWorldBacking}");
valueBinding.setValue(facesContext,backingBean);
session.remove("myBackingBean");
}
}

public void afterPhase(PhaseEvent e)
{
// don't care
}
}
Not so trivial, isn't it? :)
BTW, to enable custom phase listener you need to add this snip of code in faces config file, examples-config.xml in our case :

<lifecycle>
<phase-listener>
org.apache.myfaces.blank.listener.MyPhaseListener
</phase-listener>
</lifecycle>

4 comments:

norman said...

In Seam, your object would typically be stored in the conversation scope. Seam can propagate the conversation across a redirect by appending the conversation id to the URL. (without writing any extra code)

Stas Ostapenko said...

Hi Norman, thanks for comment.

It seems to be that Seam is pretty cool. BTW, I have a feeling that when I'll ask something about JSF, I'll get a response something like - "JBoss Seam does this" :)

In fact I'm exploring JSF + Spring + Hibernate technology stack. How it would be compared to Seam, I don't know. Anyway, I'll take a look at Jboss Seam.

Translate said...

Seam is great (if you want to be restricted to JBoss)

Stas Ostapenko said...

Well, JSF is nice technology with tons non-intuitive things, but at this moment I love Adobe Flex ! :)