SEAM redirect page from backing bean - jsf

I have a proper newbie SEAM question, I want to redirect the user to a different page from a backing bean
I know in most cases you should use pages.xml however there could be a number of different pages depending on the bean logic so it seems like it should be a lot easier to do from the bean.
I cant see any examples of people doing this so Im guessing there is a reason why, Maybe something like this would work??...
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
((HttpServletRequest) ec.redirect("http://example.com/");

You can use the Redirect component:
#Name("yourBean")
public class YourBean {
#In
Redirect redirect;
public void yourMethod() {
redirect.setViewId("/someView.xhtml");
redirect.setParameter("someParam", "someValue");
redirect.execute();
}
}
Or going with FacesManager:
FacesManager.instance().redirect("/someView.xhtml", paramMap,
conversationPropagationEnabled, includePageParams);
These only work for other JSF views (ie .xhtml). If you just want an arbitrary URL, you can use the FacesContext as you mentioned in your question.

Related

Removing #ViewScoped beans in a SPA JSF 2.2 application

We are using an SPA approach in our JSF 2.2 + PrimeFaces enabled application.
The basic idea was originally described very well here:
Refreshing dynamic content with AJAX in JSF using SPA approach
But, as we know, using this SPA approach has a drawback when using #ViewScoped beans.
As we are actually always staying in the same JSF View, #ViewScoped beans are not removed from memory, when we replace the content of a panel group with the new SPA content.
I have found a solution, but I would like to know if it's a correct approach, and/or if there is anything missing.
Basically, in our NavigationService bean, which holds the name of the page to be rendered during the SPA AJAX request, we always execute the following code:
private void clearViewScopedBeans() {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
for(Iterator<Map.Entry<String, Object>> it = viewMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Object> entry = it.next();
it.remove();
}
}
This should ensure that, after the new SPA snippet is rendered, all the previous existing #ViewScoped beans are removed.
However, I think the above code only removes the View Scoped beans, but not the related View States. Is that correct ?
I found an old blog entry which seems to do a little more logic:
http://javaevangelist.blogspot.sg/2014/08/jsf-21-tip-of-day-clearing-viewscope.html
but I dunno if it's correct as well.
Additionally, if we want to support multiple window tabs, our NavigationService bean, holding the current SPA snippet page name, must be #ViewScoped as well, and this introduces a small problem:
When running the above code for removing all the existing #ViewScoped beans... we have to exclude the NavigationService bean itself !! Otherwise we end up loading always the same page, because a new instance of the NavigationService is instantiated, with a default SPA page name, instead of the new one.
So, all in all, our code looks finally like this, where we keep a Map of "excluded" bean names, that we don't want to remove on a SPA page refresh (namely the NavigationService bean holding the SPA page name)
private void clearViewScopedBeans() {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
for(Iterator<Map.Entry<String, Object>> it = viewMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Object> entry = it.next();
if(!exclusionViewScopedBeans.contains(entry.getKey())) {
logger.info("Removing an instance of a #ViewScoped bean -> " + entry.getKey());
it.remove();
}
}
}
Now the question ... is this the correct approach for handling these kind of SPA situations? Are we missing something here?
Any feedback would be greatly appreciated... thanks a lot in advance !
I think, there is no better solution for removing/clearing ViewScopedBeans in SPA (single page app) than yours Maikel:
private void clearViewScopedBeans() {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
for(Iterator<Map.Entry<String, Object>> it = viewMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Object> entry = it.next();
it.remove();
}
}
I have tried find a lot, but SPA is not "official recommended" way of using JSF, that is multi-page app, so SPA requires more fiddling around with.
Or, Maikel, have you found or do you use something another way?

Is there anything wrong with using a CDI ViewScoped bean for caching information used in multiple pages?

All of my pages are backed their own ViewScoped bean, but I'm finding that there are a lot of similar methods used on these pages. For example, a user may want to view dates in their preferred time zone so each time a page is loaded, the DB is queried for what their preferred time zone is.
So my initial thought was to create a ViewScoped bean to manage this. The timeZone value would be only be "good" for the lifetime of the page and they would be lazy-loaded to avoid unnecessary database hits:
#Named
#ViewScoped
public class Preference implements Serializable {
#Inject
private SessionManager sessionManager;
#EJB(name = "PreferencesReadFacade")
private PreferencesReadFacadeRemote prefReadFacade;
private HashMap<String, Object> cache = new HashMap<>();
/**
* #return the user's TimeZone preference
*/
public String getTimeZone() {
if(cache.get("TimeZone") == null) {
cache.put("TimeZone", prefReadFacade.getUserPreference(sessionManager.getUserId(), "TimeZone").toString());
}
return cache.get("TimeZone").toString();
}
}
Usage:
<h:outputText value="#{preference.timeZone}"/>
Is there anything wrong with this type of methodology? Is there a better way of doing something like this?
EDIT: Would like to add that I'm using ICEfaces and Omnifaces so if there are resources in these libraries at my disposal, I'm certainly open to using those.
Your approach is bsolutely correct - you may reuse the same bean in multiple pages regardless of its scope. If those pages are in the same scope, a bean would be reused, otherwise a new bean would be created with an empty cache. If the scope is ViewScoped, the bean would be recreated for every page, hence DB would be accessed first when the data is needed on after a page loads.
You may also make your common bean a base bean of other viewscoped beans, which are constructed for a particular page (they must remain viewscoped).
Or, you may inject your Preference bean into any other Named bean, which is used in your pages. In this way, you may inject it to a bean with any scope, but CDI will always give you the same bean for a view (within viewscope), but different when you redirect to a new page.
But your solution is equally correct, if not even better.
You can create one #SessionScoped bean and hold there user preferences like time zone. Then #Inject it to your #ViewScoped beans and get time zone from #SessionScoped. As long as http session lives, the DB query will be done only once in the user session if you do it in #PostConstruct and assign to variables.

javax.faces.FacesException Can't instantiate class [duplicate]

I want to make a redirect in my #PostConstruct in 4 of my backing beans. As I've learned from the follwoing question:
JSF PostConstruct Exception Handling - Redirect
I know that I'm supposed to use:
#PostConstruct
public void init() {
if (shouldRedirect) {
try {
FacesContext.getCurrentInstance().getExternalContext().redirect("bolagsSok_company.xhtml");
return;
} catch (IOException e) {
//do nothing
}
}
....
}
This works great for 2 of my Backing beans... but for the other two, the non-redirected-xhtml file is still making calls to the backing bean and doesn't redirect. I've confirmed (with debug) that the backing beans indeed calls both FacesContext.getCurrentInstance().getExternalContext().redirect("bolagsSok_company.xhtml"); and return; statements.
Any clues what could be wrong?
Redirecting in a #PostConstruct might be too late if the response is already committed. I.e. when the first few bytes of the response are already been sent to the client. This is a point of no return. That can in your case happen when the backing bean is referenced (and thus constructed) for the first time relatively late in the view, maybe about halfway or in the end.
You could solve this in one of the following ways:
Reference the bean for the first time as early as possible in the view.
Use <f:event type="preRenderView"> instead of #PostConstruct. This will invoke the method right before the render response starts (thus, before any bit is been sent to the response). Or, when you're on JSF 2.2 already, use the <f:viewAction>. Additional advantage is that the <f:viewAction> can return a navigation case outcome like return bolagsSok_company?faces-redirect=true" without the need to fiddle with ExternalContext#redirect().
Increase the default Facelets buffer size by javax.faces.FACELETS_BUFFER_SIZE context param in web.xml to about the size of the largest HTML response.
See also:
Hit a bean method and redirect on a GET request
Is there any easy way to preprocess and redirect GET requests?
How to navigate in JSF? How to make URL reflect current page (and not previous one)

Redirect in #PostConstruct causes IllegalStateException

I want to make a redirect in my #PostConstruct in 4 of my backing beans. As I've learned from the follwoing question:
JSF PostConstruct Exception Handling - Redirect
I know that I'm supposed to use:
#PostConstruct
public void init() {
if (shouldRedirect) {
try {
FacesContext.getCurrentInstance().getExternalContext().redirect("bolagsSok_company.xhtml");
return;
} catch (IOException e) {
//do nothing
}
}
....
}
This works great for 2 of my Backing beans... but for the other two, the non-redirected-xhtml file is still making calls to the backing bean and doesn't redirect. I've confirmed (with debug) that the backing beans indeed calls both FacesContext.getCurrentInstance().getExternalContext().redirect("bolagsSok_company.xhtml"); and return; statements.
Any clues what could be wrong?
Redirecting in a #PostConstruct might be too late if the response is already committed. I.e. when the first few bytes of the response are already been sent to the client. This is a point of no return. That can in your case happen when the backing bean is referenced (and thus constructed) for the first time relatively late in the view, maybe about halfway or in the end.
You could solve this in one of the following ways:
Reference the bean for the first time as early as possible in the view.
Use <f:event type="preRenderView"> instead of #PostConstruct. This will invoke the method right before the render response starts (thus, before any bit is been sent to the response). Or, when you're on JSF 2.2 already, use the <f:viewAction>. Additional advantage is that the <f:viewAction> can return a navigation case outcome like return bolagsSok_company?faces-redirect=true" without the need to fiddle with ExternalContext#redirect().
Increase the default Facelets buffer size by javax.faces.FACELETS_BUFFER_SIZE context param in web.xml to about the size of the largest HTML response.
See also:
Hit a bean method and redirect on a GET request
Is there any easy way to preprocess and redirect GET requests?
How to navigate in JSF? How to make URL reflect current page (and not previous one)

How can i send a parameter to be used in the #PostConstruct method of a backing bean?

I need to preload some data to be displayed when the page loads. The initialization steps are performed on a #PostConstruct-annotated method but now i need to use a parameter in order to get the data.
What i'm trying to do:
#PostConstruct
public void init()
{
List data = getDataFromDB(parameter) /*Need to read a parameter created somewhere else*/
}
Is there a way to achieve this?
Thanks in advance
It's kind of hard to say what you mean by "a parameter set somewhere else". I will assume that "somewhere else" means "sent from browser by HTTP". In such case you should create a standard property in your managed bean and:
in JSF 2.0 you could annotate it with #ManagedProperty("#{param.nameOfParameterToRead}")
in JSF 1.2 and less - use managed-property element in your bean description (faces-config.xml).
Like this:
#ManagedBean
#RequestScoped
class MyManagedBean {
#ManagedProperty("#{param.id}")
public Integer id;
#PostConstruct
public void init(){
data = getDataFromDB(id)
}
// setters and getters (mandatory, even though annotation is on an attribute!!!)
}
Careful: injecting properties does not use JSF converters, so it is best to inject strings and take care of conversion in your own code.
how about reading from Properties file, or fetching List from DB ??

Resources