Injecting a session-scoped bean in JSF2 - jsf

I'm having some difficulty interacting with a Session-scoped managed bean after a user programmatically logs into my web application.
BACKGROUND:
I have a [javax.enterprise.context.]Session-scoped bean named "SessionHelper" where I place a lot of information gathered from the user as he/she uses the application. In my logon page (which is NOT SessionScoped), Here's a sample of what I'm doing:
#Inject SessionHelper theHelper;
....
FacesContext theContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = theContext.getExternalContext();
HttpServletRequest theRequest = (HttpServletRequest) externalContext.getRequest();
....
theRequest.login(username, password);
....
theSession.method(dostuff);
After this section of code is executed, my application redirects into a protected directory and allows the user (based on roles) to perform their job functions.
When I attempt to "#Inject SessionHelper" into any of my protected resources, my understanding is that I should get the specific SessionScoped instance of SessionHelper that has the data set right after the call to login. This should be available to me for as long as the session (for that specific user) is valid. Unfortunately, the instance I'm getting has none of my "theSession.method(dostuff)" in it.
Am I fundamentally misunderstanding the scope here?
The only thing I could potentially see is that the initial #Inject into my login page is not carried over after the session has been created. If this is the case, is there a way to force a re-injection after the session is created?
As always, thank you very much for your help!!

Related

Transfer Java object between JSF pages

I tried to implement flash in JSF. It's working well if I have to transfer Java object between pages in one session. But I have a spacial case which I need to solve.
I have a submit form where user enters personal data. Then he opens second page which is used to open payment gateway(paypal) page. When the payment is applied Paypal redirects the user back to the web site in a new web page.
I need some way to transfer the Java object data between the first and the last page. Is there any solution?
I'm using JSF 2.2.6 with Tomcat 8.
I guess the redirect of paypal to your site create a new session, if this is the case all you have to do is create a DTO object and store into BD with session ID using serialization Java Serializable Object to Byte Array
But if paypal call on same session all you got to do is get the object from the session like this
FacesContext facesContext = getFaceContext();
Application app = facesContext.getApplication();
ExpressionFactory elFactory = app.getExpressionFactory();
ELContext elContext = facesContext.getELContext();
ExternalContext externalContext = facesContext.getExternalContext();
HttpSession session = (HttpSession) externalContext.getSession(true);
session.setMaxInactiveInterval(-1);
ValueExpression valueExp = elFactory.createValueExpression(elContext, expression, Object.class);
YourBean yourBean = (YourBean) valueExp.getValue("#{yourBean}");
The scope of this bean it must be #SessionScoped

Notify all #SessionScoped-Beans-Instances from all Users

My Problem
I have a #SessionScoped sessionInformationBean, which holds a Person-Entity from a logged in user. So, if a User logs in, I am looking up the corresponding Entity and put in in the #SessionScoped CDI Bean. This Bean is used to retrieve the current user (a Person-Entity) at any position in code, so that you can check, if it is a Admin or things like that.
#Inject
private PersonFacade personFacade;
private Person currentUser;
public Person getCurrentUser() {
if (currentUser == null) {
String loginname = FacesContext.
getCurrentInstance().
getExternalContext().getRemoteUser();
currentUser = personFacade.findByLoginname(loginname);
}
return currentUser;
}
But set the case, an Admin is giving this logged in user ( the Person-Entity) some Admin-Rights and saves him to the database. In this case, the Person at the #SessionScoped Bean is not updated, therefore the already logged in user is not seeing his Admin-Rights after a refresh of his page. Thats the problem. To avoid this problem I am fetching the user new from the database every access (There is no cache activated) to the #SessionScoped bean.
What I want
But I want to cache him and avoid a database access every time. So, I thought, if anyone saves a user, I will simply notice all sessionInformationBean-Instances and set the currentUser-Attribute to null. So, the next call, they fetch it again from database and cache it till its set to null again from my Person.save()-Operation.
What I tried
But that seems to be a little bit tricky. I thought I can handle it with CDI-Events, but they only will be pushed to the sessionInformationBean of the user, that is editing the other user.
Maybe something to do with my problem: CDI Events observed across sessions
Then I thought.. okay.. lets do it with Primefaces-Push. But the same thing.. the Events are just coming to my own sessionInformationBean.
EventBus eventBus = EventBusFactory.getDefault().eventBus();
eventBus.publish("/session", "test");
I thought the purpose of push and WebSockets is to notify all users or sessions.
What should I do?
So, the question is: How to access all instances of a specific #SessionScopedBean? I just want to access the sessionInformationBean from every logged in user and set the currentUserto null.
There's no built in way I can think of to do this. What I would recommend is to add an ApplicationScoped bean. Whenever your SessionScoped bean is created, register it with this app scoped bean. When you want to process this event iterate through all of these objects.
I'm curious though, what happens when you have multiple servers?

