JSF : hacking redirect and request scope managed bean
Strting with MyFaces project and Maven 2 is very easy :
mvn archetype:createWhen project is created, you can run web app (jetty is used here) :
-DarchetypeGroupId=org.apache.myfaces.maven
-DarchetypeArtifactId=maven-archetype-myfaces
-DarchetypeVersion=1.0-SNAPSHOT
-DgroupId=com.mycoolcompany.web
-DartifactId=jsfRedirect
mvn -U -PjettyConfig jetty:runNow 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 OKThere is nothing special here.
Content-Type: text/html; charset=ISO-8859-5
Content-Language: ru
Content-Length: 7742
Server: Jetty(6.1.6rc1)
Form submit:
POST /jsfRedirect/helloWorld.jsf HTTP/1.1Posted data is the following:
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
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
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.
<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.
How to avoid data loss ? Ohh.. it is a long story.
public class HelloWorldBacking {Ok, but how to get this value back in view ? It is a long story...
....
public String sendSessionHack(){
FacesContext facesContext =
FacesContext.getCurrentInstance();
Map session =
facesContext.getExternalContext().getSessionMap();
session.put("myBackingBean",this);
return "successSessionHack";
}
}
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;Not so trivial, isn't it? :)
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
}
}
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:
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)
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.
Seam is great (if you want to be restricted to JBoss)
Well, JSF is nice technology with tons non-intuitive things, but at this moment I love Adobe Flex ! :)
Post a Comment