Synchronizing session scoped managed bean - jsf

I have 2 screens in my JSF 2.0 application. Screen 1 searches for students and lists students in a table. The student name in the results table is a link to the student details page (screen 2).
My managed bean:
#SessionScoped
public class TestController {
private Student studentOnUI; // Student being viewed on the UI
// synchronized public getters and setters
public String viewStudentAction(String studentId) {
this.studentOnUI = getStudentFromDB( studentId );
return "studentDetailedPage";
}
public synchronized String clearSearchAction() {
this.studentOnUI = null;
return "studentSearchPage";
}
screen 1 xhtml snippet
<!-- search fields -->
<h:commandButton
value="Clear Search"
action="#{testController.clearSearchAction()}"/>
<!-- Search button -->
<!-- search results table -->
Screen 2 xhtml snippet
<h:outputText value="#{testController.studentOnUI.name}" />
<h:dataTable
value="#{testController.studentOnUI.subjects}"
var="subject">
<h:outputText value="#{subject.score}"/>
</h:dataTable>
I face the following issue:
After running search, user clicks on
a student name (to move into screen
2)
When the render response phase of
screen 2 is in progress (which has
EL references to
testController.studentOnUI), He
clicks on clear button (without waiting for the first request to complete). Now the
thread handling the clear request
sets testController.studentOnUI to null and
the first thread that is in render
response phase throws
NullPointerException when it
evaluates
{testController.studentOnUI.*}.
Although the managed bean handles synchronization (correctly?) it does not solve the concurrency issue here because, the following could happen
Thread 1 (handling request to navigate to screen 2) - evaluates #{testController.studentOnUI.name} and happily renders the value. And then exits the testController.getStudentOnUI() synchronized method. So thread 1 no longer has the lock on the controller instance (in session scope). context switch happens.
Thread 2 (handling request to clear
search results) - does
testController.studentOnUI = null in
clearSearchAction(). context switch
happens.
Thread 1 - evaluates the next EL in the page (#{testController.studentOnUI.subjects}) and throws a NullPointerException (as testController.studentOnUI is now null).
Appreciate any pointers on what I am doing wrong with this approach or if a different approach needs to be adopted here.

this is really strange behaviour but then i would rather disable the link on the first page with JavaScript to make it "unclickable".
try with jQuery:
1 button: $('#button').attr("disabled", true);
2 link: $('a.something').click(function(e) {
e.preventDefault();
});
I'm not sure about the synchronization in the backing bean since the JSF Controller has to put the lock on your Student object, not the clear method. Am I wrong?

Related

Form not submitting on a page with fragment 'rendered' conditions and ui:param

I'm creating a some sort of online shop with JSF. I have a product.xhtml page that displays the product by ID. I'm passing that ID as a param (.../product.xhtml?id=3) and I'm getting the ID with <ui:param name="productID" value="#{request.getParameter('id')}" />
That all works well.
Next, I'm showing and hiding certain elements in the page with <f:subview> (I've used <ui:fragment> before). The reason is that if the user deletes the ?id=3 ID parameter, the page will show an error (eg. code <f:subview id="main" rendered="#{productID != null and productID != ''}">). Another reason is that if the product belongs to the buyer, the BUY button will not appear and if the user is not authenticated the BUY button will not appear.
The problem is with the BUY button. It is in a form and the action of the button is just a simple test method (for now) from the CDI bean that prints something to the server console and redirects the user. Unfortunately, this does not work. The page (/product.xhtml) gets reloaded with no ID param.
I've tried several things like this and this and nothing is working.
I've tried using the ViewScoped and SessionScoped for my CDI bean instead of RequestScoped, but that does nothing. The ViewScoped fails to build.
I've also changed the <ui:fragment> to <f:subview>
Here's some code..
CDI bean controller
#Named
#RequestScoped
public class ProductManager {
...
public String buy(Product product) {
FacesContext context = FacesContext.getCurrentInstance();
try {
HttpSession session = Util.getSession();
User buyer = (User)session.getAttribute("user");
Date date = new Date();
System.out.println("TEST DATA: ");
System.out.println("sale product: "+product.getTitle());
System.out.println("sale buyer: "+buyer.getUsername());
System.out.println("sale date: "+date);
}
catch(Exception ex) {
System.err.println("ProductManager#buy -> "+ex.getMessage());
}
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "An error occured", null));
return "index";
}
...
}
Products.xhtml
...
<ui:param name="productID" value="#{request.getParameter('id')}" />
...
<f:subview id="buyBtn" rendered="#{user.username != login.username and login.regUser}">
<h:form style="margin-top: 30px;">
<b:navCommandLink styleClass="btn btn-info btn-block" disabled="#{!product.status}" value="Buy" action="#{productManager.buy(product)}"></b:navCommandLink>
</h:form>
</f:subview>
...
I can provide full code if needed.
What I'm expecting the code to do is that whenever I click on the BUY button, I'll get redirected to my page and the TEST DATA will be printed on server console.
After many. many attempts I solved this. I cannot find the exact explanation, as I read a ton of articles on this topic.
I used OmniFaces. Next in products.xhtml I changed h:form to o:form (from OmniFaces) and have these two set to true includeRequestParams="true" includeViewParams="true". My bean then became org.omnifaces.cdi.ViewScoped
This is also a great article