JSF I'm Losing Session State When Changing From Managed To CDI

Edit: This is an epic face palm situation. Wrong import for SessionScoped. So tired last night while checking it I was sure I was using enterprise sessionscoped import while I was still using faces sessionscoped import. I'm leaving this up as an aid to doofuses like me. :)
It is early in this project. After implementing up to this point with managed beans, I changed my managed beans to CDI beans as this seems to be the latest consensus on the best way to do things But this has broken previously working code. I cannot for the life of me figure out why. Help and advice is appreciated.
Happy Path (Summary... detail below code extracts)
If user not logged in, show login or register links.
If user logged in show user preferences or logout links.
Now Crappy Path with CDI (I don't blame CDI)
If user not logged in, show login or register links.
If user logged in still see login or register links. (bad, bad app)
The objects involved are
a facelet menu panel (with a primefaces login dialog... I don't think this has any thing to do with it but included for completeness) with render attributes if logged in or not,
a session scoped user bean,
a request scoped authentication bean to log the user in and out.
Objects used listed below. Implemented as CDI beans.
facelet
<h:panelGroup id="loginPanel" rendered="#{!user.loggedIn}">
Show login buttons and stuff
</h:panelGroup>
<h:panelGroup id="logoutPanel" rendered="#{user.loggedIn}">
Show logout buttons and stuff
</h:panelGroup
authentication bean
#Named(value = "webAuthenticationBean") //formerly managedbean
#RequestScoped
public class WebAuthenticationBean implements Serializable {
#Inject private UserBean user; //formerly a managed property which worked
...
request.login(uername, password);
user.setuserdata(username); // sessionscoped user state here used to check login state among other things later.
...
return(true) // they are now logged in
user bean
#Named(value = "user") //formerly managedbean
#SessionScoped
public class UserBean implements Serializable {
#EJB
private UserService userService; //stateless session bean
private userInfo = new UserInfo(); // keeps user state and can be used as a DTO/VO
#PostConstruct
public void init() {
//sets default state to "guest user". This is NOT a logged in state
}
public void setuserdata(String username){
userInfo = userService.getUserInfo(username);
// method called from WebAuthenticationBean
// sets the user state to a non-guest user (they're logged in).
// I can see in debug mode that this is being called and retrieving
// the user data from the database and setting "userInfo"
}
public void isLoggedIn() throws InvalidUserException{
// checks state to see if they are logged in, basically a bit more than are they still a guest or not
returns (true) if logged in
returns (false) if not logged in
// this worked with managed beans
}
...
So here is the actual use case when I watch in debug mode:
Happy Path (prior to change to CDI bean)
1) User navigates to the welcome page
2) the user bean is queried to see if they are logged in (user.loggedIn in the facelet).
3) userbean checks logged in state. If they are still a guest they aren’t logged in.
4) They are identified as a guest so isLoggedIn() returns false.
5) Login button is shown.
6) User requests logs in
7) authentication bean begins login process: request.login returns successfully
8) authenticationbean sets user data: user.setuserdata(username) returns successfuly.
9) authentication bean loginMethod returns (they are logged userprincipal on the server)
Alternate (crappy) path branch here (happy path continues)
10) The menu rechecks login state (user.loggedIn)
11) userbean checks for appropriate state and sees they are valid non guest user
12) userbean returns (true) they are logged in
13) menu shows logout button
Crappy Path (what happens after I changed these to CDI beans)
10) The menu rechecks login state (user.loggedIn)
11) userbean checks for appropriate state and sees they are a guest //the updated user state seems to have disappeared from this user in this session.
12) userbean returns (false) they are not logged in //but they are
13) menu shows login button // they can’t login anyway since the server already sees them as logged in, in this session (ServletException: Attempt to re-login while the user identity already exists).
Why using managedbeans would I be able to see the userbean maintain its data in session scope but with cdi beans it does not? I am stumped. I’ll switch back to managed beans if I have to, it isn’t a big issue, but I would like to find out what I messed up.
I added some debugging code in the init method of the UserBean, and it appears as if the system is treating the SessionScoped UserBean as if it were RequestScoped. That is it is initializing on every call.
#PostConstruct
public void init() {
if (userInfo == null) {
userInfo = new UserInfoDTO();
userInfo.setUserName("Guest");
List<String> guestGroup = Arrays.asList(CoreUserGroupType.GUEST.toString());
userInfo.setUserGroups(guestGroup);
System.out.println("UserBean.init INSIDE Init If Statement");
}
System.out.println("UserBean.init OUTSIDE Init If Statement");
}
If it were really acting like it was SessionScoped the userInfo object would not be null every time and the 'if' statement would not be executed every time. But it is executing on every call to UserBean. So this is at the crux of the problem. As a matter of fact if it acted like it were in session scope it would not hit the init method at all on every call as it would still be initialized.
Am I not creating a sessionscoped bean properly? It would appear so, but I don't see how. As mentioned, this code ran fine when defined as a managedbean.
changed to the correct sessionscoped import and all is well. nothing hurt but my pride.

