2
votes

Edit 4

What I wanted to do is implement forgotPassword page. For example purpose I have taken below example and it is not real user related question where I will keep username in session scope.

index.xhtml would be forgotPassword page where I would be entering username. After entering username, I would be clicking Welcome Me - Action and in chkMe(), I would be checking that user and send new password on his/ her email id and in welcome.xhtml, I would be saying Hi User ABC, we have sent new password at [email protected].


Main Post

I am trying to print data from one bean to another with two cases. Below is the code I have.

index.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="http://java.sun.com/jsf/core"      
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>JSF 2.0 Hello World</title>
    </h:head>
    <h:body>
        <h3>JSF 2.0 Hello World Example - hello.xhtml</h3>
        <h:form>
           <h:inputText value="#{helloBean.name}"></h:inputText>
           <h:commandButton value="Welcome Me - Plain" action="welcome"></h:commandButton>
           <h:commandButton value="Welcome Me - Action" action="#{helloBean.chkMe()}"></h:commandButton>
        </h:form>
    </h:body>
</html>

welcome.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"    
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>JSF 2.0 Hello World</title>
    </h:head>
    <h:body bgcolor="white">
        <h3>JSF 2.0 Hello World Example - welcome.xhtml</h3>
        <h4>Welcome --#{helloBean.name}--</h4>
    </h:body>
</html>

HelloBean.java

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import java.io.Serializable;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class HelloBean implements Serializable {

    private static final long serialVersionUID = 1L;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String chkMe() {
        return takeMeToAnotherPage("welcome");
    }

    public String takeMeToAnotherPage(String linkToGo) {
        return linkToGo + "?faces-redirect=true";
    }
}

When I enter text as Checking in textfield and click button, Welcome Me - Plain, I see text as Welcome --Checking-- text in welcome.xhtml, however when I click Welcome Me - Action, I don't see any text (I see as Welcome ----)

I don't know why this is happening.

Any idea/ suggestion why this is happening.


Edit 1

I believe this is all causing because of ?faces-redirect=true, but I have to have use this as if I don't use ?faces-redirect=true, the URL in address bar is previous url.

e.g. If I am on page1.xhtml and I go to page2.xhtml, still URL will say page1.xhtml.

So not sure what to do in such case.


Edit 2

Well, what I actually want to do is forgotPassword page where I will enter username in index.xhtml (considering above example) and if that username is correct, on welcome.xhtml, I will have Hi User ABC, Please use new password for next login. We have sent you email at [email protected].

RequestScope was working perfectly, but the problem was with URL address and hence I added ?faces-redirect=true. But as its redirect, http session is closing and hence on welcome.xhtml, I don't get any value (which is what is happening above).

Another solution from skuntsel was to use FlashScope, but there again the problem is when I refresh welcome.xhtml, data is gone, which drives me crazy.

Can anyone suggest for what needs to be done?


Edit 3

The problem in session scope is as below.

Consider I open two tab and on both tab I have index.xhtml. On tab1 I enter Fahim and clicked Welcome Me - Action. On tab1, welcome.xhtml comes and I see text as Welcome Fahim. This is perfect.

Now I come to tab2, and enter name as XYZ, and clicked Welcome Me - Action I get welcome.xhtml and I see text as Welcome XYZ. This is also Perfect.

The problem is when I comes back on tab1 and refresh the page. When I refresh the tab1 (welcome.xhtml), I see Welcome XYZ which is wrong as earlier it was Welcome Fahim and it should be Welcome Fahim.

8
Fahim, you should take a close look at an excellent article by BalusC on communication in JSF. - skuntsel
Why don't you directly call @takeMeToAnotherPage' instead of @checkMe' like #{helloBean.takeMeToAnotherPage}.My Guess would be when you call a method like you did , the page already redirected when it comes to takeToAnotherPage. So, You are trying to display redirected page which doesn't exist in the case of @RequestScopedBean. - SRy
@Srinivas : Still result is same - Fahim Parkar
All might see Edit 2 which is what actually I want. Its not about user, its about forgot session. Example I used is just for demo purpose. - Fahim Parkar
@FahimParkar I think that the updated answer actually solves your problem (as well as the original one did). - skuntsel