How can be stopped automatic method call from #ManagedBean class at page refresh, JSF

In my XHTML page I'm calling a method from my #ManagedBean class. The method should be executed only when I click the link which to is linked the method:
<h:link value="Continue Reading ยป"
outcome="contracts/resources/data/imag2_facebook.html"
onclick="#{sessionFilter.incForLink2('contracts/resources/imagini/facebook.jpg','Facebook down? Current problems and status', 'contracts/resources/imagini/facebook.jpg')}" />
It's weird why the method is executed at every page refresh without clicking on the link. I want the method to execute only when I click on the link. I have also tried with h:commandLink but the results are the same. Do you have an idea what I need to change, or what is wrong? The #ManagedBean class is application scoped. The method I call is the following:
public void incForLink2(String link, String title, String imgAllLink) {
Article article = links.get(link);
if (article != null) {
Integer pageHits = article.getPageHits();
article.setPageHits(pageHits + 1);
System.out.println(link + " = " + pageHits);
} else {
Article article1 = new Article(1, title, link, imgAllLink);
links.put(link, article1);
}
}
onClick is a handler for JavaScript in the webbrowser! The Method you call is Java and executed on the server!
Java renders the Website before the website is sent to the webbrowser, in this phase is the method is invoked. After the sending to the webbrowser the javascript can be executed, this is a second phase. You simply need a new request to the server for executing java-methods. I prefer to use a4j:jsFunction.

Redirect my page after h:commandLink action in JSF [duplicate]

