navigation issues with Primefaces mobile using HttpServletRequest - jsf

I'm performing url redirects between primefaces mobile pages (pm:page). For instance from login.jsf to /secure/myPage.jsf, both pm:pages. After successful authentication the user should be redirect to myPage.jsf. The login is triggered like this:
<pm:commandButton value="login" update="messages"
actionListener="#{loginbean.doLogin}" >
<f:param name="targetUrlParam" value="defaultTarget" />
</pm:commandButton>
and the redirect within the method:
public void doLogin(ActionEvent e) throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext ec = context.getExternalContext();
try {
HttpServletRequest req = (HttpServletRequest) ec.getRequest();
Authentication authentication = SecurityContextHolder.
getContext().getAuthentication();
... // Authentication stuff with Spring Security
try {
HttpSession session = req.getSession(false);
String cp = ec.getRequestContextPath();
String redirectUrl = cp;
... //performing some filtering depending on Roles and target-urls
}
String encodedURL = ec.encodeResourceURL(redirectUrl);
((HttpServletResponse) ec.getResponse()).sendRedirect(encodedURL);
} catch (AuthenticationException ae) {
UtilBean.addErrorMessage("bad_credential");
}
Unfortunately the redirect doesn't occur! It might have to do with the lifecycle of primefaces mobile 3.0M3 because everything works fine with normal JSF pages.
Any suggestions? Thanks

This is not entirely the right way to send a redirect in JSF. I'm not sure why it works in "normal" JSF (that should fail over there as well!). You basically need to call FacesContext#responseComplete() after the redirect to instruct JSF that it should not navigate to the default outcome. However, much better is to perform the redirect using ExternalContext#redirect() as it will do that implicitly.
So in your case, replace
((HttpServletResponse) ec.getResponse()).sendRedirect(encodedURL);
by
ec.redirect(encodedURL);

Related

URL redirection not working on preRenderView event

I finally got messages passed between pages, but this is not redirecting me to the user login page (../../index.xhtml) instead it shows the forbidden page :
public String permission() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
String isLog = (String) sessionMap.get("isLogged");
if(isLog != "TRUE") {
System.out.println("*** The user has no permission to visit this page. *** ");
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Info : ", "Loggez-vous"));
context.getExternalContext().getFlash().setKeepMessages(true);
//context.getExternalContext().redirect("../../index.xhtml");
return "../../index.xhtml?faces-redirect=true";
} else {
System.out.println("*** The session is still active. User is logged in. *** ");
}
return "../../index.xhtml?faces-redirect=true";
}
Of course, restriced page has this :
<f:event type="preRenderView" listener="#{userloginMB.permission()}"/>
Redirection using get external context will make the messages lost.
Ignoring the general design problem (look here for a starting point), it seems that you mixed up the new JSF 2.2 <f:viewAction> and the old JSF 2.0/2.1 <f:event type="preRenderView"> trick.
Returning a navigation outcome as String on a GET request is only supported in <f:viewAction>, not in <f:event type="preRenderView">. For the latter you need ExternalContext#redirect() which you happened to have outcommented.
So, you should do either
<f:event type="preRenderView" listener="#{bean.onload}"/>
public void onload() throws IOException {
// ...
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.redirect(ec.getRequestContextPath() + "/index.xhtml");
}
or
<f:metadata>
<f:viewAction action="#{bean.onload}"/>
</f:metadata>
public String onload() {
// ...
return "/index.xhtml"; // Note: no need for faces-redirect=true!
}
and not mix up them.
Note that I wrote the code in such way that you can always use an /path relative to the web root without the need to fiddle with ../ nonsense.
See also:
What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for?
You need to specify absolute path from the root context when using faces-redirect=true.
so your outcome string should look like:
return "/dir1/dir2/index.xhtml?faces-redirect=true";
if index.xhtml resides in (context root) i.e. Web Content/index.xhtml then use this outcome string:
return "/index.xhtml?faces-redirect=true";
if index.xhtml resides in Web Content/pages/dir2/index.xhtml then use this outcome string:
return "/pages/dir2/index.xhtml?faces-redirect=true";

JSF h:outputLink call bean method before GET

I need to send some parameters from one xhtml to another, but I don't want these parameters to appear in the URL. How to do that? I could use p:commandLink, but then I don't know how to open the destination page from the bean method. The destination page should be accessible by friendly URL, not by xhtml name.
This code will open the page /users/view. How can I send parameters without them appearing in the URL?
<h:outputLink value="/users/view">
<h:outputText value="#{entry.employee}" />
</h:outputLink>
Ignoring the strange design, you could use put the data in the flash scope and send a redirect in a <h:commandLink> action method:
public void view() throws IOException {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.getFlash().put("employee", employee);
ec.redirect(ec.getRequestContextPath() + "/users/view");
}
And then in the backing bean associated with the target page:
#PostConstruct
public void init() {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
employee = (Employee) ec.getFlash().get("employee");
}
See also:
Pass an object between #ViewScoped beans without using GET params
What is the difference between redirect and navigation/forward and when to use what?

JSF 1.2 Add Parameter to Redirect Request