8 Answers

2
votes

Using current user in session scope is a good idea, to my taste.

Though, if it doesn't fit you, I can offer some more alternatives.

Passing username as a view parameter

Which translates to

<h:form>
    <h:inputText value="#{helloBean.name}"/>
    <h:commandButton value="Welcome Me - Action" action="#{helloBean.chkMe}"/>
</h:form>

and

public String chkMe() {
    return takeMeToAnotherPage("welcome");
}

public String takeMeToAnotherPage(String linkToGo) {
    return linkToGo + "?faces-redirect=true&username=" + name;
}

and additional view parameter in welcome.xhtml

<f:metadata>
    <f:viewParam name="username"/>
</f:metadata>

Another option is to instantiate another request scoped bean just in time and pass information to it

<h:form>
   <h:inputText value="#{helloBean.name}"/>
   <h:commandButton value="Welcome Me - Plain" action="welcome">
       <f:setPropertyActionListener value="#{helloBean.name}" target="#{welcomePageBean.username}"/>
   </h:commandButton>
</h:form>

with

@ManagedBean
@RequestScoped
WelcomePageBean {

    private String username;//+getter + setter
    //other fields associated with the welcome view

}

Using Flash object

Details entry view (fragment), base.xhtml:

<h:form>
    <h:outputText value="Enter user name for password reset: " />
    <h:inputText value="#{flash.username}" />
    <br/>
    <h:commandButton value="Send me a confirmation email" action="#{forgotBean.changePassword}" />
<h:form>

ForgotBean of base.xhtml:

@ManagedBean
@RequestScoped
public class ForgotBean {

    public ForgotBean() {   }

    public String changePassword() {
        //check user constraints and return failure outcome in case somthing is wrong
        //generate new password and persist it to the database
        //send a configmation e-mail
        return "successful-reset?faces-redirect=true";
    }

}

Success view (fragment), successful-reset.xhtml:

<h:outputText value="Password was reset for user #{receptorBean.username}, e-mail configmation sent." />
<br/>
<h:link value="View homepage" outcome="home" />

ReceptorBean of successful-reset.xhtml:

@ManagedBean
@RequestScoped
public class ReceptorBean {

    @ManagedProperty("#{flash}")
    private Flash flash;

    private String username;

    public ReceptorBean() {   }

    public String getUsername() {
        if(username == null) {
            String uname = (String)flash.get("username");
            flash.keep("inputText");
            username= uname;
        }
        return username;
    }

    public Flash getFlash() {
        return flash;
    }

    public void setFlash(Flash flash) {
        this.flash = flash;
    }

}
1
votes

I suppose the faces-redirect=true makes a http redirect, insted of a http forward. The redirect is handled by the browser, which sends a new http request. Since you are using RequestScoped Mbean, the new request will rendered with a new MBean. So use a SessionScoped Mbean or navigate without redirection.

1
votes

Maybe your bean is @RequestScoped therefore. Change it to Session scoped bean.

1
votes

Let's abstract from JSF and analyse the requirements you have from HTTP level.

URL must change to welcome.xhtml

It means when button is clicked two requests must be sent to server:

POST index.xhtml (with username parameter in the request body) - to submit user's input to server

GET welcome.xhtml - to get a new page

When server processes GET welcome.xhtml request there are two ways how to get the data:

  • Get the data from the client's request (only a way to pass data with GET request is to put it to URL)
  • Have it available on the server (we should have saved it on 'POST index.xhtml' processing)

Page must survive refresh

Refresh is just a repeat of last request. It means we are repeating GET welcome.xhtml request. And again we have the same two ways to get the data (from url and from the server).