This question already has answers here:
Creating master-detail pages for entities, how to link them and which bean scope to choose
(2 answers)
Closed 7 years ago.
I have two pages:
listMedicalJourneys.xhtml
listAffectedEmployees.xhtml
From the first page:
<h:form>
<h:commandLink action="#{MedicalJourneyController.listAffectedEmployees()}" value="Manage">
<f:setPropertyActionListener value="#{element.id}" target="#{MedicalJourneyController.medicalJourneyId}" />
</h:commandLink>
</h:form>
The #RequestScoped bean #{MedicalJourneyController} has this method:
public String listAffectedEmployees() {
MedicalJourney m = medicalJourneyBean.getMedicalJourneyById(medicalJourneyId);
setMesList(new ArrayList<MedicalJourneyEmployeeService>(m.getMedicalJourneyEmployeeServices()));
setSelectedMedicalJourney(m);
return "listAffectedEmployees.faces?faces-redirect=true";
}
When I use the redirect to change URL, the next page doesn't show the selected value.
if i understand you right, you want to select an item inside one view, and display its SubItems in the next View. if this is the Case then, there are seviral ways to acheave this, here i will show you a way based on your Concept/Code above, so lets assume the following:
listMedicalJourneys.xhtml the View where you select an Item
listAffectedEmployees.xhtml the view Where you display SubItems of the preselected Item
and i assume that you have for each View its own Controller/ManagedBean,
so lets call the first one medicalJourneysManager and for the second view affectedEmployeesManager, both are requestScoped Beans
in the medicalJourneysManager your "selection Methode" action event should only get the selected ItemId, and pass this selected id to the next Page. Next Page Controller should then load the List of SubItems. Because a requestScoped Bean is only available during this single request, and as soon as you navigate anywhere, your bean will be reinitialized, that means the loaded data is lost at this moment.
so in your medicalJourneysManager define some ActionMethode like this:
//JSF 2.+
public String selectMedicalJourny(int medicalJourneyId) {
// do what ever you want before redirect, i.e. any validations, ...etc if required
return "listAffectedEmployees.faces?faces-redirect=true&medicalJourneyId="+medicalJourneyId;
}
this will redirect you to the next View where you display a list of AffectedEmployees
this new View needs its Controller "affectedEmployeesManager" to load the list of affectedEmployees.
so in the this managed Bean do something like this:
#PostConstruct
private void init(){
try{
String medicalJourneyId = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("medicalJourneyId");
// now do whatever you want with it, load its subNodes/affectedEmployeesList, ...etc
MedicalJourney m = medicalJourneyBean.getMedicalJourneyById(medicalJourneyId);
setMesList(new ArrayList<MedicalJourneyEmployeeService>(m.getMedicalJourneyEmployeeServices()));
setSelectedMedicalJourney(m);
}catch(Exception e){
e.printStackTrace();
}
}
a simpler alternative to commandLink is using a direct link with that param without any action commands.
here is a helpfull link.

h:commandButton action redirect to context root, possible?

