Part 1 of this series describes a scenario in which your application automatically saves form data on the server and you want to be able to restore forms after your users close and reopen their browsers. The solution works even if the user's browser crashes, or if the user leaves the application without clicking Submit on the Web form.
The sample application included with this article (see Download) contains a typical JSF form named SupportForm.jsp whose data is periodically submitted to the server
using the JavaScript functions of the AutoSaveScript.js
file. Both SupportForm.jsp and AutoSaveScript.js are described in Part 1, which
also shows how to use a JSF phase listener to process Ajax requests without interfering
with the application logic.
In this article, you'll learn how to build a thread-safe data repository for keeping auto-saved form data. You'll see how to choose data structures, how to populate them with the form data extracted from the JSF component tree, how to restore the state of the JSF components, how to limit the memory resources of the data repository, and how to implement its persistence. You'll also learn several Web techniques, such as using filters and browser ID cookies.
Identifying users across sessions
To restore Web forms after users close and reopen their browsers, the application needs
to identify users across sessions, which is easy if users are authenticated. If your
application uses standard methods for authenticating users, you can call the getUserPrincipal() method defined by the HttpServletRequest interface and then obtain the user name with the
getName() method of java.security.Principal.
If your application must support form-saving and form-restoring features for anonymous
users, as well, you can set a browser ID, which is very similar to the session ID that
tracks the user during a single session. In fact, you can take the value of the session
ID cookie and set another cookie named BROWSERID when the
user accesses the application for the first time. Unlike the session ID, which is
generated at the beginning of a session and expires at the end of the session, the
BROWSERID cookie is set only once and can expire after a
long period, such as several years.
A servlet filter is the right place for setting the BROWSERID cookie because the filter can intercept every HTTP
request, adding the cookie to the HTTP response if the user accesses the application
for the first time. Once the browser receives the cookie through the first response,
any subsequent request will contain the BROWSERID cookie,
allowing the application to identify the anonymous user though his browser
ID. The sample code that accompanies this article contains a class named BrowserIdFilter, which implements javax.servlet.Filter. This class also has a method named getBrowserId() (see Listing 1), which iterates
over the cookies of a request object, returning the value of the BROWSERID cookie or null if such a
cookie doesn't exist.
Listing 1. Getting the browser ID cookie
package autosave;
...
import javax.servlet.http.Cookie;
...
public class BrowserIdFilter implements Filter {
public static String BROWSERID = "BROWSERID"; // cookie name
public static String getBrowserId(HttpServletRequest httpRequest) {
String browserId = null;
Cookie cookies[] = httpRequest.getCookies();
if (cookies != null)
for (int i = 0; i < cookies.length; i++) {
if (BROWSERID.equals(cookies[i].getName())) {
browserId = cookies[i].getValue();
break;
}
}
return browserId;
}
...
}
|
Listing 2 shows the doFilter() method
of BrowserIdFilter, which uses getBrowserId() to test if the cookie hasn't already been set. If
getBrowserId() returns null,
doFilter() gets the session object with getSession() and the session ID with getId(). Then, doFilter() creates a Cookie object, sets the maxAge and path properties, and adds the cookie to the response object. To
ensure the proper processing of the HTTP request, the request
and response objects are passed to the doFilter() method of the chain parameter.
Listing 2. Implementing the doFilter() method of the Filter interface
package autosave;
...
import javax.servlet.Filter;
import javax.servlet.FilterChain;
...
public class BrowserIdFilter implements Filter {
public static String BROWSERID = "BROWSERID"; // cookie name
public static int IDAGE = 3600 * 24 * 365 * 3; // three years
...
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
if (getBrowserId(httpRequest) == null) {
// The BROWSERID cookie has not been found. This must be
// the first time the user accesses the application.
// Use the current session's ID as the value for
// the BROWSERID cookie.
HttpServletResponse httpResponse
= (HttpServletResponse) response;
String browserId = httpRequest.getSession().getId();
Cookie browserCookie = new Cookie(BROWSERID, browserId);
browserCookie.setMaxAge(IDAGE);
browserCookie.setPath(httpRequest.getContextPath());
httpResponse.addCookie(browserCookie);
}
chain.doFilter(request, response);
}
...
}
|
Configuring the servlet filter
Listing 3 shows how to configure the BrowserIdFilter class
in web.xml so the filter can intercept every HTTP request.
Listing 3. Configuring the filter
<web-app ...>
...
<filter>
<filter-name>BrowserIdFilter</filter-name>
<filter-class>autosave.BrowserIdFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>BrowserIdFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
|
The browser ID solution should be used only if your forms do not contain sensitive information because all users accessing the application through a shared computer would actually be treated as a single user. The only secure way to identify users across sessions is to use standard authentication methods based on user names and passwords. However, the password-based authentication has the inconvenience that users must register to the application. In many cases, security is very important, but sometimes users might prefer to remain anonymous instead of registering to a Web site. The browser ID offers an easy way to track anonymous users.
The form auto-saving feature presented in this series works with authenticated and anonymous users. In the following sections, you'll see how to store and retrieve the auto-saved form data and how to work with it in a multithreaded environment.
First, you must choose a data structure and a repository for keeping the form data. This is an important decision because the repository will be accessed frequently for storing temporary data. In the sample application, data is automatically saved every 10 seconds for every form instance, but in a real application, it would be reasonable to increase this interval up to 10 minutes if you have a large number of concurrent users.
Memory is the obvious place for storing the auto-saved form data because most of this data is stored only for a short time, then it is replaced by fresher data. Each form instance saves its data periodically until the user submits the form. Any temporary data associated with a submitted form should be flushed from the memory. If the user cannot submit the form or if he abandons the page without clicking the Submit button, the last saved data is kept in memory as long as possible. When the user returns to the form, he will have the option to restore the form if the saved data is still available.
Saving and restoring the values of the JSF components
The auto-saved data of every form instance is kept in a Map<String, Object> instance. Each entry and element of such a
data map contains the value of a JSF input component whose ID is also the key in the
data map. This structure is similar to the parameter map of javax.servlet.ServletRequest, but it is not the same because the
request parameter map contains string arrays, while the repository data map keeps the
converted and validated values of all input components of a JSF view. The saveValues() method of the DataMapRepository class is a recursive method that traverses a JSF
component tree, populating a data map with the values of the input components that
implement EditableValueHolder (see Listing 4).
Listing 4. Storing the values of the JSF input components into a data map
package autosave;
...
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
...
public class DataMapRepository ... {
...
public static void saveValues(UIComponent comp,
Map<String, Object> dataMap) {
if (comp == null)
return;
if (comp instanceof EditableValueHolder) {
// Input component. Put its value into the data map
EditableValueHolder evh = (EditableValueHolder) comp;
dataMap.put(comp.getId(), evh.getValue());
}
// Iterate over the children of the current component
Iterator children = comp.getChildren().iterator();
while (children.hasNext()) {
UIComponent child = (UIComponent) children.next();
// Recursive call
saveValues(child, dataMap);
}
}
...
}
|
The restoreValues() method (shown in Listing 5) traverses the JSF component tree, restoring the values of
the input components. This method also clears the submittedValue property of each EditableValueHolder component so the form's data can be restored to
the data from the map, ignoring any submitted data. The saveValues() method is used later in this article and restoreValues() in Part 3 of this series.
Listing 5. Restoring the values of the JSF input components
package autosave;
...
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
...
public class DataMapRepository ... {
...
public static void restoreValues(UIComponent comp,
Map<String, Object> dataMap) {
if (comp == null || dataMap == null)
return;
if (comp instanceof EditableValueHolder) {
// Input component. Get its value from the data map
// and clear any submitted value
EditableValueHolder evh = (EditableValueHolder) comp;
evh.setValue(dataMap.get(comp.getId()));
evh.setSubmittedValue(null);
}
// Iterate over the children of the current component
Iterator children = comp.getChildren().iterator();
while (children.hasNext()) {
UIComponent child = (UIComponent) children.next();
// Recursive call
restoreValues(child, dataMap);
}
}
}
|
Each data map instance has a unique ID composed from the user ID and the JSF view ID.
Therefore, it is natural to keep all these data maps in a repository map. The DataMapRepository class extends java.util.LinkedHashMap and has a method named getDataMapId() (see Listing 6) that generates
the ID of a data map, using the given faces context whose methods return objects
containing the necessary user and view information.
If the user is logged in, getDataMapId() obtains the user
name from the Principal object. Otherwise, the user is
anonymous and getDataMapId() uses the browser ID. The JSF
view ID, which is part of the returned ID, is unique for each JSF page. Therefore, the
data map ID is unique for each user and page combination.
Listing 6. Generating the unique ID of a data map
package autosave;
...
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
...
import java.security.Principal;
...
public class DataMapRepository ... {
...
public static String getDataMapId(FacesContext ctx) {
UIViewRoot root = ctx.getViewRoot();
if (root == null)
return null;
ExternalContext ectx = ctx.getExternalContext();
String userId = null;
Principal principal = ectx.getUserPrincipal();
if (principal != null) {
// Use the name of the authenticated user.
userId = principal.getName();
} else {
// Use the browser ID of the anonymous user.
userId = BrowserIdFilter.getBrowserId(
(HttpServletRequest) ectx.getRequest());
}
if (userId == null)
return null;
// Concatenate the user ID and the JSF view ID
return userId + root.getViewId();
}
...
}
|
Limiting the memory resources of the data repository
Some restrictions are necessary because the Java heap is limited and the data
repository shouldn't consume too much memory. In the sample application, the repository
has a limit of data map instances it can store. When this limit is reached, the oldest
data map is removed from the repository and becomes eligible for garbage collection.
This mechanism is already built into the java.util.LinkedHashMap class — you just have to
override the removeEldestEntry() method to return true when the repository has more entries than the maximum allowed number.
Listing 7 shows the DataMapRepository
class, which extends LinkedHashMap, adding the maxDataMaps property and overriding the removeEldestEntry() method, as explained earlier. In addition, DataMapRepository contains a constructor that allows you to create
copies of the repository instances, which can be useful if you want to take a snapshot
of the repository while the application is running. Both the original repository and
its copy will contain the same data map objects, which is correct because data maps
cannot be modified, as you'll see later in this article.
Listing 7. The data repository class
package autosave;
...
import java.util.LinkedHashMap;
import java.util.Map;
public class DataMapRepository
extends LinkedHashMap<String, Map<String, Object>> {
private static final int DEFAULT_MAX_DATA_MAPS = 1000;
private int maxDataMaps;
public DataMapRepository() {
maxDataMaps = DEFAULT_MAX_DATA_MAPS;
}
public DataMapRepository(DataMapRepository repository) {
maxDataMaps = repository.maxDataMaps;
putAll(repository);
}
public int getMaxDataMaps() {
return maxDataMaps;
}
public void setMaxDataMaps(int maxDataMaps) {
this.maxDataMaps = maxDataMaps;
}
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > maxDataMaps;
}
...
}
|
The getDataMap() and setDataMap()
methods (see Listing 8) can be used to access the data maps of the
repository. These two methods use getDataMapId() to generate
the ID for the given context, then they call the get(),
put(), and remove() methods
inherited from the LinkedHashMap class.
Listing 8. Methods for storing and retrieving data maps
package autosave;
...
public class DataMapRepository ... {
...
public Map<String, Object> getDataMap(FacesContext ctx) {
String id = getDataMapId(ctx);
if (id == null)
return null;
return get(id);
}
public void setDataMap(FacesContext ctx, Map<String, Object> dataMap) {
String id = getDataMapId(ctx);
if (id == null)
return;
if (dataMap != null)
put(id, dataMap);
else
remove(id);
}
...
}
|
Working in a multithreaded environment
The DataMapRepository class presented previously and its
base class LinkedHashMap are not thread-safe. One way to
deal with this is to use the synchronizedMap() method of
java.util.Collections, which returns a thread-safe wrapper
map. In the case of the form data repository, it is better to build a custom
wrapper class, which controls how the repository is accessed, in addition to
ensuring that it is safely used in a multithreaded server environment.
Using a thread-safe wrapper for the data repository
Listing 9 shows the wrapper class, which is named RepositoryWrapper in the sample application. It has a single field
named repository of type DataMapRepository. The getRepository()
method returns a copy of the private repository, and setRepository() creates a new copy, as well. These copies, however,
contain the same data map objects as the original repository, which is OK because data
maps are not modified after being created and populated with the form data.
Listing 9. The wrapper class of the data repository
package autosave;
...
public class RepositoryWrapper implements java.io.Serializable {
private DataMapRepository repository;
public RepositoryWrapper() {
repository = new DataMapRepository();
}
public synchronized DataMapRepository getRepository() {
return new DataMapRepository(repository);
}
public synchronized void setRepository(
DataMapRepository repository) {
if (repository != null)
this.repository = new DataMapRepository(repository);
else
this.repository.clear();
}
...
}
|
The RepositoryWrapper class contains thread-safe methods for
accessing the data maps and the maxDataMaps property of the
wrapped repository (see Listing 10). The getDataMap() and setDataMap() methods
retrieve and store the data map for the given FacesContext.
The hasDataMap() method returns true if a data map exists for the ctx
parameter and the clearDataMap() method removes a data map
from the repository.
Listing 10. Thread-safe methods for accessing the data repository
package autosave;
...
import javax.faces.context.FacesContext;
...
import java.util.Map;
public class RepositoryWrapper implements java.io.Serializable {
...
public synchronized Map<String, Object> getDataMap(
FacesContext ctx) {
return repository.getDataMap(ctx);
}
public synchronized void setDataMap(FacesContext ctx,
Map<String, Object> dataMap) {
repository.setDataMap(ctx, dataMap);
}
public synchronized boolean hasDataMap(FacesContext ctx) {
return getDataMap(ctx) != null;
}
public synchronized void clearDataMap(FacesContext ctx) {
setDataMap(ctx, null);
}
public synchronized int getMaxDataMaps() {
return repository.getMaxDataMaps();
}
public synchronized void setMaxDataMaps(int maxDataMaps) {
repository.setMaxDataMaps(maxDataMaps);
}
...
}
|
Configuring the wrapper as a JSF managed bean
All map objects containing the form data are kept in a repository instance whose
wrapper is configured as a JSF managed bean in the faces-config.xml file (see Listing 11). The
specified bean name is repositoryWrapper and the bean scope
is application. The JSF configuration file can also be used
to provide a value for the maxDataMaps property of the data repository.
Listing 11. Configuring the repository wrapper as a JSF managed bean
<faces-config>
...
<managed-bean>
<managed-bean-name>repositoryWrapper</managed-bean-name>
<managed-bean-class>autosave.RepositoryWrapper</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
...
<managed-property>
<property-name>maxDataMaps</property-name>
<value>100</value>
</managed-property>
</managed-bean>
...
</faces-config>
|
The RepositoryWrapper class has two static methods that
return the managed bean instance. These methods (shown in Listing
12) can use a FacesContext or a ServletContext for retrieving the wrapper bean from the application scope.
Listing 12. Getting the managed bean instance
package autosave;
import javax.faces.application.Application;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.servlet.ServletContext;
...
public class RepositoryWrapper implements java.io.Serializable {
...
public static RepositoryWrapper getManagedBean(FacesContext ctx) {
Application app = ctx.getApplication();
ValueBinding vb = app.createValueBinding("#{repositoryWrapper}");
return (RepositoryWrapper) vb.getValue(ctx);
}
public static RepositoryWrapper getManagedBean(ServletContext ctx) {
return (RepositoryWrapper) ctx.getAttribute("repositoryWrapper");
}
}
|
Modifying the JSF phase listener
In Part 1 of this series, you found a class named
AutoSaveListener that was used to handle Ajax requests,
retrieving the submitted data from the JSF component tree. In this article, the form
data will be stored in the data repository, instead of being printed.
Saving the current view's data into the repository
Listing 13 shows the saveCurrentView()
method of AutoSaveListener, which stores the form data of
the current JSF view into the repository. The first step is to call getCurrentInstance(), which returns the faces context, then use
getViewRoot() to obtain the root component of the JSF view.
After that, saveCurrentView() creates a new data map and
calls the saveValues() method of the DataMapRepository class to store the values of the JSF components
into the data map. As a safety measure, the map object is passed to Collections.unmodifiableMap(), which returns a wrapper map that
throws an exception at any attempt to modify its state. The unmodifiable map is stored
into the repository with the setDataMap() method of the
wrapper object.
Listing 13. Storing the form data of the current JSF view into the repository
package autosave;
...
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
...
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class AutoSaveListener implements PhaseListener {
...
public void saveCurrentView() {
// Get the faces context of the current request.
FacesContext ctx = FacesContext.getCurrentInstance();
// Get the root component of the current view.
UIViewRoot root = ctx.getViewRoot();
// Create a new data map.
Map<String, Object> dataMap = new HashMap<String, Object>();
// Store the component values into the data map.
DataMapRepository.saveValues(root, dataMap);
// Make the data map unmodifiable.
dataMap = Collections.unmodifiableMap(dataMap);
// Get the managed bean instance wrapping the data repository.
RepositoryWrapper wrapper = RepositoryWrapper.getManagedBean(ctx);
// Store the data map into the repository.
wrapper.setDataMap(ctx, dataMap);
// Stop request processing.
ctx.responseComplete();
}
...
}
|
As you probably remember from Part 1, the form auto-saving should not
interfere with the application logic. The data model must not be updated and no action
method should be called after an auto-save. After saving the values of the input components, saveCurrentView() must stop the request processing so that the auto-saved data, which is actually the user input of a partially filled form, isn't stored into the application data model. Therefore, saveCurrentView() calls the responseComplete() method of the faces context to signal that the JSF request processing life cycle should be interrupted.
As explained in Part 1, the form data must be saved after the JSF
validation phase, when all values of the JSF components have been converted and
validated by the JSF framework. In addition to verifying the phase ID of the PhaseEvent parameter, the afterPhase()
method (shown in Listing 14) gets the Ajax-Request header to see if the current request auto-saves the
form data. This header is set in the submitFormData()
function of the AutoSaveScript.js file, which was presented
in Part 1. If the header's value is Auto-Save, the afterPhase() method calls saveCurrentView() to store the valid values of the JSF components
into the data repository.
If the header marking the Ajax requests is not present, the user must have clicked the
Submit button of the Web form. In this case, afterPhase()
verifies if the faces context contains any messages, whose absence indicates that the
user-submitted data is valid because afterPhase() is invoked
after the JSF validation phase. If the form data is valid, any previously saved data
for the current user and view combination is cleared from the repository with clearDataMap().
Listing 14. Listening for JSF phase events
package autosave;
...
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
...
public class AutoSaveListener implements PhaseListener {
...
public void afterPhase(PhaseEvent e) {
if (!e.getPhaseId().equals(PhaseId.PROCESS_VALIDATIONS))
return;
FacesContext ctx = e.getFacesContext();
Map headers = ctx.getExternalContext().getRequestHeaderMap();
if ("Auto-Save".equals(headers.get("Ajax-Request"))) {
// Auto-Save Request. Save data into the repository.
saveCurrentView();
} else {
// The user must have clicked the Submit button.
if (!ctx.getMessages().hasNext()) {
// There are no error messages.
// This means the final submitted data is valid and
// the temporary auto-saved data is no longer needed.
RepositoryWrapper wrapper
= RepositoryWrapper.getManagedBean(ctx);
wrapper.clearDataMap(ctx);
}
}
}
...
}
|
Implementing the repository's persistence
In this section, the sample application is enhanced to save the repository on the
server before shutdown and restore it when the server starts again. Object
serialization is an easy way for implementing the repository's persistence since the
DataMapRepository class, the contained data maps, and their
elements are serializable. Remember that data maps contain the values of the JSF
components, which must be serializable according to the JSF specification.
Object serialization has its well-known flaws, but in this case, it is a reasonable persistence solution because the number of form data instances that are kept in the repository is limited. Also, there is no tragedy if the temporary data of the repository is lost in the event that the server crashes due to some problem unrelated to the application. A more reliable solution, based on a relational or object database, would require more CPU resources, whose allocation can't be justified for storing the partial user input, which is updated every 10 seconds. Nevertheless, the repository's state should survive a server or application restart.
Using a ServletContextListener
The DataMapPersistence class of the sample application
implements the javax.servlet.ServletContextListener
interface so it can get notifications when the application is started and stopped. The
listener methods serialize and deserialize the data repository , using the File returned by the getDataFile() method
(shown in Listing 15). The data file is kept in the WEB-INF directory of the sample application.
Listing 15. Getting the file of the data repository
package autosave;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextListener;
...
import java.io.File;
public class DataMapPersistence implements ServletContextListener {
private File getDataFile(ServletContext sctx) {
String path = sctx.getRealPath("/WEB-INF/repository.ser");
if (path == null)
return null;
return new File(path);
}
...
}
|
The servlet/JSP container will call the contextInitialized()
method during the application initialization. Listing 16 shows
this method, which deserializes the repository object and sets a context attribute
named loadedRepository, which can be accessed later, using
the JSP/JSF EL. The servlet context's attributes are the same thing as the JSP/JSF
variables kept in the application scope. The loaded
repository will be used later to set the repository property
of the RepositoryWrapper bean.
Listing 16. Loading the data repository during application initialization
package autosave;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
...
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DataMapPersistence implements ServletContextListener {
...
public void contextInitialized(ServletContextEvent e) {
ServletContext sctx = e.getServletContext();
File dataFile = getDataFile(sctx);
if (dataFile == null || !dataFile.exists())
return;
try {
ObjectInputStream in = new ObjectInputStream(
new BufferedInputStream(
new FileInputStream(dataFile)));
try {
// Read the data repository from the file.
Object repository = in.readObject();
// Store the loaded repository into the application scope.
sctx.setAttribute("loadedRepository", repository);
} finally {
in.close();
}
} catch (Exception x) {
sctx.log("Loading Error", x);
}
}
...
}
|
No servlet or JSP page will be invoked before the contextInitialized() call, which means the JSF framework might not
be initialized yet. Therefore, contextInitialized() cannot
set the repository property of the RepositoryWrapper bean, which is managed by the JSF framework. This
setting is done in the faces-config.xml file, where the
loadedRepository variable from the application scope can be safely used to set the repository property of the managed bean (see Listing 17).
Listing 17. Storing the loaded data into the repository wrapper
<faces-config>
...
<managed-bean>
<managed-bean-name>repositoryWrapper</managed-bean-name>
<managed-bean-class>autosave.RepositoryWrapper</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
<managed-property>
<property-name>repository</property-name>
<value>#{loadedRepository}</value>
</managed-property>
...
</managed-bean>
...
</faces-config>
|
The servlet/JSP container will call the contextDestroyed()
method when the application is shut down. After getting the RepositoryWrapper instance that is managed by the JSF framework, the
contextDestroyed() method retrieves a copy of the data
repository from the wrapper bean and serializes the repository's copy into the data
file (see Listing 18).
Listing 18. Saving the repository's data in a file before application shutdown
package autosave;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
...
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class DataMapPersistence implements ServletContextListener {
...
public void contextDestroyed(ServletContextEvent e) {
ServletContext sctx = e.getServletContext();
File dataFile = getDataFile(sctx);
if (dataFile == null)
return;
try {
ObjectOutputStream out = new ObjectOutputStream(
new BufferedOutputStream(
new FileOutputStream(dataFile)));
try {
// Get a copy of the data repository from the wrapper bean.
RepositoryWrapper wrapper
= RepositoryWrapper.getManagedBean(sctx);
Object repository = wrapper.getRepository();
// Serialize the data repository into the file.
out.writeObject(repository);
} finally {
out.close();
}
} catch (Exception x) {
sctx.log("Saving Error", x);
}
}
}
|
Listing 19 shows how the DataMapPersistence class is configured as a servlet context listener
in the web.xml file.
Listing 19. Configuring the servlet context listener
<web-app ...>
...
<listener>
<listener-class>autosave.DataMapPersistence</listener-class>
</listener>
...
</web-app>
|
In this second part of the series, you've learned how to identify users across sessions, how to implement a thread-safe repository for the form data, how to save and restore the values of the JSF input components, and how to implement the persistence for the data repository. In the third and final part of the series, you'll see how to fill out JSF forms with the saved data and how to use more JavaScript techniques in JSF applications.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample application | wa-aj-jsf2.zip | 19KB | HTTP |
Information about download methods
Learn
-
Find more Ajax resources on Wikipedia.
-
Find useful resources related to Java Maps at The Collections
Framework.
-
The Java
Servlet Specification provides more information on servlet listeners and
filters.
-
Find everything you need know about Forms in HTML 4.01 Documents.
-
The developerWorks Web development
zone is packed with tools and information for Web 2.0 development.
-
The developerWorks Ajax resource
center contains a growing library of Ajax content as well as useful resources to get
you started developing Ajax applications today.
-
Be sure to check out Part 1 of this
series, which lays the foundation for the tasks described in this article. And, stay
tuned for Part 3 of this series, where you'll wrap up the process.
Get products and technologies
-
JavaServer
Faces Technology - Download contains the JSF reference implementation and the JSF specification.
-
The Apache MyFaces Project is another popular JSF implementation.
Discuss
Andrei Cioroianu is a Senior Java Developer and Consultant at Devsphere, a provider of custom Java EE development and Ajax/JSF consulting services. You can reach Andrei through the contact form at www.devsphere.com.
Comments (Undergoing maintenance)