URL must not contain data (username)

It means the data can't be retrieved from client's request. So only way to get the data is to store it on server first during 'POST index.xhtml' processing.

Multiple tabs with different data

Oops, the only solution survived this far cannot satisfy the last requirement. There is no way to differ requests from different tabs as tabs share the same browser session (same cookies, etc.). So requests from different tabs look exactly the same, there is no way to determing whethere user refreshed the page or copied the url and opened a new tab.

Conclusion

If you make the requirements that strict - it is not possible to implement what you want (it is not a limitation of JSF, it is how HTTP works). So you have to relax the requirements.

Drop multiple tabs support - and you can use Session scope.

Drop survive refresh - and you can use Flash scope.


And most interesting is relaxing the requirement to do not pass data in url.

Simplest you can do is to pass username parameter as is. You might say it is a security risk, but I would say that the risk is pretty the same as passing the username in POST index.xhtml request (just a way how parameter is passed differs a bit). Only additional security risk I see is that someone can pass by user's computer and pry username in browser's url bar.

More advanced approach would be to encrypt username and pass encrypted value in URL, and decrypt it on processing of welcome.xhtml.

And if we develop this approach further we will come close to so called Conversation scope (check out JBoss Seam long-running conversations). It is when you add to URL meaningless (from user perspective) parameter, and value of the parameter is some identifier which serves as a key for a data map on the server.

Dummy implementation of Conversation scope:

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

@ManagedBean
@SessionScoped
public class Bean implements Serializable {

    private int prevCid = 0;

    private Map<String, Object> conversationScope = new HashMap<String, Object>();

    public String getName() {
        return (String) getData();
    }

    public void setName(String name) {
        setData(name);
    }

    public String chkMe() {
        return takeMeToAnotherPage("welcome");
    }

    public String takeMeToAnotherPage(String linkToGo) {
        return linkToGo + "?cid=" + getCid() + "&faces-redirect=true";
    }

    private Object getData() {
        return conversationScope.get(getCid());
    }

    private void setData(Object o) {
        conversationScope.put(getCid(), o);
    }

    private String getCid() {
        HttpServletRequest req = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
        String cid = (String) req.getAttribute("cid");

        if (cid == null) {
            cid = req.getParameter("cid");
            if (cid == null) {
              cid = "" + prevCid++;
            }
            req.setAttribute("cid", cid);
        }

        return cid;
    }
}
1
votes

Example using Flash object

I don't actually understand what is not working. Below is a working example of a main-dependent views in which command button on the first view triggers a redirect to the dependent view and handles data transfer by using JSF Flash object. In the answer provided, the second page data (username) survives both postbacks and page refresh actions.

I will repost the code once again.

Main view:

<h:form>
    <h:outputText value="Enter your name: " />
    <h:inputText value="#{flash.username}" />
    <h:commandButton value="Reset my password" action="#{forgotBean.changePassword}" />
</h:form>

Forgot bean:

@ManagedBean
@RequestScoped
public class ForgotBean {

    public ForgotBean() {   }

    public String changePassword() {
        return "/dependent?faces-redirect=true";
    }

}

Dependent view:

<h:form>
   <h:outputText value="#{confirmBean.username}: your password has been changed and the confirmation has been sent to [get value from your bean]" />
   <h:commandButton value="Postback" />
</h:form>

Confirm bean:

@ManagedBean
@RequestScoped
public class ConfirmBean {

    @ManagedProperty("#{flash}")
    private Flash flash;

    private String username;

    public ReceivedBean() {   }

    public String getUsername() {
        if(username == null) {
            String uname = (String)flash.get("username");
            flash.keep("username");
            username = uname;
        }
        return username;
    }

    public Flash getFlash() {
        return flash;
    }

    public void setFlash(Flash flash) {
        this.flash = flash;
    }

}

