I need a little direction understanding sessions in JSF (2.2.6). I've tried to find some documentation but am still missing something.
I have a #RequestScoped login bean which saves parameters in the session map for reference by other session scoped backing beans. They get the user info when they go through the PostConstruct method and everything works great.
However the logic fails when more than one window is used or the user doesn't logoff and goes directly back to the login page. JSF treats this as the same session and the #PostConstructs are not invoked.
I am pretty sure I could invalidate the session but that doesn't solve the problem of multiple users from different browser windows.
Any guidance or reference sites would be appreciated.
Thanks in advance!
John
session HAS to be same for every browser window, the only exception is when using anonymous mode: i.e. chrome behave like having two browsers opened at the same time.
another way to have multiple sesssions is to use different server name:
http://localhost:8080/app and http://127.0.0.1:8080/app may not share a single session.
however sessions never overlaps.
your problem, if i understand right, is when a logged user access login page and re-login, preserving his old session, that's why session beans are not PostConstructed again (independently from window used).
a general solution is to forbid access to login page for logged users.
and in general, container will throw an AlreadyAuthenticatedException or similar when user re-login without prior logout.
cut a long story short, just a preliminary example waiting for your code:
#ManagedBean
#SessionScoped
public class UserBean implements Serializable
{
private static final long serialVersionUID = 1L;
private User user;
public boolean isLoggedIn()
{
return user != null;
}
public void login(String username, String password)
{
// maybe you want to check isLoggedIn() and either call logout() or throw an exception
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
request.login(username, password);
user = someDAO.loadFromDatabase(username);
}
public void logout()
{
// maybe you want to check isLoggedIn() and either throw an exception or do nothing
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
request.logout();
user = null;
// suggested for another scenario
// request.getSession().invalidate();
}
// getters and setters
}
and
#ManagedBean
#SessionScoped
public class OperationBean implements Serializable
{
private static final long serialVersionUID = 1L;
#ManagedProperty("#{userBean}")
private UserBean userBean;
public void execute()
{
if(!userBean.isLoggedIn())
{
FacesContext.getCurrentInstance().getExternalContext().redirect("login.jsf");
return;
}
User user = userBean.getUser();
// do something
}
// getters and setters
}
with this combination, instead of using OperationBean's #PostContruct i used #ManagedProperty, so that OperationBean contains an always-up-to-date reference to user, without caring multiple re-logins.
Related
I have a requirement to keep the JSF 2.2 CDI conversation from expiring. I tried implementing a heartbeat mechanism where I click a 'hidden' button using Ajax which in turn calls a servlet. But the Conversation still expires. I set the timeout to 10s for testing purposes and my code is as shown below.
// The begin conversation method in my managed bean
public void beginConversation() {
if (conversation.isTransient())
{
conversation.setTimeout(10000);
conversation.begin();
}
}
// JQuery document ready function
$(document).ready(function() {
setInterval(function(){$.get("/HeartbeatServlet");}, 5000);
});
// Heartbeat servlet
#WebServlet("/HeartbeatServlet")
public class HeartbeatServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
{
System.out.println("Heartbeat Received");
request.getSession();
}
}
It prints the "Heartbeat Recieved" text every 5 seconds. But the conversation still expires.
The conversation timeout is how long the conversation will stay alive. It is not a permanent tracker of a conversation. It is not meant to be a keep alive on requests.
There is no extend conversation concept, however you can use begin(id) to recreate a conversation. Any bean state in that conversation will be lost.
We had the same problem. Adding "cid" to the request for the servlet did not help. In our experience, the heartbeat Servlet does not know about the Conversation. It will keep the current session going, but won't help with the Conversation.
Our solution for this was to add a viewscoped managedbean in which we inject the Conversation. On the UI we used PrimeFaces p:poll to periodically call a method on the managebean.
The method is just printing log statements.
#ManagedBean
#ViewScoped
public class ConversationListener implements Serializable {
...
/**
* Inject Conversation instance if it is available.
*/
#Inject
private Conversation conversation;
#Inject
FacesBroker facesBroker;
...
/**
* Method to refresh current conversation
*/
public void renewConversation() {
if (conversation == null) {
System.out.println("no conversation");
} else {
HttpServletRequest request = (HttpServletRequest) facesBroker.getContext().getExternalContext().getRequest();
log.info("*** ConversationListener called for conversation ID: {}", new Object[] { conversation.getId() });
log.info("*** ConversationListener called for session ID: {}", new Object[] { request.getSession().getId() });
}
}
}
I've found that keeping a conversation from expiring can be achieved by calling a method on the conversation scoped bean periodically with Primefaces' p:poll, configured to use a reasonable interval (5 min interval has worked for me), no fancy magic required.
this could be implementation specififc but if you start a conversation with begin() as you do, the framework should append a conversation id as a requestparameter. this is how the conversation is mapped to a request.
with weld as cdi implementation this should be "cid". i dont know how other implementations handle the conversation.
try to append the parameter to your heartbeat request and it should map the conversation and maybe refresh the timeout.
hope that helps
I am trying to construct a test website that display various information about a student with JSF 2.0, EJB 3.1 and JPA 2.0.
After a student login, the student can browse different pages for displaying different kind of information, which is what a usual registration management system does. Say displaying a timetable according to enrollment information in one page, and display the assignments in another page. Information that will be displayed include attributes of the student, mapped entities like registered course, assignment submitted etc.
At first, I tried to create a stateless bean and get the information required for each page, and change some attributes of student
#Entity
public class Student{
#Id
private String sid;
private String address;
#ManyToMany
private List<Assignment> submittedAssignments;
#ManyToMany
private List<Course> courses;
}
#Stateless
#LocalBean
public class studentDao {
#PersistenceContext(unitName="PU")
private EntityManager em;
public Student getStudent(String sid){
return em.find(Student.class, sid);
}
public List<Course> getCoursesByStudent(String sid){
return em.find(Student.class, sid).getCourses();
}
public List<Assignment> getAssignmentsByStudent(String sid){
return em.find(Student.class, sid).getSubmittedAssignments();
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void ChangeAddress(String sid, String newAddress){
em.find(Student.class, sid).setAddress(newAddress);
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void SubmitAssignment(String sid, Assignment submittedAssignment){
}
//Some more methods, just list a few for illustration.
}
#ManagedBean
#SessionScoped
public class LoginSession{
private String sid;
//getter
public login(String sid){
//probably will need password validation later, so I have to connect to db and check the password.
this.sid=sid;
}
}
#ManagedBean
#RequestScoped
public class AssignmentBean{
#EJB
private StudentDao studentDao;
#ManagedProperty(value="#{loginSession}")
private LoginSession session;
public List<Assignment> getAssignments(){
return studentDao.getAssignmentsByStudent(session.sid);
}
}
assignment.xhtml:
<h:datatable value="#{assignmentBean.assignments}"></h:datatable>
And then, I find it quite tedious to pass student from managed bean to stateless ejb, retrieve student, again and again, then return only a part of information to ManagedBean for display, therefore I think I can do it like this.
#Stateful
#LocalBean
public class studentDao {
#PersistenceContext(unitName="PU", type=PersistenceContextType.EXTENDED)
private EntityManager em;
private Student currentStudent;
public void login(String sid){
//didn't make a password, just type sid and done.
currentStudent = em.find(Student.class, sid);
}
public Boolean getLoggedIn(){
return currentStudent != null;
}
public Student getCurrentStudent(){
return currentStudent;
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void ChangeAddress(String newAddress){
currentStudent.setAddress(newAddress);
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void SubmitAssignment(Assignment submittedAssignment){
}
}
#ManagedBean
#SessionScoped
public class LoginSession{
#EJB
private StudentDao studentDao;
public login(String sid){
studentDao.login(sid);
}
}
assignment.xhtml:
<h:datatable value="#{loginSession.studentDao.currentStudent.submittedAssignments}"></h:datatable>
As the currentStudent lives with the extended persistence context, I can retrieve the information directly. And I do not have to pass SID to EJB again and again for each operation, also applies for finding that student again and again as it is already there managed.
I am not sure if it is an abuse of #Stateful bean as the pages had been a large group of conversations, unlike the common "shopping cart" example where user can choose a lot of orders, make payment, then commit all database changes after validation and user confirm.
Please comment on the above SFSB usage whether it is an abuse of SFSB and why if it is an abuse, or which one of the above two designs is better (or suggest another if both were bad as I am the first time writing a web application starting from scrap but a few books from Apress, those started with Pro whatever, EJB3, JPA, JSF, Java EE 6 with glassfish etc).
Thank you.
If you use stateful session beans, the EJB container manages state on behalf of your client, so there is no need to pass around an ID: It's more comfortable.
In your case it's OK.
Generally speaking, consider these items:
Stateless services are sometimes a little bit easier to test: If you write a test case, you do not have to consider the side-effects on the state.
If your stateful session bean holds cached data, you have to handle cache invalidation.
If your session bean is #Remote: If client and server are not on the same JVM, each method call requires marshalling, and goes over the network.
If you have lots of stateful sessions which consume resources on the server side: You already have two Lists there, and their number and their content will most likely grow over the time. Using a stateless setup, you can design an application where the state is only managed in the browser, for example.
If someone else is using your comfortable stateful API, and you must change it later, you will have endless discussions.
For these reasons I always prefer the stateless setup using data transfer objects. But again, in your case it's OK.
I still get confused about the PAGE and the CONVERSATION (temp) scope. Maybe I get some help here.
As fas as I know, variables outjected to the PAGE scope live as long as the user only postbacks the same page. The temporary CONVERSATION scope instead even survives a redirect to the next page.
Here is a little example with two effects that are confusing to me:
First, component and outjections are in CONVERSATION scope and the tempUser data is displayed in a jsf page. But in the save method called from that jsf-page, the injected tempUser is null. Why?
Second, if I do the same but change component and #In/#Outs scopes to PAGE scope, the tempUser gets correctly injected on postback - but gets not saved, for wathever reason, although even the super.update()-method on userHome gets called. Or is there a problem in using the homeEntities that ways (the idea iwa to use them only as DAO wrapper)?
#Name("userAction")
#Scope(ScopeType.CONVERSATION)
public class UserAction implements Serializable {
private static final long serialVersionUID = -4852371546895918692L;
#In(create = true)
private UserHome userHome;
#Out(scope = ScopeType.CONVERSATION)
#In(required = false,scope = ScopeType.CONVERSATION)
User tempUser;
#RequestParameter
private Long userId;
#Factory("tempUser")
public User getUser() {
if (tempUser == null) {
userHome.setUserId(userId);
tempUser = userHome.getInstance();
userHome.clearInstance();
}
return tempUser;
}
public void save() {
userHome.setInstance(tempUser);
userHome.update();
}
}
The xhtml contains a a:form with
<a:commandButton
id="update"
styleClass="button admin"
action="#{userAction.save}"
value="#{messages['user.action.update']}"/>
Thanks for replies. Sorry, if this is two problems in one.
I would like to integrate ice push into my web application.
I have a page(placeinfo.jsf) shows information of a place and ui:include weather.jsf for weather information of the place.
Users access to the page at http://xxx.com/xxx/placeinfo.jsf?place=california.
Simple example of code,
placeinfo.jsf
1) ice:output value="placeInfoBean.population"
2) ice:output value="placeInfoBean.language"
3) ui:include src="./weather.xhtml"
weather.jsf
1) ice:output value="weatherBean.humidity"
2) ice:output value="weatherBean.visibility"
PlaceInfoBean.java
#ManagedBean(name="placeInfoBean")
#RequestScoped
public class PlaceInfoBean
{
String population;
String language;
#PostConstruct
public void init()
{
HttpServletRequest request= (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
String place = request.getParameter("place");
setPopulation(PlaceInfoDao.getPopulation(place));
setLanguage(PlaceInfoDao.getlanguage(place));
}
}
WeatherBean.java
#ManagedBean(name="weatherBean")
#RequestScoped
public class WeatherBean
{
String humidity;
String visibility;
#PostConstruct
public void init()
{
HttpServletRequest request= (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
String place = request.getParameter("place");
setHumidity(WeatherDao.getHumidity(place));
setVisibility(WeatherDao.getVisibility(place));
}
public WeatherBean()
{
PushRenderer.addCurrentSession("weather");
}
}
I have another page to update the weather, and the method calls
PushRenderer.render("weather");
WeatherBean actually did a refresh, postconstrust method run again, but found there is no request param of "place" which suppose to be "california", and the page doesn't work properly then.
Question:
1) May I know besides session, how can the page remembers the value before PushRenderer did something?
2) Is it a proper way to get the request param for WeatherBean?
or request param should be passed by ui:param from placeInfo.jsf?
How to get the value of ui:param in WeatherBean?
Thank you!
Have you tried to set the managed beans as #SessionScoped? Or #ApplicationScoped? Did it work?
I have a problem I don't understand: Behind any View I have a controller ManagedBean that is RequestScoped and a data ManagedBean, that holds the data for the view and is SessionScoped.
So there are two views, which are login with loginData and loginController and overview with overviewData and overviewController.
The functionality should be like that:
The User logs into the application (loginController method)
If Authentication is successfull, there is a redirect to overview.xhtml (again in loginController method)
Then the overviewData gets its data by the overviewController, which retrieves them from business logic layer
The overview.xhtml shows the retireved data
So, the point is that I want to fill overviewData out of loginController, right after login! (???or if possible right befor overview view is constructed, if possible???).
I tried it with managedProperties, but the one I initiate in loginController is a different object than the managedProperty in overviewController, although they have the same name! How is that possible.
Oh boy, I doubt you guys understand what I mean, so I need to post some code:
LoginController.java
...
#ManagedBean
#RequestScoped
public class LoginController {
#ManagedProperty(value = "#{overviewData}")
private OverviewData overviewData;
OverviewController overviewController;
public LoginController(){
overviewController = new OverviewController ();
}
String login() throws Exception {
UsernamePasswordToken token = new UsernamePasswordToken(loginData.getName(), loginData.getPw().trim());
try {
currentUser.login(token);
overviewController.fillProjects();
...
OverviewController.java
...
#ManagedBean
#RequestScoped
public class OverviewController {
#ManagedProperty(value = "#{overviewData}")
private OverviewData overviewData;
public void fillProjects(){
if(overviewData == null){
overviewData = new OverviewData();
}
overviewData.setProjects(projectService.getProjects()); //retrieves data from business logic
}
...
OverviewData.java
...
#ManagedBean(name = "overviewData")
#SessionScoped
public class OverviewData {
private List<ProjectDTO> projects; //that's what the view needs to display the overview
public void setProjects(List<ProjectDTO> projects) {
this.projects = projects;
}
...
I hope that helps to show my problem, if you don't understand it, pls ask in a comment..
Would be nice if you can help me :-)
Cheers...
You're creating beans yourself using new instead of letting JSF do the job.
overviewController = new OverviewController ();
and
overviewData = new OverviewData();
This is wrong. JSF won't utilize any beans which you've created yourself this way. Remove those lines and add another #ManagedProperty on overviewController inside LoginController (and make the property private).
#ManagedProperty(value="#{overviewController}")
private OverviewController overviewController;
JSF will create the bean itself and set it as managed property directly after parent bean's construction. You just have to access it the usual Java way (without the need for nullchecks).