Invalidate Session of a specific user

So for my webapp, if I remove a user that is currently logged in, and I want to invalidate his/her session. So that as soon as he/she refresh the page or navigate, they are no longer log in. The way I have now is that if a User logged in successfully, I will store the user object in my SessionScoped bean, and store the HttpSession to the Application Map. Below is my code
This is my SessionScoped bean
#PostConstruct
public void init() {
User user = UserDAO.findById(userId, password);
Map<String, Object> appMap = FacesContext.getCurrentInstance().
getExternalContext().getApplicationMap();
HttpSession session = (HttpSession) FacesContext.getCurrentInstance().
getExternalContext().getSession(false);
appMap.put(userId, session);
}
Is this a correct approach? If so, how do I clean up my application map?
Is this a correct approach?
There are basically 2 ways.
Store the HttpSession handle in the application scope by the user ID as key so that you can get a handle of it and invalidate it. This may work for a small web application running on a single server, but may not work on a web application running on a cluster of servers, depending on its configuration.
I would only store it in another map in the application scope, not directly in the application scope like as you did, so that you can easier get an overview of all users and that you can guarantee that an arbitrary user ID won't clash with an existing application scoped managed bean name, for example.
Add a new boolean/bit column to some DB table associated with the user which is checked on every HTTP request. If the admin sets it to true, then the session associated with the request will be invalidated and the value in the DB will be set back to false.
how do I clean up my application map?
You could use HttpSessionListener#sessionDestroyed() for this. E.g.
public void sessionDestroyed(HttpSessionEvent event) {
User user = (User) event.getSession().getAttribute("user");
if (user != null) {
Map<User, HttpSession> logins = (Map<User, HttpSession>) event.getSession().getServletContext().getAttribute("logins");
logins.remove(user);
}
}
I think you can use your approach (with some modifications proposed by #BalusC) plus some notification mechanism (to make it work in distributed environment). You can do one of the following:
Use a topic queue subscribed by all your servers. When you remove user from your admin panel the JMS message will be created and sent to the topic. Every server will be responsible for invalidating the user session if it exists on the particular server (if the session is referenced in servletContext map).
Implement some action to invalidate the user session and run this action on every server in the cluster (The admin panel should send HTTP request to every server).
Use JGroups and TCP reliable multicast.
All of these solutions are not simple but much faster than polling the DB server on every request.

JSF PhaseListener viewId always one behind

Im trying to prevent users to access special pages with a phaselistener. for that reason im trying to figure out on which page they try to access.
but my problem is, i only get the page they where before. not the actual page.
public void afterPhase(PhaseEvent event)
{
FacesContext fc = event.getFacesContext();
System.out.println("test1" + fc.getViewRoot().getViewId());
}
and same here
public void afterPhase(PhaseEvent event)
{
FacesContext fc = event.getFacesContext();
HttpServletRequest request = (HttpServletRequest) fc.getExternalContext().getRequest();
String uri = request.getRequestURI();
System.out.println("uri: " + uri);
}
why is that, and how do i get the pagename the user is trying to access? Not that one that they required one step before, or better the page they are coming from.
It is one step behind because that is the way sequence of HTTP POST request behaves. When you are navigating in JSF application via command buttons each request goes as a post request.
Since you are protecting some resources make sure they are accessed via HTTP GET than you will get exact view id, this can be achieved as
User directly hits the url from address bar of browser.
After a post of jsf app redirect it to the resource instead of simple JSF navigation. POST-REDIRECT-GET pattern falls into this have a look here.
If you are showing some messages after each POST, you might need Flash map for that, which is new feature in JSF2, if you are on JSF1.x hard luck, you can implement flash if you want to.
To conclude catch the view ids on HTTP GET request.
Hope this helps...

Resources