Project context
I've created from scratch an URL rewriting with two major component :
public class URLFilter implements Filter
{
...
}
public class URLViewHandler extends GlobalResourcesViewHandler
{
...
}
The first class is used to forward clean URLs to the right view with an ID different for each page. The second class override the function getActionURL() so that h:form and ajax functionnalities continues to work.
Theses classes translate like this :
Real URL Internal URL
/ <-> page.jspx?key=1
/contact <-> page.jspx?key=2
/projects/management <-> page.jspx?key=3
etc
Current solution
My problem right now is for my user login and logout button :
<!-- Login button used if user is not logged, go to a secured page (which display error message). If he log with this button, the current page is reloaded and displayed properly. This button works perfectly -->
<h:commandButton rendered="#{pageActions.item.isPrivate}" value="#{msg.button_connect}" actionListener="#{userActions.onButtonLoginClick}" />
<!-- Login button used anywhere on public pages that redirect to user home after login, works perfectly since I haven't changed to clear url. -->
<h:commandButton rendered="#{not pageActions.item.isPrivate}" value="#{msg.button_connect}" actionListener="#{userActions.onButtonLoginClick}" action="userHome.jspx?faces-redirect=true" />
<!-- Logout button that works (it redirects at http://website.com/context-name/ but keep the ?key=1 at the end. -->
<h:commandButton value="#{msg.button_disconnect}" actionListener="#{userActions.onButtonLogoutClick}" action="page.jspx?key=1&faces-redirect=true" styleClass="button" style="margin-left: 5px;" />
My whishes
My question : Is there a better way to program the logout button since I need to redirect to context-root, currently I'm using the view name with the home page key but I would prefer 1. using a real path 2. not keep the ?key=1 at the url.
Thank you!
Final code
Based on BalusC answer, here is my final code to share to others :
#ManagedBean
#RequestScoped
public class NavigationActions
{
public void redirectTo(String p_sPath) throws IOException
{
ExternalContext oContext = FacesContext.getCurrentInstance().getExternalContext();
oContext.redirect(oContext.getRequestContextPath() + p_sPath);
}
}
<h:commandButton rendered="#{not pageActions.item.isPrivate}" value="#{msg.button_connect}" actionListener="#{userActions.onButtonLoginClick}" action="#{navigationActions.redirectTo(userSession.language.code eq 'fr' ? '/profil/accueil' : '/profile/home')}" />
Since I don't need the key when I have the path, it is even more better, thank you again BalusC to put me on the right track! Sent a small donation :)
This isn't possible with (implicit) navigation. The / is unfortunately not a valid JSF view ID.
Use ExternalContext#redirect() instead. Replace
action="page.jspx?key=1&faces-redirect=true"
by
action="#{userActions.redirectToRootWithKey(1)}"
with
public void redirectToRootWithKey(int key) throws IOException {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.redirect(ec.getRequestContextPath() + "?key=" + key);
}

JSF: why is empty test in rendered invoked during apply request values phase during form submission under request scoped POST REDIRECT GET

This question is spawned from the partial answer to JSF2: why does empty test in rendered of panelGroup in composite prevent action from being called?
In the following an Element is an #Entity with a name and id. A view.xhtml JSF page takes the id as a viewParam and uses setID(Long id) of the #ManagedBean #RequestScoped ElementController to trigger loading of the corresponding Element by id from database (that plays no further role in the question) and this found Element is set as the 'current' Element available (for historical reasons by a slightly different name) as Element getSelected().
The view.xhtml page performs a rendered attribute test #{not empty elementController.selected}, and has a h:commandButton with action that performs a faces-redirect, along with the id as query parameter, back to the view.xhtml page.
For some reason I do not fully understand, on form submission the test (and thus getSelected) is invoked in both the apply request phase and the process validations phase, before the viewParam id can be set (and thus before the current/selected Element can be found and set) in the update model values phase.
The greatly abbreviated view.xhtml page is:
<f:view>
<f:metadata>
<f:viewParam name="id" value="#{elementController.id}"/>
</f:metadata>
</f:view>
<h:body>
<h:form>
<h:panelGroup rendered="#{not empty elementController.selected}">
<h:outputText value="#{elementController.selected.name}"/>
</h:panelGroup>
<h:commandButton value="Apply" action="#{elementController.action}" />
</h:form>
</h:body>
(The sense of the form submission is lost above, but it does not matter for the this question.)
ElementController extends RequestController:
public void setId(Long id) {
log_debug("setId","id",id);
if (id != null) {
this.id = id;
T found = (T) getAbstractFacade().find(id);
if (found == null) {
String $error = "No object with id(" + id + ") found for class " + getManagedClass().getSimpleName();
log_error($error);
}
setCurrent(found);
}
}
public T getSelected() {
log_debug("getSelected","current",current);
if (current == null) {
log_warn("getSelected","null current Element");
}
return current;
}
public Object action() {
String $i = "action";
log_debug($i);
if (current==null) {
log_warn($i, "can't generate action outcome for null current element");
return null;
}
return "/view?faces-redirect=true&id="+current.getId();
}
Now on form submission, getSelected() happens to get called twice, and when current==null, once during the apply request values phases and once during the process validations phase, due to the test #{not empty elementController.selected} before the setting of the id (and thus loading of the Element entity) can occur thanks to the viewParam in the view.xhtml.
The question is, why is the rendered=#{not empty elementController.selected} invoked at all during the apply request phase and process validations phase ?
It is not invoked during those phases when I perform an initial GET load of the view.xhtml with id parameter, only during a form submission POST and subsequent redirect and GET.
The reason that the rendered attribute is consulted twice or more after a post back is because JSF traverses the component tree in each phase.
The name 'rendered' is perhaps not the best possible name, as it doesn't just make rendering of the component to which it applies conditional, but actually processing it in general.
It's consulted in the first place for 'apply request values' to see if that component and its children should be processed to have those request values applied to them. It's consulted again in 'process validations', since its value might have changed between phases.
It's not invoked 'during those phases when I perform an initial GET load', because when you perform a GET the component tree isn't traversed in those phases (only the metadata is processed, which is the reason view params are put in a special metadata section).
In order to make the id that you received from the GET request available in the action method after the post back, you'd best use the view scope (#ViewScoped) for your backing bean.

Resources