-
Available since OmniFaces 1.3
The <o:enableRestorableView>
taghandler instructs the view handler to recreate the entire view whenever the view has been expired, i.e. whenever ViewHandler.restoreView(FacesContext, String)
returns null
and the current request is a postback. This effectively prevents ViewExpiredException
on the view. This tag needs to be placed in <f:metadata>
of the view.
There are however technical design limitations: the recreated view is exactly the same as during the initial request. In other words, the view has lost its state. Any modifications which were made after the original initial request, either by taghandlers or (ajax) conditionally rendered components based on some view or even session scoped variables, are completely lost. Thus, the view should be designed that way that it can be used with a request scoped bean. You can use it with a view scoped bean, but then you should add a @PostConstruct
which checks if the request is a postback and then fill the missing bean properties based on request parameters.
Usage
To enable the restorable view, just add the <enableRestorableView>
to the view metadata.
<f:metadata>
<o:enableRestorableView/>
</f:metadata>
Mojarra's new stateless mode
Since Mojarra 2.1.19, about 2 months after OmniFaces introduced the <o:enableRestorableView>
, it's possible to enable a stateless mode on the view by simply setting its transient
attribute to true
:
<f:view transient="true">
...
</f:view>
This goes actually a step further than <o:enableRestorableView>
as no state would be saved at all. However, on those kind of pages where <o:enableRestorableView>
would work just fine, this statelessness should not form any problem at all. So, if you have at least Mojarra 2.1.19 at hands, use the transient="true"
instead.
The demo is shown as a "login and logout" use case on 2 different pages, one with restorable view enabled and the other with restorable view disabled:
- Demo
- restorableView-enabled.xhtml
- restorableView-disabled.xhtml
- restorableView.xhtml
- Auth
- InvalidateSessionServlet
<p>
The demo is shown as a "login and logout" use case on 2 different pages,
one with restorable view enabled and the other with restorable view disabled:
</p>
<ul>
<li><h:link value="Page with restorable view enabled" outcome="/restorableView-enabled" /></li>
<li><h:link value="Page with restorable view disabled" outcome="/restorableView-disabled" /></li>
</ul>
<ui:composition template="/WEB-INF/templates/layout.xhtml"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="jakarta.faces.core"
xmlns:ui="jakarta.faces.facelets"
xmlns:o="http://omnifaces.org/ui"
>
<ui:define name="contentTitle">Restorable view - ENABLED</ui:define>
<ui:define name="meta">
<f:metadata>
<o:enableRestorableView />
</f:metadata>
</ui:define>
<ui:define name="content">
<ui:include src="/WEB-INF/includes/demo/restorableView.xhtml" />
</ui:define>
</ui:composition>
<ui:composition template="/WEB-INF/templates/layout.xhtml"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="jakarta.faces.facelets"
>
<ui:define name="contentTitle">Restorable view - DISABLED</ui:define>
<ui:define name="content">
<ui:include src="/WEB-INF/includes/demo/restorableView.xhtml" />
</ui:define>
</ui:composition>
<ui:composition
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="jakarta.faces.core"
xmlns:h="jakarta.faces.html"
xmlns:ui="jakarta.faces.facelets"
xmlns:o="http://omnifaces.org/ui"
>
<h:form rendered="#{empty user}">
<h3>Login</h3>
<p>Here's an example login form. The password is just "password" (without quotes ;) ).</p>
<h:panelGrid columns="3">
<o:outputLabel for="username" value="Username" />
<h:inputText id="username" value="#{auth.username}" required="true" />
<h:message for="username" />
<o:outputLabel for="password" value="Password" />
<h:inputSecret id="password" value="#{auth.password}" required="true" />
<h:message for="password" />
<h:panelGroup />
<h:commandButton value="Login" action="#{auth.login}">
<f:ajax execute="@form" render="@form" />
</h:commandButton>
<h:messages globalOnly="true" layout="table" />
</h:panelGrid>
</h:form>
<h:form rendered="#{not empty user}">
<h3>You're logged in as #{user}</h3>
<p>Press the below button to logout.</p>
<p><h:commandButton value="Logout" action="#{auth.logout}" /></p>
</h:form>
<p>
Pressing the below button will invalidate the session on the server side by ajax. When restorable view is
<strong>not</strong> enabled, then this should result in a <code>ViewExpiredException</code> error when login
or logout button is pressed afterwards. This exception is handled by a "Sorry... The session has expired!"
error page. See also
<h:link value="FullAjaxExceptionHandler" outcome="/exceptionhandlers/FullAjaxExceptionHandler" />
showcase page for source code of error pages.
</p>
<p>
<input type="button" value="Invalidate session"
onclick="$.get('#{request.contextPath}/invalidatesession?#{now.time}', alert('Session invalidated!'))" />
</p>
<p>
<h:link value="Back to main enableRestorableView showcase page" outcome="/taghandlers/enableRestorableView" />.
</p>
<o:highlight />
</ui:composition>
package org.omnifaces.showcase.demo;
import java.io.IOException;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;
import org.omnifaces.util.Faces;
import org.omnifaces.util.Messages;
@Named
@RequestScoped
public class Auth {
private String username;
private String password;
public void login() throws IOException {
if ("password".equals(password)) {
Faces.invalidateSession(); // "good practice", minimizes risk in session fixation hack.
Faces.setSessionAttribute("user", username);
Faces.refresh();
}
else {
Messages.addGlobalError("Unknown login, please try again");
}
}
public void logout() throws IOException {
Faces.invalidateSession();
Faces.refresh();
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package org.omnifaces.showcase.servlets;
import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/invalidatesession")
public class InvalidateSessionServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getSession().invalidate();
}
}