Wednesday, November 14, 2007

JSF : MyFaces and Sun RI profiles for Maven 2

I've used MyFaces as implementation of JSF 1.1 specification. Why ? The main reason is great community and great support for Maven 2 - maven-archetype-myfaces for example, + Tomahawk.

And now, I'm interested in what is the difference between the two ? Any comments appreciated.

Maven 2 is my friend here, since I can use profiles - one for JSF RI, another one for MyFaces. I guess it could be useful for someone else, so here is the code :

<profiles>
<profile>
<id>jsfri</id>
<activation>
<property>
<name>jsf</name>
<value>ri</value>
</property>
</activation>
<dependencies>
<dependency>
<groupId>javax.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>1.1_02</version>
<exclusions>
<exclusion>
<groupId>java.servlet.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>1.1_02</version>
<exclusions>
<exclusion>
<groupId>java.servlet.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>java.net</id>
<name>java.net Maven 1 Repository</name>
<url>
https://maven-repository.dev.java.net/nonav/repository
</
url>
<layout>legacy</layout>
</repository>
</repositories>
</profile>
<profile>
<id>myfaces</id>
<activation>
<property>
<name>jsf</name>
<value>myfaces</value>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-api</artifactId>
<version>1.1.6-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-impl</artifactId>
<version>1.1.6-SNAPSHOT</version>
</dependency>
<!--
<dependency>
<groupId>org.apache.myfaces.tomahawk</groupId>
<artifactId>tomahawk</artifactId>
<version>1.1.6-SNAPSHOT</version>
</dependency>
-->
</dependencies>
<repositories>
<repository>
<releases>
<enabled>false</enabled>
</releases>
<snapshots/>
<id>apache-maven-snapshots</id>
<url>
http://people.apache.org/repo/m2-snapshot-repository
</url>
</repository>
</repositories>
</profile>
</profiles>
To switch between profiles use "mvn -Djsf=ri" or "mvn -Djsf=myfaces".

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>

Thursday, November 01, 2007

JSF : Redirect to URL with param from navigation-case

I'm evaluating JSF now. I have to admit that some of JSF ideas are pretty nice, but there are many non-intuitive things which make life hard. From another point of view, it is framework, with it's own rules of the game.

From my little experience I could say that JSF doesn't like the way of passing params via GET. Of course it is possible, but I couldn't say it is trivial and easy as in Struts for example. So, here is some technical stuff.

I need to to redirect from navigation-case to URL with param which is a property of a managed bean. It looks like this :

<navigation-rule>
<from-view-id>/add.jsp</from-view-id>
<navigation-case>
<from-outcome>added</from-outcome>
<to-view-id>/view.jsf?myParam=#{myManagedBean.id}</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
I've tried to figure out how to do it, but had no success here. In fact I found solution, but it's more something like a hack than solution.
So, I've asked MyFaces mailing list. Many thanks to all the people who answered me.

To keep the story short, here is solution. Custom view and navigation handlers should be added to use managed bean property as url param in navigation-case.

I think to myself... should I use core JSF or switch to Apache Orchestra, or JBoss Seam to make development with JSF less painful ? JSF is still young, 1.2 is the latest version of specification and frameworks that use JSF has many additional functionality. Who knows ?