Doing a CRUD, I have a RequestScoped Ticket bean.
I have an XHTML page that until now I have been using to create the new Tickets; the ticket fields are populated directly (#{ticket.description} and so on)
Now I have a search form that lists the tickets, and with each ticket a link with the ticket id as a parameter. I want the backing bean to retrieve the ticket bean from EJB/JPA (already done) and put it into the request. I see 3 ways to do so:
Copy the data from the bean retrieved from JPA into the bean provided by injection. Ugly / prone to omissions.
Use ExternalContex#getRequestMap and put the bean there myself. Does not look very proper. Am I right?
Include my ticket bean into another bean class so I can do myNewBean.setTicket(ticketFromJpa);. Seems the best of the options, yet I do not like having to prefix all my EL in the page just for this.
There is out there any cleaner, more proper way of doing what I want?
Thanks in advance.
UPDATE:
To reword what I want, with a little more information. I have a commandLink in page P1, that calls action A in backing bean B1. B1 does its logic and redirects to page P2.
I wanted to set a (request scoped) bean B2 in action A, and that B2 would be available to draw P2.
From experiments, I have found that after leaving action A the framework creates a new B2 request scoped bean, so it looks like that the request scope is shorter than I expected.
The flash scope propesed by Damian looks like it works more like I want, but forces me to redesign the page around #{flash} and that (when I want to use it to create a new bean) I must also add the bean to the flash in an action method (currently it justs goes to the page and the managed bean is available)
I expected a Request scoped bean to be maintained since
To view the ticket in another page, you could do one of the following:
1) use h:link with a f:param containing the ID of the ticket
<h:link value="#{ticket.description}" outcome="/viewTicket" >
<f:param name="id" value="#{ticket.id}" /
</h:link>
Then in the viewTicket.xhtml (or whatever you called the page) you read the ID parameter, and get the ticket from JPA.
This is how the managed would basically look like:
#ManagedBean
#ViewScoped
public class ViewTicketMBean implements Serializable {
private String ticketId;
private Ticket ticket;
#PostConstruct
public void init() {
ticketId = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("id");
// read ticket from JPA by ID
}
}
2) instead of h:link, if you don't want to expose ticket ID, you can use a commandLink, which before navigating to viewTicket.xhtml, gets the ticket from JPA and puts it in flash scope. Then, in viewTicket you get the ticket from the flash scope. This is how the action method of the commandLink couldl look like
Ticket ticket = null;
// get ticket from JPA
FacesContext.getCurrentInstance().getExternalContext().getFlash().put("ticket", ticket);
return "/viewTicket";
Related
I've looked at some other threads here and I think I have the general idea of how to use a SessionScoped bean for maintaining a user's logged in state. However, as I am relatively new to JSF 2, I am trying to figure out the best way to use the bean during login and on subsequent pages to render a header/footer (using same page template, but different menus/links depending on login state and non-logged in state).
I have a typical JSF2 login form that calls a backing bean (request scoped) login() method. All of that works, and I am going to tie it in to the container security soon as I have a little more time to work on and figure that part out (realms, roles, etc).
In the old pre-JSF (jsp/struts) days, upon login, I'd add an object, usually a user ID Long value that would be easy to replicate across a cluster of sessions and restore a full user object from. This kept the httpsession data minimal (one Long value per user), and regardless of what server a user was routed, I could determine they were logged in and pull up their user object and go from there.
With JSF, I am not sure the right way to do this. I have a SessionScoped bean with a Long userid property. Upon the login() method succeeding, I use the ExternalContext to add the attribute of the SessionScoped object, something like:
User user = loginBean.login(username, password);
Session session = new Session();
session.setUserid(user.getId());
externalContext.getSessionMap().put("usersession", session);
externalContext.redirect(originalURL);
The Session is the SessionScoped bean, and it's CDI name is usersession. I hope that is the right way to stick the bean into the HttpSession so it can be used on pages, etc with EL.
The first thing I am not sure of is because the bean is SessionScoped, do I need to put it into the session after creating it in the above code? Or is that done automatically upon creation for me since it's scoped as session?
The second question is.. being picky that I am, I don't want a Session object in the session until the user logs in, even if the userid is null. So on a xhtml page, if I have something like:
<h:panelGroup render="#{usersession.loggedin}"...>
Will that create and put the Session bean into the session the first page that uses that? Or will that Session object not be put into the HttpSession until I do so with my code above? My guess is, if it gets put into the session upon creation, then the use of it on any page will create it and stick it in the session. I am less bothered by this..the object with just the Long property is very little memory use per user on the system, but like I said, I am being picky and more so wanted to understand exactly when a SessionScoped object gets stored into the session.
Thanks.
The first thing I am not sure of is because the bean is SessionScoped, do I need to put it into the session after creating it in the above code? Or is that done automatically upon creation for me since it's scoped as session?
The managed bean is autocreated in the given scope if referenced for the first time in EL by #{beanName} while it's not in the scope yet. So, no, you don't need to do it yourself. Just inject it as managed property in your login bean.
#ManagedProperty("#{usersession}")
private Session session;
// ...
User user = loginBean.login(username, password);
session.setUserid(user.getId());
externalContext.redirect(originalURL);
Will that create and put the Session bean into the session the first page that uses that? Or will that Session object not be put into the HttpSession until I do so with my code above?
It will do that once you reference it for the first time in EL by #{beanName}.
Unrelated to the concrete question, another way is
User user = loginBean.login(username, password);
externalContext.getSessionMap().put("user", user);
externalContext.redirect(originalURL);
and check for the logged-in user as follows
<h:panelGroup rendered="#{not empty user}">
See also:
Performing user authentication in Java EE / JSF using j_security_check
Is there a way to display a specific JSF page based on the request URL?
Let's say I have a JSF page "details.xhtml". The managed bean "detailsBean" has a list of objects where each object has its own ID. Now if a user requests the page "../details.xhtml?id=1", the list should be queried for an object with ID 1 and the resulting details page of this object should be displayed.
I already wrote a converter implementation class which can convert from object to ID and vice versa, but I don't know how to use it properly. Do I have to work through the JAX-RS specification for this to work or is there a more simple solution?
In JSF you can do this by using a so-called view parameter. You declare these in the metadata section of your Facelet:
<f:metadata>
<f:viewParam name="id" value="#{yourBean.yourObject}" label="id"
converter="yourObjectConverter"
/>
</f:metadata>
This will grab the URL parameter id from the request URL. E.g. if you request the page this appears on with localhost:8080/mypage.jsf?id=1, then 1 will be handed to the yourObjectConverter and whatever this converter returns will be set in yourBean.yourObject.
Your backing bean will thus get the converted object. No need to pollute your backing bean over and over again with the same query code.
#ManagedBean
public class YourBean {
private SomeObject someObject;
public void setYourObject(SomeObject someObject) {
this.someObject = someObject;
}
}
If your backing bean is view scoped, you may want to use the OmniFaces variant of viewParam instead, since otherwise it will needlessly convert after each postback (if your converter does a DB query, you definitely don't want this).
Working full examples:
http://code.google.com/p/javaee6-crud-example/source/browse/WebContent/user_edit.xhtml
http://code.google.com/p/javaee6-crud-example/source/browse/src/backing/UserEdit.java
Further reading:
Communication in JSF 2.0 - Processing GET request parameters
Stateless vs Stateful JSF view parameters
You can achieve this with plain JSF with the following steps
Capture the ID in the request to determine what object is being queried for in your DetailsBean from the request parameter. There are many ways to achieve this, one of which is adding the following annotation to your managed bean (this is currently only permitted for a #RequestScoped bean, see why here).
#ManagedProperty(value="#{param.id}")
int requiredObjectId;
The annotation above will capture the id parameter from the request and assign it to the requiredObjectId variable.
Using the captured Id, setup your object in your bean in a #PostConstruct method
#PostConstruct
public void queryForObject(){
//use the requiredObjectId variable to query and setup the object in the backing bean
}
The object retrieved should be assigned as an instance variable of your managed bean
In your view, you could then reference the queried object that has been setup in the backing bean
<h:panelGrid columns="2">
<h:outputText value="Title"/>
<h:outputText value="#{detailsBean.selectedObject.title}"/>
</h:panelGrid>
If your bean is in a scope broader than the request scope, you'll need a combination of constructs to cleanly pull that request parameter before view rendering.
Capture the request parameter within the JSF view itself using
<f:metadata>
<f:viewParam name="id" value="#{detailsBean.requiredObjectId}" required="true" requiredMessage="You must provide an Object Id"/>
</f:metadata>
**OR**
Due to the nature of JSF Lifecycle processing, doing the above alone may not make the value available for your use in time for object setup. You could use the following instead.
<f:metadata>
<f:event type="preRenderView" listener="#{detailsBean.setObjectId}" />
</f:metadata>
What we've done here is specify a method (that captures the id) in the backing bean that must be executed before the view is rendered, ensuring that the id parameter is available as at the time you need it. Proceed with step 3, only if you're using <f:event/> above.
In the backing bean, you now define the setObjectId method
public void setObjectId(){
Map<String,String> requestParams = FacesContext.getExternalContext().getRequestParameterMap();
requiredObjectId = Integer.parseInt(requestParams.get("id"));
}
Note that the above option is generally a work around/hack and not a clean solution as such
Please note: This question is about CDI scopes as we are using CDI scopes in the app and not JSF scopes.
1) Controller Bean (TestController.java) which is in RequestScoped (enterprise context) is called index_cut.xhtml, when we come for first time on this page.
2) On button “Load”, we load the following method to populate the sapFinancialPeriodList which works fine and displays the data
3) After changing the content on the page and submitting, the sapFinancialPeriodList appears as NULL in the following method –
Any suggestions?
Your bean is request scoped and you're loading the data model on action only instead of on (post)construction. When the HTTP response after the action which loaded the data is finished, then the bean is garbaged. The subsequent request would get a brand new instance of the bean with all properties set to default. However, as the same data model isn't been preserved during (post)construct, it remains empty.
In JSF2 you'd solve this with using #ViewScoped. This way the bean will live as long as you're interacting with the same view by postbacks (which return null or void).
In CDI you'd need to solve this using #ConversationScoped, which in turn requires some additional #Inject Conversation boilerplate, complete with begin() and end() calls at the right moments. For a concrete example, see also What scope to use in JSF 2.0 for Wizard pattern?.
An alternative is to pass the parameters responsible for creating the data model to the subsequent request via <f:param> in the command link/button as follows
<h:commandButton value="save" ...>
<f:param name="period" value="#{bean.period}" />
</h:commandButton>
and then recreate exactly the same data model in (post)constructor of the request scoped bean as follows
String period = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("period");
List<SapFinancialPeriod> sapFinancialPeriodList = someservice.list(period);
(the above is by the way nicer to solve with #ManagedProperty if you were using standard JSF; as far as I know CDI doesn't have an annotation which enables you to set a HTTP request parameter as a bean property)
See also:
How to choose the right bean scope?
Unrelated to the concrete problem, the upcoming JSF 2.2 solves this functional requirement in a nicer way using the new "Faces Flow" feature with the new #FlowScoped annotation and the new xmlns:j="http://java.sun.com/jsf/flow" tags.
I have a multi-page form, aka a Wizard pattern, where Page 1 corresponds to Step 1 of the wizard form, Page 2 corresponds to Step 2, etc. Each page other than the last has a Next button on it that takes you to the next page in the form. The final page has a submit button that submits all the data for all pages in the wizard.
What scope should I use to maintain the state of the data entered on each form? e.g. should I use a View Scoped bean that holds all the data entered on all pages? Will that work since I'll be navigating to different pages (Which I believe are considered to be different "views"; and if they're different views, I believe the View Scoped data will be lost when you navigate to the next page in the wizard)
I believe the View Scoped data will be lost when you navigate to the next page in the wizard)
That's correct. The view scope lives as long as you're interacting with the same view and get trashed whenever a new view get created. You're looking for the "conversation scope". This isn't available by any of the JSF managed bean scopes. This is however available by CDI #ConversationScoped. So if your environment happen to support CDI, you could make use of it:
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.inject.Inject;
import javax.inject.Named;
#Named
#ConversationScoped
public class Wizard implements Serializable {
#Inject
private Conversation conversation;
#PostConstruct
public void init() {
conversation.begin();
}
public void submitFirstStep() {
// ...
}
// ...
public String submitLastStep() {
// ...
conversation.end();
return "someOtherPage?faces-redirect=true";
}
// ...
}
The conversation is managed by the automatically inserted cid request parameter.
If you'd like to stick to the JSF view scope, then your best bet is to create a single page wherein you render the multiple steps conditionally:
<h:panelGroup rendered="#{wizard.step == 1}">
<ui:include src="/WEB-INF/wizard/step1.xhtml" />
</h:panelGroup>
<h:panelGroup rendered="#{wizard.step == 2}">
<ui:include src="/WEB-INF/wizard/step2.xhtml" />
</h:panelGroup>
<h:panelGroup rendered="#{wizard.step == 3}">
<ui:include src="/WEB-INF/wizard/step3.xhtml" />
</h:panelGroup>
Or, you could use a 3rd party component library like PrimeFaces which has a <p:wizard> component for exactly this purpose.
From my pov, good choice here is session scoped beans. When needed, user will be able to interrupt the wizard, visit other pages, doc, manuals, whatever, and get back to the same wizard step. Of course it can be done via view-scoped beans (see BalusC answer). Personally I prefer view-scoped beans when ajax is heavily involved. In that case I'd recommend to combine these two scopes.
You can find an example using the conversation scope for creating a wizard at this site:
JEE6 – CDI and Conversation Scope
I have a page with datatable with product information from which at a product selection action I redirect to product info page passing a parameter:
configurableNavigationHandler.performNavigation("productInfo?faces-redirect=true&prId=" + selectedCpl.getP().getPrId());
In my viewscoped bean in my init method I get the request parameter and fill the objects needed:
#ManagedBean
#ViewScoped
public class ProductInfo implements Serializable {
private Product p;
private Integer prId;
#PostConstruct
private void init() {
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
if (request.getParameter("prId") != null) {
prId = Integer.parseInt(request.getParameter("prId"));
p = pf.find(prId);
On my productInfo.xhtml I have a product info view and a dialog to edit the product info but when I press submit my the request parameter is null:
<p:commandButton styleClass="button-edit" value="Submit" actionListener="#{productInfo.saveProduct()}" update="prodInfo" oncomplete="dlg.hide();"/>
I'm using jsf 2.0 with primefaces elements.
Can anyone help me? Thank you.
That's not a session parameter. That's a request parameter. That it's null is because you are not sending it along with the submit request. Send it by <f:param>.
<p:commandButton ...>
<f:param name="prId" value="#{productInfo.prId}" />
</p:commandButton>
Unrelated to the concrete problem, there are several other potential problems. First, the view scoped bean should not be recreated when you submit the form. Perhaps you're using tag handlers in the view. Second, you should absolutely avoid hauling the raw javax.servlet API from under the JSF covers as much as possible. Use ExternalContext#getRequestParameterMap() instead. Third, the <f:viewParam> is much cleaner than that postconstruct. Fourth, redirecting by a navigation handler smells like a design problem in the view, e.g. why not use just a GET link?
The in-depth explanations on all of those issues are available in Communication in JSF 2.0.