i use Jsf 1.2 and have navigation rule with redirect, my question is how to add request parameters to view redirected
This isn't possible using navigation rules in JSF 1.x. Use ExternalContext#redirect() instead.
public void action() throws IOException {
// ...
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
String url = ec.getRequestContextPath() + "/page.jsf?someparam=" + URLEncoder.encode(someparam, "UTF-8");
ec.redirect(url);
}

Adding faces message to redirected page using ExternalContext.redirect()

I am using ExternalContext.redirect(String); method to redirect user to another page:
FacesContext.getCurrentInstance().addMessage(new FacesMessage("Bla bla bla..."));
FacesContext.getCurrentInstance().getExternalContext().getFlash().setKeepMessages(true);
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.redirect(ec.getRequestContextPath() + "/scenario.xhtml");
As Matt Handy mentioned in his answer, I used Flash.setKeepMessages(true); but it does not seem to work with ExternalContext.redirect. (Although it works when I redirect by returning a page name from bean's action method.)
Now how can I add FacesMessage so that it is visible in the redirected (scenario.xhtml) page?
This seems to be a timing problem. This listener method is invoked during the preRenderView event. According to the source code of ELFlash (Mojarra's Flash implementation as returned by ExternalContext#getFlash()) it turns out that it won't set the flash cookie when you're currently sitting in the render response phase and the flash cookie hasn't been set yet for the current request:
Here are the relevant lines from ELFlash:
if (currentPhase.getOrdinal() < PhaseId.RENDER_RESPONSE.getOrdinal()) {
flashInfo = flashManager.getPreviousRequestFlashInfo();
} else {
flashInfo = flashManager.getNextRequestFlashInfo(this, true);
maybeWriteCookie(context, flashManager);
}
The maybeWriteCookie would only set the cookie when the flash cookie needs to be passed through for the second time (i.e. when the redirected page in turn redirects to another page).
This is an unfortunate corner case. This ELFlash logic makes sense, but this isn't what you actually want. Basically you need to add the message during INVOKE_APPLICATION phase instead. There is however no such event as postInvokeAction. With the new JSF 2.2 <f:viewAction> tag it should be possible as it really runs during invoke application phase.
<f:viewAction action="#{bean.onload}" />
As long as you're not on JSF 2.2 yet, you'd need to look for alternate ways. The easiest way would be to create a custom ComponentSystemEvent.
#NamedEvent(shortName="postInvokeAction")
public class PostInvokeActionEvent extends ComponentSystemEvent {
public PostInvokeActionEvent(UIComponent component) {
super(component);
}
}
Now you need somewhere a hook to publish this event. The most sensible place is a PhaseListener listening on after phase of INVOKE_APPLICATION.
public class PostInvokeActionListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.INVOKE_APPLICATION;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, PostInvokeActionEvent.class, context.getViewRoot());
}
}
If you register it as follows in faces-config.xml
<lifecycle>
<phase-listener>com.example.PostInvokeActionListener</phase-listener>
</lifecycle>
then you'll be able to use the new event as follows
<f:event type="postInvokeAction" listener="#{bean.onload}" />
Update this is also available in the JSF utility library OmniFaces, so you don't need to homebrew the one and other. See also the InvokeActionEventListener showcase example.
Use the flash to keep messages over a redirect.
Add these two lines to your code before redirecting:
FacesContext context = FacesContext.getCurrentInstance();
context.getExternalContext().getFlash().setKeepMessages(true);
Note that the there are some issues with Mojarra's flash scope implementation. Keep this in mind if you use it.
Using Matt Handy's example as a reference, I created the method below that worked very well for me.
public static void Message(String message) {
FacesMessage fm = new FacesMessage(FacesMessage.SEVERITY_INFO, mensagem, null);
FacesContext context = FacesContext.getCurrentInstance();
context.getExternalContext().getFlash().setKeepMessages(true);
context.addMessage(null, fm);
}

logout commandLink logs the user out, but doesn't update the page

I have a logout link in my JSF app that invalidates the session to log the user out. It works but it doesn't redirect the user to the logon page. It stays on the same page. If I try to access the same page again it does direct back to the logon. I want this to happen immediately.
logout link:
<h:form>
<h:panelGroup id="loginout">
<h:outputText value="#{todoController.loggedInUser}" />
<h:commandLink value="logout" action="#{todoController.logout}" />
</h:panelGroup>
</h:form>
logout code:
public String logout()
{
System.out.println("testing logout");
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
final HttpServletRequest r = (HttpServletRequest)ec.getRequest();
r.getSession( false ).invalidate();
return "../login.html?faces-redirect=true";
}
This can happen if the outcome is invalid. login.html doesn't seem to be a JSF page, so JSF navigation will simply fail.
You want to use ExternalContext#redirect() instead.
public void logout() throws IOException {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.invalidateSession();
ec.redirect("../login.html");
}
Note that the above also demonstrates a more JSF-ish way to invalidate the session. Whenever you need to haul the raw javax.servlet.* API from under the JSF hoods, you should always ask yourself twice: "Is there really not a JSF-provided API for this?"

Resources