Or, alternatively, you can do the preprocessing (validating user's name, collecting data like e-mail, etc.) in a @PostConstruct of the ConfirmBean. Also, you could do it @ViewScoped so that preprocessing won't happen on postbacks.

Kick-off example of your @PostConstruct method:

@PostConstruct
public void init() {
    //without managed property flash object is also available via
    //FacesContext.getCurrentInstance().getExternalContext().getFlash()
    String name = flash.get("username");
    flash.keep("username");
    //do necessary validations
    //get necessary data from your service
    //handle wrong user input
    this.username = name;
    //set up other data of the bean
    //change setter and getter of the field username to 'ordinary'
}

Example using session scoped bean

If you would like the bean to survive page refresh (thus, @ViewScoped fails), if you want to implement Post-Redirect-Get (thus, <f:setPropertyActionListener> fails), if you want to work on basically the same data, that is virtually undefined, in different tabs (thus, Flash fails), if you want to keep off meaningful get requests (thus, <f:viewParam> fails), then the only way that I see (staying within JSF) is to make use of a @SessionScoped @ManagedBean that will keep the user input data in a collection. This way, page refresh, postbacks, redirection, multi-tabs and meaningful view parameters restrictions are relaxed and will be enabled in your application.

Note that the provided code does not account for any exception handling / checks. And, what is more important and relevant, the incremented integer as a key in the map should be better substituted for a random value, like a UUID.

The views (m for index, d for welcome):

(m.xhtml):

<h:body>
    <h:form>
        <h:inputText value="#{forgotBean2.username}" />
        <h:commandButton value="Change my password" action="#{forgotBean2.changePassword}" />
    </h:form>
</h:body>

(d.xhtml):

<f:metadata>
    <f:viewParam name="id" required="true" />
</f:metadata>
<h:body>
    <h:form>
        <h:outputText value="#{confirmBean2.username}: your password has been changed" />
        <h:commandButton value="Postback" action ="#{confirmBean2.postbackAction}" />
    </h:form>
</h:body>

The beans (ForgotBean2 - for business action in m.xhtml, ConfirmBean2 - for display and (possible) actions in d.xhtml, UserRequestsBean - for storing information in the session):

(ForgotBean2):

@ManagedBean
@RequestScoped
public class ForgotBean2 {

    @ManagedProperty("#{userRequestsBean}")
    private UserRequestsBean userRequestsBean;

    private String username;

    public ForgotBean2() {   }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public UserRequestsBean getUserRequestsBean() {
        return userRequestsBean;
    }

    public void setUserRequestsBean(UserRequestsBean userRequestsBean) {
        this.userRequestsBean = userRequestsBean;
    }

    public String changePassword() {
        //do business job
        Map<Integer, String> fMap = userRequestsBean.getRequests();
        int id = 0;
        if(fMap.containsValue(username)) {
            for(Map.Entry<Integer, String> entry : fMap.entrySet()) {
                if(entry.getValue().equals(username)) {
                    id = entry.getKey();
                    break;
                }
            }
        } else {
            if(fMap.isEmpty()) {
                id = 1;
            } else {
                id = (int)Collections.max(fMap.keySet()) + 1;
            }
            fMap.put(id, username);
        }
        return "/q15038451/d?faces-redirect=true&id=" + Integer.toString(id);
    }

}

(ConfirmBean2):

@ManagedBean
@ViewScoped
public class ConfirmBean2 implements Serializable {

    @ManagedProperty("#{userRequestsBean}")
    private UserRequestsBean userRequestsBean;

    private Integer id;

    private String username;

    public ConfirmBean2() {   }

    @PostConstruct
    public void init() {
        if(id == null) {
            String vpid = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("id");
            id = Integer.parseInt(vpid);
        }
        Map<Integer, String> fMap = userRequestsBean.getRequests();
        String uname = fMap.get(id);
        //do necessary validations
        //get necessary data from your service
        //handle wrong user input
        this.username = uname;
        //set up other data of the bean
    }

    public UserRequestsBean getUserRequestsBean() {
        return userRequestsBean;
    }

    public void setUserRequestsBean(UserRequestsBean userRequestsBean) {
        this.userRequestsBean = userRequestsBean;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String postbackAction() {
        return null;
    }

}

(UserRequestsBean):

@ManagedBean
@SessionScoped
public class UserRequestsBean implements Serializable {

    private Map<Integer, String> requests = new HashMap<Integer, String>();

    public UserRequestsBean() {   }

    public Map<Integer, String> getRequests() {
        return requests;
    }

    public void setRequests(Map<Integer, String> requests) {
        this.requests = requests;
    }

}

As noted above, you might want to generate UUIDs (providing for unique and random values) instead of incremented Integers.

1
votes

When you are refreshing a page you are submitting a new request to a server. As HTTP protocol is stateless you can tell the sever that I am 'user1' by adding it in a URL as the URL will be unique across tabs.

If you dont want to add the parameter in URL , the server will maintain relationship between two requests by using session. You can then use the session to store variables etc.

In your case it wouldn't work because you dont want to share the data among tabs. The reason data is shared between tabs is because the session cookies are shared on tabs.

You need to disable the cookies and maintain the session using URL rewriting .

http://docstore.mik.ua/orelly/java-ent/servlet/ch07_03.htm

The Application server you are using should maintain the URL in jsf with sessionid appended to them , however while redirecting you may need to append session id to your URL.

    public String takeMeToAnotherPage(String linkToGo) {
           HttpServletRequest req = (HttpServletRequest)FacesContext.getCurrentInstance().
getExternalContext().getRequest(); 
           return linkToGo + "?faces-redirect=true&jsessionid=" 
+ req.getSession().getId();
        }

As your application is used on multiple tabs you can disable session cookies and use url rewriting to maintain session.

http://jf.omnis.ch/archives/2004/12/disabling-session-cookie-in-tomcat.html

https://community.jboss.org/thread/141685

The other option would be to use conversation scope where you begin a new conversation whenever user visits a welcome page.Your application will maintain two different conversation in two tabs.You can learn more about it over here ,

http://www.andygibson.net/blog/tutorial/cdi-conversations-part-2/

1
votes

When you are refreshing a page you are submitting a new request to a server. As HTTP protocol is stateless you can tell the sever that I am 'user1' by adding it in a URL as the URL will be unique across tabs.

If you dont want to add the parameter in URL , the server will maintain relationship between two requests by using session. You can then use the session to store variables etc.

In your case it wouldn't work because you dont want to share the data among tabs. The reason data is shared between tabs is because the session cookies are shared on tabs.

You need to disable the cookies and maintain the session using URL rewriting .

http://docstore.mik.ua/orelly/java-ent/servlet/ch07_03.htm

The Application server you are using should maintain the URL in jsf with sessionid appended to them , however while redirecting you may need to append session id to your URL.

  public String takeMeToAnotherPage(String linkToGo) {
           HttpServletRequest req = (HttpServletRequest)FacesContext.getCurrentInstance().
getExternalContext().getRequest(); 
           return linkToGo + "?faces-redirect=true&jsessionid=" 
+ req.getSession().getId();
        }

As your application is used on multiple tabs you can disable session cookies and use url rewriting to maintain session.

http://jf.omnis.ch/archives/2004/12/disabling-session-cookie-in-tomcat.html

https://community.jboss.org/thread/141685

The other option would be to use conversation scope where you begin a new conversation whenever user visits a welcome page.Your application will maintain two different conversation in two tabs.You can learn more about it over here ,

http://www.andygibson.net/blog/tutorial/cdi-conversations-part-2/

1
votes

ADD this code to your index page below body tag

<t:saveState value="#{helloBean}" />

for t tag lib use

<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%>

and also add tomahawk lib.

if you have no idea about savestate or param passing refer this link

this will help you basic steps to pass value. between pages in jsf.