Here’s the scenario, we have a JSF app using PrimeFaces and running on Tomcat 7, all managed beans are ViewScoped and they have a quite complex structure. Recently we were asked to implement the following requirement: Generate a URL that can be shared among users. By clicking on the link, the view should be restored to the same state saved previously when the link was generated.
In general lines, my idea is to implement a phase listener and invoke saveState/restoreState on viewRoot object accordingly. By the time the URL is generated, the phaseListener will get the UIViewRoot state by invoking saveState and then serialize the object and persist in a database table. The reverse will happen when the link is accessed, the object will be retrieved from the database, deserialized and restored on the UIViewRoot by invoking restoredState.
When trying to restore the state the actual results is not what I expected, since the the managed beans properties are null after invoking restoreState on UIViewRoot.
The saveState is being called after the render phase is completed, and the serializable object looks good as all values are set correctly. The restoreState is being called in the “restore view” phase, I have been trying many combinations about the time the restoreState is invoked (after/before phases) and none seems to work.
For testing purposes I have implemented the methods in a PhaseListener. Here’s the code snippet, the logic to call them is omitted:
private void saveState(FacesContext context) {
UIViewRoot viewRoot = context.getViewRoot();
Object savedState = viewRoot.saveState(context);
if(savedState instanceof Serializable) {
Serializable s = ((Serializable) savedState);
try {
this.serialize(s);
} catch (IOException e) {
logger.error("Error saving state.", e);
}
}
}
private void restoreState(FacesContext context) {
UIViewRoot viewRoot = new UIViewRoot();
FileInputStream fis;
try {
fis = new FileInputStream("/view.ser");
Serializable savedState = this.deserialize(fis);
viewRoot.restoreState(context, savedState);
} catch (ClassNotFoundException | IOException e) {
logger.error("Error restoring state.", e);
}
}
I’m not a JSF expert and therefore I’m not sure if the approach I’m trying to use makes any sense at all, so here’s my question for you JSF gurus: can this requirement be implemented somehow? If yes, what is the correct way to implement it? I have researched the forum and I cannot find something similar that I could reuse.
Any help will be greatly appreciated. Thanks ahead.
UPDATE:
This is how we managed to save and restore the view state.
The view state is being saved and serialized in the web bean:
private void saveViewState(FacesContext context) throws IOException {
UIViewRoot viewRoot = context.getViewRoot();
Object savedState = viewRoot.saveState(context);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(savedState);
}
In order to restore the state, a phase listener intercepts the Faces Request before Restore View phase, deserialize the view state object and invokes restoreState
on UIViewRoot
:
private void restoreState(FacesContext context) {
// details omitted
ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais);
Serializable viewState = (Serializable) ois.readObject();
UIViewRoot viewRoot = new UIViewRoot();
viewRoot.restoreState(context, viewState);
viewRoot.setRenderKitId(RenderKitFactory.HTML_BASIC_RENDER_KIT);
viewRoot.setViewId(savedView.getViewId());
context.setViewRoot(viewRoot);
context.renderResponse();
}