I've tried to implement the basic notification system for a basic social network with p:poll on view layer and a simple NotificationService class which gets the new notifications from DB and refreshes the notifications list of NotificationBean which is viewscoped for each user. Process flow similar to this:
-Poll calls NotificationBean.getNewNotifications for example every 15 sec.
--getNewNotifications calls NotificationService and DAO methods
---notificationList of user is refreshed
----DataTable on view layer shows new notifications
But the concern of p:poll is about it's performance because it sends a query at every interval expiration.
PrimeFaces has PrimePush which based on Atmosphere Framework, it opens web-sockets and seems like more suitable for creating notifications system.
But I don't know which components and which properties of them should be used. It has p:socket component with channel property. Should I use usernames as a channel values? Below code coming from PrimeFaces showcase and summarizes the last sentences:
<p:socket onMessage="handleMessage" channel="/notifications" />
As far as I understood from this showcase example this p:socket listens notifications channel. And pusher code snippet is:
PushContext pushContext = PushContextFactory.getDefault().getPushContext();
pushContext.push("/notifications", new FacesMessage(summary, detail));
But this will notify all user pages, I need a pusher which notifies specific user. Let say there are 2 users and assume that User1 adds User2 as a friend. There must be sth. like that:
pushContext.push("User2/notifications", new FacesMessage("friendship request", "from User1"));
But I am not sure this is the correct usage for this kind of functional requirement or not. Considering scalability of the app there can be expensive cost of opening so many channels per a process.
Thanks for helping.
PrimeFaces push supports one or more channels to push. To be able to create private channels for specific reasons; for example per user like in your case, you can create more than one channels. I had used unique ids for this purpose.
Basically, I've implemented a managed bean which is application scoped that handles user channel matching which should be considered. You can maintain it in different ways.
#ManagedBean
#ApplicationScoped
public class ChannelsBean {
Map<String, String> channels = new HashMap<String, String>();
public void addChannel(String user, String channel) {
channels.put(user, channel);
}
public String getChannel(String user) {
return channels.get(user);
}
}
Then inject this bean in your backing bean which sends notifications.
#ManagedBean
#SessionScoped
public class GrowlBean {
private String channel;
#ManagedProperty(value = "#{channelsBean}")
private ChannelsBean channels;
private String sendMessageUser;
private String user;
#PostConstruct
public void doPostConstruction() {
channel = "/" + UUID.randomUUID().toString();
channels.addChannel(user, channel);
}
public void send() {
PushContext pushContext = PushContextFactory.getDefault().getPushContext();
pushContext.push(channels.getChannel(sendMessageUser), new FacesMessage("Hi ", user));
}
//Getter Setters
}
You should give the channel value to p:socket. Here is the kickoff example of the page;
<p:growl widgetVar="growl" showDetail="true" />
<h:form>
<p:panel header="Growl">
<h:panelGrid columns="2">
<p:outputLabel for="user" value="User: " />
<p:inputText id="user" value="#{growlBean.sendMessageUser}" required="true" />
</h:panelGrid>
<p:commandButton value="Send" actionListener="#{growlBean.send}" />
</p:panel>
</h:form>
<p:socket onMessage="handleMessage" channel="#{growlBean.channel}" />
<script type="text/javascript">
function handleMessage(facesmessage) {
facesmessage.severity = 'info';
growl.show([facesmessage]);
}
</script>
For scalability issues, you should maintain the active or inactive channels. You can remove the one which is not in session or inactive for some time. Remove channels by using #PreDestroy annotation when beans are destroying. There is one channel for one user session in my solution.
My suggestion is; do not use usernames explicitly on the pages. It is not good for security reasons.
Related
A JSF page contains a list of bank accounts.
When the user clicks on the id of an account, a new page is displayed which allows to withdraw some money from the account. This new page has a parameter view of type Account with a converter to convert the id into an Account. The page displays information on the account and ask the amount to withdraw.
When the user submit the form to withdraw some money, the new balance is registered in the database with a stateless EJB (with a merge).
I would like to detect if another user modify the same account at the same moment but it is impossible because there is never an OptimisticLockException.
The explanation: if the version number (attribute annotated with #Version) is 5 at the begining, and if another user change the balance just after and submit the form right away, the version number is incremented to 6. I thought the first user would have an OptimisticLockException but it is not the case. Indeed, when the first user submits the form to change the balance, the form is rebuild on the server and the acccount is read again with the new version number equal to 6 and the version number change is not detected when the new balance is registered in the database with a merge (followed by a commit) in the EJB.
So the first user is not aware that another user changed the value of the balance while he was working on the account. It could be a problem.
How could I do to detect the concurrent change? Should I keep the version number in the code and compare with the version number just before the merge (with a pessimistic lock!)? Is there another better way to do that? Perhaps I have to change the structure of my pages?
My code (sorry, it's in French):
JSF page that modify an account (add or withdraw money):
<ui:composition template="./template.xhtml">
<ui:param
name="soustitre"
value="Ajouter/retirer de l'argent sur le compte de
#{mouvementBean.compteBancaire.nom}"/>
<ui:define name="metadata">
<f:metadata>
<f:viewParam name='id' value='#{mouvementBean.compteBancaire}'
converter='#{modifierBean.converter}' />
</f:metadata>
</ui:define>
<ui:define name="content">
<f:phaseListener type="jsf.util.DebugPhaseListener"/>
<h1>Ajouter/enlever de l'argent sur le compte de
#{mouvementBean.compteBancaire.nom}</h1>
<h:form id="form">
<h3>Type mouvement :</h3>
<h:selectOneRadio
id='typeMouvement'
value='#{mouvementBean.typeMouvement}'
required='true'
layout='pageDirection'
requiredMessage="Vous devez dire s'il s'agit d'un ajout ou d'un retrait">
<f:selectItem itemValue="ajout"
itemLabel="Ajout"/>
<f:selectItem itemValue="retrait"
itemLabel="Retrait"/>
</h:selectOneRadio>
<h:message for="typeMouvement"/>
<h3>Montant de la sommme</h3>
<h:inputText
id="montant" value='#{mouvementBean.montant}' required='true'
requiredMessage="Le montant doit être un nombre entier positif"/>
<h:message for="montant"/>
<br/><br/>
<h:commandButton action="#{mouvementBean.sauvegarder()}"
value="Enregistrer"/>
</h:form>
</ui:define>
</ui:composition>
The backing bean for the page:
Named(value = "mouvementBean")
#ViewScoped
public class MouvementBean implements Serializable {
#EJB
private GestionnaireDeCompteBancaire gestionnaireDeCompteBancaire;
private CompteBancaire compteBancaire;
private String typeMouvement;
#Min(value = 1, message = "Le montant doit être un entier positif")
private int montant;
public CompteBancaire getCompteBancaire() {
return compteBancaire;
}
public void setCompteBancaire(CompteBancaire compteBancaire) {
this.compteBancaire = compteBancaire;
}
public String getTypeMouvement() {
return typeMouvement;
}
public void setTypeMouvement(String typeMouvement) {
this.typeMouvement = typeMouvement;
}
public int getMontant() {
return montant;
}
public void setMontant(int montant) {
this.montant = montant;
}
public String sauvegarder() {
// Enregistre le mouvement dans l'entité
switch (typeMouvement) {
case "ajout":
compteBancaire.deposer(montant);
break;
case "retrait":
try {
compteBancaire.retirer(montant);
} catch (CompteException ex) {
Logger.getLogger(MouvementBean.class.getName()).log(Level.WARNING, null, ex);
Util.messageErreur("Solde insuffisant sur le compte",
"Solde insuffisant sur le compte de " + compteBancaire.getNom(),
"form:montant");
return null;
}
break;
}
// Enregistre dans la base de données
gestionnaireDeCompteBancaire.modifierCompte(compteBancaire);
// Message de succès
Util.addFlashInfoMessage(typeMouvement + " de " + montant + " effectué pour "
+ compteBancaire.getNom());
return "listeComptes?faces-redirect=true";
}
}
The converter (in another backing bean ModifierBean):
public Converter getConverter() {
return new Converter() {
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
// Pour faire des tests pour la concurrence
CompteBancaire c =
gestionnaireDeCompteBancaire.getById(Long.parseLong(value));
System.out.println("Dans le convertisseur à la fin de getAsObject " + c);
return c;
// return gestionnaireDeCompteBancaire.getById(Long.parseLong(value));
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
return ((CompteBancaire)value).getId().toString();
}
};
}
The link toward the page in the list of all the accounts:
<p:dataTable value="#{listeManagedBean.comptes}" var="item"
paginator="true" rows="5"
rowsPerPageTemplate="2,5,10,20">
<p:column filterBy="#{item.id}" filterMatchMode="exact"
sortBy="#{item.id}"
style="width: 5%; text-align: center;">
<f:facet name="header">
<h:outputText value="Id"/>
</f:facet>
<h:link outcome="mouvement" value="#{item.id}">
<f:param name="id" value="#{item.id}"/>
</h:link>
</p:column>
The problem is:
when the first user submits the form to change the balance, the form is rebuild on the server and the acccount is read again with the new version number equal to 6
The account should be loaded / reloaded only
When the user clicks on the id of an account, a new page is displayed which allows to withdraw some money from the account
And not when
user submits the form to change the balance
You should use at least a #ViewScoped bean to maintain the account loaded (and detached) between postback requests.
You should not re-load (or refresh) the account on postback requests.
These should be sufficient to raise a OptimisticLockException.
If you want mutual exclusion on load instead on save, use a PessimisticLock. In this case only the first user gets access to the account page, the second one will get a PessimisticLockException.
based on your comments, use ListConverter / ListIndexConverter or make your own converter extends ValueChangeConverter to avoid converter reloads.
UPDATE
first of all, make sure of this:
#Named(value = "mouvementBean")
#ViewScoped
public class MouvementBean implements Serializable
{
public class MouvementBean()
{
// log your bean creation to verify that it's not re-instantiated on postback:
// in some situation CDI annotations like #javax.inject.Named
// are not compatible with #javax.faces.bean.ViewScoped and your bean becomes
// RequestScoped. I don't think this is the case, but double check it. And if you
// don't need CDI just stick with #javax.faces.ManagedBean
}
}
second, f:viewParam is executed on postback too! so either use o:viewParam (OmniFaces, again :D) or try with a straightforward #PostConstruct solution (and remove f:viewParam from page):
#Named(value = "mouvementBean")
#ViewScoped
public class MouvementBean implements Serializable
{
#EJB
private GestionnaireDeCompteBancaire gestionnaireDeCompteBancaire;
private CompteBancaire compteBancaire;
#PostConstruct
public void init()
{
FacesContext facesContext = FacesContext.getCurrentInstance();
// OmniFaces, again and again :)
// String stringId = Faces.getRequestParameter("id");
String stringId = facesContext.getExternalContext().getRequestParameterMap().get("id");
long id = NumberUtils.toLong(stringId);
compteBancaire = gestionnaireDeCompteBancaire.getById(id);
// or you can do it with converter. But, generally, it's not a good practice to instance converters on your own, like you do
// in ModifierBean.getConverter(). Create a standalone converter class that implements javax.faces.convert.Converter and
// register it with #FacesConverter("compteConverter"), so you can delegate instantiation to Application
//
// Application application = facesContext.getApplication();
//
// compteBancaire = (CompteBancaire) application.createConverter("compteConverter").getAsObject(facesContext, null, stringId);
}
}
third, even if it actually works, you declare f:metadata in a wrong position. The correct position is this (f:metadata doesn't need to be 'included', it's not a regular component):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
...>
<f:metadata>
<f:viewParam name="id" value="#{mouvementBean.compteBancaire}"
converter="#{modifierBean.converter}" />
</f:metadata>
<ui:composition template="./template.xhtml">
<ui:param name="soustitre"
value="Ajouter/retirer de l'argent sur le compte de #{mouvementBean.compteBancaire.nom}"/>
...
</ui:composition>
</html>
Thanks Michele for your update. "f:viewParam is executed on postback too" was what I missed. As <f:viewParam> is considered as an input component, its value is convertered and validated in the validation phase of the life cycle of JSF.
I mark your answer as a good answer but I write this other answer to summarize our discussion for the future readers and to tell them there is also another solution.
So, there is 2 ways to solve my problem:
solution 1: the solution you explain in the update of your answer (I haven't tested it yet).
solution 2: use a <f:viewAction> instead of the converter, as I explained before. In the code I gave in my question you have just to remove <f:converter> and to add <f:viewAction> in the metadata to call the code that convert an id (a long) into a CompteBancaire. You also have to add a property for the id in the backing bean.
Solution 2 needs a viewscope for the backing bean to keep the value of the variable compteBancaire. I don't know if it is necessary for your solution. With my first code (the one which is in my question and which had a problem with concurrency) a request scope was sufficient (in my code you can replace #ViewScoped by #RequestScoped because the variable compteBancaire is initialized by the converter).
About your remarks:
I don't think f:metadata is in the wrong position. I use a template which contains a place (<ui:insert>) for metadata. In the pages which use the template, f:metadata will be in the good place (exactly like in the code you give in example).
I don't use #javax.faces.bean.ViewScoped; I use #javax.faces.view.ViewScoped from CDI so I don't think there could be a problem with #Named.
Why do you say that using a method to return a converter is not good? What type of problem could I have with this way of doing? Often I use a standalone class for a converter but returning a converter in a method facilitates the injection of an EJB (with #EJB).
Since this is bank account money, you need to have a very reliable solution. Maybe the best solution would be implementing long running transaction with locking the row in database so that anyone would not be able to select this row for updating. I mean on ui this would look bad to see message like "someone update the data blabla" which can occur if you implement optimistic lock approach. You should not allow user to see the page where he could update data at the time when someone else is updating this data.
Or if you choose optimistic locking then you will have to store the state of the object on server only in stateful bean (session) and implement retry logic to make all the checks necessary for payment or etc. and retry updating the object again with new version number. Also do not forget that all db updates should be in one transaction or you can get bad data. Use spring transactional annotations.
I'm building a simple POC to experiment with Faces Flow.
page 1: displays a list of companies. The user selects company A then goes to page 2.
page 2: on the selected company page, the user clicks a commandLink to start a wizard to create a new employee to be added to the company A.
Behing the scenes I've got a #FlowScoped("addNewUsertoCompanyFlow") bean MyFlowBean.
In its #PostConstruct method, the MyFlowBean needs to fetch an object corresponding to the company A from a service (#Inject).
What's the right way to let MyFlowBean know about the ID of the company A so that it can fetch it from the service ?
Thanks.
Ok, I came up with a solution. The key was not to use the flow backing bean #PostConstruct but rather use the flow initializer, where I can grab request parameters.
So I'm using some additional input in the form that will start my flow:
<h:form id="myForm" prependId="false">
<h:commandLink value="Enter myFlow" action="my-flow"/>
<h:inputHidden id="parameter" name="parameter" value="8"/>
</h:form>
In my flow definition I've defined an initializer for the flow, calling some method in the flow backing bean
#Produces #FlowDefinition
public Flow defineFlow(#FlowBuilderParameter FlowBuilder flowBuilder) {
String flowId = "my-flow";
flowBuilder.id("", flowId);
flowBuilder.initializer("#{myFlowBean.startFlow()}");
...
}
I've then grabbed the parameter inside the backing bean.
#Named
#FlowScoped("my-flow")
public class MyFlowBean implements Serializable {
public void startFlow() {
String parameter = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("parameter");
//now do sthg with the parameter, such as fetching data from an injected service
....
}
}
Of course it's also possible to do that at the flow definition level
flowBuilder.initializer("#{trainingFlowBean.startFlow(param['parameter'])}");
and just have a parameter in the startFlow method
public void startFlow(String parameter) {
...
}
On the Dashboard each User can see some Basic Stats. Lets take for example the "last login" Date. (But there are many more stats / values / settings to display)
The XHTML Files looks simplidfied like this:
<h:outputText value="statisticController.lastLoginDate()" />
The Bean itself uses #Inject to get the Session and therefore the current user:
#Named
#RequestScoped
public StatisticController{
#Inject
private mySessionBean mySession;
#PostConstruct
private void init(){
//load stats for mySession.currentUser;
}
}
Now, i want to generate a List where for example a certain role can view the values for ALL users. Therefore i can't use the Session Inject anymore, because the StatisticController now needs to be generated for multiple Users.
Having regular Classes this would not be a big problem - add the userEntity to the constructor. What is the "best practice" to solve this in JSF?
If i modify the StatisticController to something like this:
#Named
#RequestScoped
public StatisticController{
public void init(User user){
//load stats for user;
}
}
i would need to call init(user) manually of course. How can this be achieved from within a Iteration in the XHTML file?
I could refactor it so the valueLoading happens in the actual getter method, and iterate like this:
<ui:repeat var="user" value="#{userDataService.getAllUsers()}">
<h:outputText value="statisticController.lastLoginDate(user)" />
...
</ui:repeat>
But then i would need to load "every" value seperate, which is bad.
So a way like this would be "better":
<ui:repeat var="user" value="#{userDataService.getAllUsers()}">
statisticController.init(user);
<h:outputText value="statisticController.lastLoginDate()" />
...
</ui:repeat>
However this doesnt look very comfortable either. Further more doing things like this, will move nearly "all" Backend Stuff into the Render Response Phase, which is feeling wrong.
Any Ideas / Tipps how to solve this in a way that's not feeling "like a workaround"?
Create a new model wrapping those models.
public class UserStatistics {
private User user;
private Statistics statistics;
// ...
}
So that you can just use e.g.
public class UserStatisticsBacking {
private List<UserStatistics> list;
#EJB
private UserService userService;
#EJB
private StatisticsService statisticsService;
#PostConstruct
public void init() {
list = new ArrayList<UserStatistics>();
for (User user : userService.list()) {
list.add(new UserStatistics(user, statisticsService.get(user)));
}
}
// ...
}
(better would be to perform it in a new UserStatisticsService though)
with
<ui:repeat value="#{userStatisticsBacking.list}" var="userStatistics">
<h:outputText value="#{userStatistics.user.name}" />
<h:outputText value="#{userStatistics.statistics.lastLoginDate}" />
...
</ui:repeat>
An alternative to using a wrapped model proposed by BalusC is to store two separate lists with model data. With this approach you don't need to introduce modifications.
Following this route you'll be iterating over one list with <ui:repeat> and, ensuring equality of sizes of both lists, get second list element by index, which in turn is available via varStatus attribute that exports the iteration status variable.
<ui:param name="stats" value="#{bean.stats}/>
<ui:repeat value="#{bean.users}" var="user" varStatus="status">
<h:outputText value="#{user}/>
<h:outputText value="#{stats[status.index]}/>
</ui:include>
Population of lists may be done beforehand in PostConstruct method:
private List<User> users;
private List<Statistic> stats;
#PostConstruct
public void init() {
users = userService.list();
stats = statService.list();
}
This question already has answers here:
How to ajax-refresh dynamic include content by navigation menu? (JSF SPA)
(3 answers)
Closed 1 year ago.
I'm relatively new to JSF and trying to learn how current JSF 2 applications are designed. I've seen reference to single page applications that use ajax. Can someone fill me in on some of the techniques used and / or point me to a model or book? The books I've seen (JSF Complete Reference etc.) are good for basic tech issues but I can't find a source for current design techniques.
Thanks
Dave
In order to implement your Single Page Application, you should state which piece of your page should be rendered. This can be accomplished making use of a boolean flag such as create, edit, list, and so on. For instance, see the following (Just relevant code)
<h:body>
<h:form rendered="#{userController.stateManager.create}">
<h:panelGroup rendered="#{not empty facesContext.messageList or userController.stateManager.failure}">
<!--render error message right here-->
</h:panelGroup>
<div>
<label>#{messages['br.com.spa.domain.model.User.name']}</label>
<h:inputText value="#{user.name}"/>
</div>
<h:commandButton action="#{userController.create}">
<f:ajax execute="#form" render="#all"/>
<f:actionListener type="br.com.spa.web.faces.listener.StateManagerActionListener" />
<f:setPropertyActionListener target="#{userController.stateManager.create}" value="true"/>
<f:setPropertyActionListener target="#{userController.user}" value="#{user}" />
</h:commandButton>
</form>
</h:body>
Notice that our form will be rendered when a flag create is true - See second line above. To wrap our flags, we create a classe named StateManager as follows
/**
* I am using lombok, which takes care of generating our getters and setters. For more info, please refer http://projectlombok.org/features/index.html
*/
#Setter #Getter
public class StateManager {
private boolean create;
private boolean edit;
private boolean list;
}
Now, because we are using only a single page, we should use a ViewScoped managed bean, which keep our managed bean scoped active as long as you are on the same view - Is it a single page application, right ? So, no navigation. With this in mind, let's create our managed bean.
#ManagedBean
#ViewScoped
public class UserController implements StateManagerAwareManagedBean {
private #Inject UserService service;
private #Getter #Setter stateManager = new StateManager();
private #Getter #Setter List<User> userList = new ArrayList<User>();
private #Getter #Setter User user;
#PostConstruct
public void initialize() {
list();
}
public void create() {
service.persist(user);
stateManager.setCreate(false);
stateManager.setList(true);
stateManager.setSuccess(true);
}
public void edit() {
service.merge(user);
stateManager.setEdit(false);
stateManager.setList(true);
stateManager.setSuccess(true);
}
public void list() {
userList = service.list();
stateManager.setList(true);
}
}
For each action method, we define which piece of our page should be rendered. For instance, consider that our form was processed, covering all of JSF lyfecycle, which implies that their values was successfully converted and validated, and our action method invoked. By using as example our create action method - see above -, we set its create flag as false because our form was converted and validated, so we do not need to show it again (Unless you want). Furthermore, we set both list and success flag as true, which indicates that the list of our page should be rendered and our form was successfully processed - You could use this flag to show something like "User created" such as bellow
<h:panelGroup rendered="#{userController.stateManager.success}">
#{messages['default.created.message']}
</h:panelGroup>
Now, let's discuss which piece of our page should be rendered when it is called for the first time. Maybe you do not know but a void method annotated with #PostConstruct will be called first. So we define which piece of our page should be rendered. In our example, we call list method, which sets its list flag as true and populate a backing list.
#PostConstruct
public void initialize() {
list();
}
Finally, let's review the following order nested within h:commandButton
<h:commandButton action="#{userController.create}">
<f:ajax execute="#form" render="#all"/>
<f:actionListener type="br.com.spa.web.faces.listener.StateManagerActionListener" />
<f:setPropertyActionListener target="#{userController.stateManager.create}" value="true"/>
<f:setPropertyActionListener target="#{userController.user}" value="#{user}" />
</h:commandButton>
First of all, you should call an ActionListener - here called StateManagerActionListener - which takes care of resetting any StateManager - code bellow. It must be called first before any other setPropertyActionListener designed to control any flag because the order defined within h:commandButton is the order in which they will be called. keep this in mind.
public class StateManagerActionListener implements ActionListener {
public void processAction(ActionEvent e) throws AbortProcessingException {
Map<String,Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
for(Map.Entry<String,Object> entry: viewMap.entrySet()) {
if(entry.getValue() instanceof StateManagerAwareManagedBean) {
((StateManagerAwareManagedBean) entry.getValue()).setStateManager(new StateManager());
}
}
}
}
StateManagerAwareManagedBean - used in our ViewScoped Managed bean -, which allows that we reset any StateManager of any ManagedBean instead of resetting one by one in our ActionListener, is defined as follows
public interface StateManagerAwareManagedBean {
StateManager getStateManager();
void setStateManager(StateManager stateManager);
}
Second, after defining our ActionListener, we use a setPropertyActionListener which set the flag which controls the enclosing piece of the view as true. It is needed because our form is supposed to be not converted and validated. So, in our action method, we set this flag as false as discussed before.
A couple of notes
User is marked as a RequestScoped ManagedBean so that it can not be injected into a ViewScoped one using a ManagedProperty because its scope is shother. To overcome this issue, i set its value by using a <f:setPropertyActionListener target="#{userController.user}" value="#{user}"> - See our form
Our example use JEE features which need a proper Application Server. For more info, refer http://docs.oracle.com/javaee/6/tutorial/doc/
ManagedBean can play different roles such as a Controller, DTO and so on. When it play a role of a Controller, i prefer suffix its name with Controller. For more info, refer http://java.dzone.com/articles/making-distinctions-between
I'm wondering what the current approach is regarding user authentication for a web application making use of JSF 2.0 (and if any components do exist) and Java EE 6 core mechanisms (login/check permissions/logouts) with user information hold in a JPA entity. The Oracle Java EE tutorial is a bit sparse on this (only handles servlets).
This is without making use of a whole other framework, like Spring-Security (acegi), or Seam, but trying to stick hopefully with the new Java EE 6 platform (web profile) if possible.
I suppose you want form based authentication using deployment descriptors and j_security_check.
You can also do this in JSF by just using the same predefinied field names j_username and j_password as demonstrated in the tutorial.
E.g.
<form action="j_security_check" method="post">
<h:outputLabel for="j_username" value="Username" />
<h:inputText id="j_username" />
<br />
<h:outputLabel for="j_password" value="Password" />
<h:inputSecret id="j_password" />
<br />
<h:commandButton value="Login" />
</form>
You could do lazy loading in the User getter to check if the User is already logged in and if not, then check if the Principal is present in the request and if so, then get the User associated with j_username.
package com.stackoverflow.q2206911;
import java.io.IOException;
import java.security.Principal;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
#ManagedBean
#SessionScoped
public class Auth {
private User user; // The JPA entity.
#EJB
private UserService userService;
public User getUser() {
if (user == null) {
Principal principal = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
if (principal != null) {
user = userService.find(principal.getName()); // Find User by j_username.
}
}
return user;
}
}
The User is obviously accessible in JSF EL by #{auth.user}.
To logout do a HttpServletRequest#logout() (and set User to null!). You can get a handle of the HttpServletRequest in JSF by ExternalContext#getRequest(). You can also just invalidate the session altogether.
public String logout() {
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
return "login?faces-redirect=true";
}
For the remnant (defining users, roles and constraints in deployment descriptor and realm), just follow the Java EE 6 tutorial and the servletcontainer documentation the usual way.
Update: you can also use the new Servlet 3.0 HttpServletRequest#login() to do a programmatic login instead of using j_security_check which may not per-se be reachable by a dispatcher in some servletcontainers. In this case you can use a fullworthy JSF form and a bean with username and password properties and a login method which look like this:
<h:form>
<h:outputLabel for="username" value="Username" />
<h:inputText id="username" value="#{auth.username}" required="true" />
<h:message for="username" />
<br />
<h:outputLabel for="password" value="Password" />
<h:inputSecret id="password" value="#{auth.password}" required="true" />
<h:message for="password" />
<br />
<h:commandButton value="Login" action="#{auth.login}" />
<h:messages globalOnly="true" />
</h:form>
And this view scoped managed bean which also remembers the initially requested page:
#ManagedBean
#ViewScoped
public class Auth {
private String username;
private String password;
private String originalURL;
#PostConstruct
public void init() {
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
originalURL = (String) externalContext.getRequestMap().get(RequestDispatcher.FORWARD_REQUEST_URI);
if (originalURL == null) {
originalURL = externalContext.getRequestContextPath() + "/home.xhtml";
} else {
String originalQuery = (String) externalContext.getRequestMap().get(RequestDispatcher.FORWARD_QUERY_STRING);
if (originalQuery != null) {
originalURL += "?" + originalQuery;
}
}
}
#EJB
private UserService userService;
public void login() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext externalContext = context.getExternalContext();
HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
try {
request.login(username, password);
User user = userService.find(username, password);
externalContext.getSessionMap().put("user", user);
externalContext.redirect(originalURL);
} catch (ServletException e) {
// Handle unknown username/password in request.login().
context.addMessage(null, new FacesMessage("Unknown login"));
}
}
public void logout() throws IOException {
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
externalContext.invalidateSession();
externalContext.redirect(externalContext.getRequestContextPath() + "/login.xhtml");
}
// Getters/setters for username and password.
}
This way the User is accessible in JSF EL by #{user}.
After searching the Web and trying many different ways, here's what I'd suggest for Java EE 6 authentication:
Set up the security realm:
In my case, I had the users in the database. So I followed this blog post to create a JDBC Realm that could authenticate users based on username and MD5-hashed passwords in my database table:
http://blog.gamatam.com/2009/11/jdbc-realm-setup-with-glassfish-v3.html
Note: the post talks about a user and a group table in the database. I had a User class with a UserType enum attribute mapped via javax.persistence annotations to the database. I configured the realm with the same table for users and groups, using the userType column as the group column and it worked fine.
Use form authentication:
Still following the above blog post, configure your web.xml and sun-web.xml, but instead of using BASIC authentication, use FORM (actually, it doesn't matter which one you use, but I ended up using FORM). Use the standard HTML , not the JSF .
Then use BalusC's tip above on lazy initializing the user information from the database. He suggested doing it in a managed bean getting the principal from the faces context. I used, instead, a stateful session bean to store session information for each user, so I injected the session context:
#Resource
private SessionContext sessionContext;
With the principal, I can check the username and, using the EJB Entity Manager, get the User information from the database and store in my SessionInformation EJB.
Logout:
I also looked around for the best way to logout. The best one that I've found is using a Servlet:
#WebServlet(name = "LogoutServlet", urlPatterns = {"/logout"})
public class LogoutServlet extends HttpServlet {
#Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession(false);
// Destroys the session for this user.
if (session != null)
session.invalidate();
// Redirects back to the initial page.
response.sendRedirect(request.getContextPath());
}
}
Although my answer is really late considering the date of the question, I hope this helps other people that end up here from Google, just like I did.
Ciao,
Vítor Souza
It should be mentioned that it is an option to completely leave authentication issues to the front controller, e.g. an Apache Webserver and evaluate the HttpServletRequest.getRemoteUser() instead, which is the JAVA representation for the REMOTE_USER environment variable. This allows also sophisticated log in designs such as Shibboleth authentication. Filtering Requests to a servlet container through a web server is a good design for production environments, often mod_jk is used to do so.
The issue HttpServletRequest.login does not set authentication state in session has been fixed in 3.0.1. Update glassfish to the latest version and you're done.
Updating is quite straightforward:
glassfishv3/bin/pkg set-authority -P dev.glassfish.org
glassfishv3/bin/pkg image-update