-
1. Re: ajax request and view scope lost
fgergaud Dec 9, 2011 10:06 AM (in response to fgergaud)I finally come to a solution. I tested it, it seems to work well. But cannot make it sure. If someone use it and find some bugs, please tell me.
The seond point is the implementation. It is not the cleanest way to do it(package, full class copy, implicit cast...). I did it with the few time I had.
If you want to try is, you can download the attachment, and unzip it in your source folder. Then configure your faces-config this way:
...
<application>
...
<view-handler>com.sun.faces.application.view.CustomViewHandler</view-handler>
...
</application>
<factory>
<partial-view-context-factory>
com.sun.faces.application.view.CustomPartialViewContextFactory
</partial-view-context-factory>
<view-declaration-language-factory>
com.sun.faces.application.view.CustomViewDeclarationLanguageFactoryImpl
</view-declaration-language-factory>
</factory>
...
Here is what I did:
The problem was when going to a new page from an ajax request, the hidden fields « javax.faces.ViewState» weren’t rendered, preventing the user from performing any from action on this new page. These hidden fields are missing because of the implementation of the com.sun.faces.application.view.MultiViewHandler class
public void writeState(FacesContext context) throws IOException {
Util.notNull("context", context);
if (!context.getPartialViewContext().isAjaxRequest()
&& !context.getViewRoot().isTransient()) {
...
WriteBehindStateWriter writer =
WriteBehindStateWriter.getCurrentInstance();
if (writer != null) {
writer.writingState();
}
context.getResponseWriter().write(RIConstants.SAVESTATE_FIELD_MARKER);
...
}
}
This method is called each time a viewState attribute must be written (basically when rendering a form tag).
The condition makes it impossible for the viewState to be rendered when the request is issued from an ajax component
So I tried to modify this implementation to bring a « javax.faces.ViewState» hidden field, whatever is the request type (i.e.: ajax or not)
My implementation.:
I created a new class called CustomViewHandler which override the previous method and remove the non-ajax restriction
public class CustomViewHandler extends MultiViewHandler {
public void writeState(FacesContext context) throws IOException {
if (!context.getViewRoot().isTransient()) {
...
}
}
When using this implementation, the viewState fields are rendered in a strang manner (surrounded by tilds: RIConstants.SAVESTATE_FIELD_MARKER). This is because the viewState fields are really rendered after le view rendering phase is completed by the method flushToWriter of the class com.sun.faces.application.view.WriteBehindStateWriter.
But this class is not instantiated when processing partial rendering (i.e.:ajax request). The output stream writers are not initialized the same way in ajax and non-ajax rendering:
ResponseWriter origWriter = ctx.getResponseWriter();
if (origWriter == null) {
origWriter = createResponseWriter(ctx);
}
ExternalContext extContext = ctx.getExternalContext();
Writer outputWriter = extContext.getResponseOutputWriter();
stateWriter = new WriteBehindStateWriter(outputWriter, ctx, responseBufferSize);
ResponseWriter writer = origWriter.cloneWithWriter(stateWriter);
ctx.setResponseWriter(writer);
(method renderView from class com.sun.faces.application.view.FaceletViewHandlingStrategy)
When processing partial rendering, the previously defined writer is overridden:
PartialViewContext pvc = ctx.getPartialViewContext();
PartialResponseWriter writer = pvc.getPartialResponseWriter();
ResponseWriter orig = ctx.getResponseWriter();
ctx.getAttributes().put(ORIGINAL_WRITER, orig);
ctx.setResponseWriter(writer);
(method processPartial from class com.sun.faces.context.PartialViewContextImpl)
It is necessary to attach the stateWritter object to the newly created partial rendering writer, this way, the stateWritter will be able to replace the viewState marker in the output stream. To be sure the stateWritter will be attached be beginning writing in the output stream, I modified the renderViewmethod from the class com.sun.faces.application.view.FaceletViewHandlingStrategy
I created a new class called CustomFaceletViewHandlingStrategy in order to override this method:
public void renderView(FacesContext ctx, UIViewRoot viewToRender) throws IOException {
...
// setup writer and assign it to the ctx
ResponseWriter origWriter = ctx.getResponseWriter();
if (origWriter == null) {
origWriter = createResponseWriter(ctx);
}
ResponseWriter writer = null;
ExternalContext extContext = ctx.getExternalContext();
Writer outputWriter = extContext.getResponseOutputWriter();
stateWriter = new WriteBehindStateWriter(outputWriter, ctx, responseBufferSize);
// Don't call startDoc and endDoc on a partial response
if (ctx.getPartialViewContext().isPartialRequest()) {
PartialViewContext pvc = ctx.getPartialViewContext();
writer = pvc.getPartialResponseWriter();
//patch begins
((CustomDelayedInitPartialResponseWriter)writer).initiateStateManaging((PartialResponseWriter)writer,stateWriter);
ResponseWriter orig = ctx.getResponseWriter();
ctx.getAttributes().put(CustomPartialViewContextImpl.ORIGINAL_WRITER, orig);
ctx.setResponseWriter(writer);
// render the view to the response
viewToRender.encodeAll(ctx);
...
} else {
//init the writers
writer = origWriter.cloneWithWriter(stateWriter);
ctx.setResponseWriter(writer);
// render the view to the response
writer.startDocument();
viewToRender.encodeAll(ctx);
...
writer.endDocument();
}
// finish writing
if(writer!=null){
writer.close();
}
boolean writtenState = stateWriter.stateWritten();
// flush to origWriter
if (writtenState) {
stateWriter.flushToWriter();
}
...
}
This way, the viewState marker processing can occur after the view rendering is completed, whatever the request type is (ajax or not). I used a method called initiateStateManaging in a class nammed CustomDelayedInitPartialResponseWriter. This class overrides the com.sun.faces.application.view.DelayedInitPartialResponseWriter class. As I had visibility problems, because of the class definition is included in class com.sun.faces.application.view.PartialViewContextImpl, I recreated the whole class and added the new method initiateStateManaging
public void initiateStateManaging(PartialResponseWriter ori, WriteBehindStateWriter stateWriter){
ResponseWriter responseWriter=ori.cloneWithWriter(stateWriter);
if (responseWriter instanceof PartialResponseWriter) {
writer = (PartialResponseWriter) responseWriter;
} else {
writer = new PartialResponseWriter(responseWriter);
}
(Actually, I used a static method called createPartialResponseWriterWithStateManaging in the CustomPartialViewContextImpl class, and invoked in the initiateStateManaging method)
To make it shorter, I modified 3 classes (named with prefix Custom)
- CustomViewHandler: removing the condition element preventing the viewState rendering for ajax- CustomFaceletViewHandlingStrategy: adding the viewState writer for partial request
- CustomPartialViewContextImpl: defining the inner class CustomDelayedInitPartialResponseWriter to make it possible to attach a viewState writer
The Packaging
As I encoutered visibility problems with the com.sun.faces.application.view.WriteBehindStateWriter class, I put my customs classes in the same package, called com.sun.faces.application.vie.
CustomFaceletViewHandlingStrategy class factory
To use the CustomFaceletViewHandlingStrategy class, a factory must be implemented, which use a cutom manager
factory:
public class CustomViewDeclarationLanguageFactoryImpl extends ViewDeclarationLanguageFactory{
private CustomViewHandlingStrategyManager viewHandlingStrategy;
@Override
public ViewDeclarationLanguage getViewDeclarationLanguage(String viewId) {
return getViewHandlingStrategyManager().getStrategy(viewId);
}
private CustomViewHandlingStrategyManager getViewHandlingStrategyManager() {
if (viewHandlingStrategy == null) {
viewHandlingStrategy =
new CustomViewHandlingStrategyManager();
}
return viewHandlingStrategy;
}
}
The manager introducing the CustomFaceletViewHandlingStrategy and used by the CustomViewDeclarationLanguageFactoryImpl factory :
public class CustomViewHandlingStrategyManager {
// The strategies associate with this instance
private volatile ViewHandlingStrategy[] strategies;
public CustomViewHandlingStrategyManager() {
WebConfiguration webConfig = WebConfiguration.getInstance();
boolean pdlDisabled = webConfig
.isOptionEnabled(WebConfiguration.BooleanWebContextInitParameter.DisableFaceletJSFViewHandler);
strategies = ((pdlDisabled)
? new ViewHandlingStrategy[] { new JspViewHandlingStrategy() }
: new ViewHandlingStrategy[] { new CustomFaceletViewHandlingStrategy(),
new JspViewHandlingStrategy() });
}
...
CustomPartialViewContextImpl class factory
The same as previously, without the manager:
public class CustomPartialViewContextFactory extends PartialViewContextFactory {
public PartialViewContext getPartialViewContext(FacesContext context) {
return new CustomPartialViewContextImpl(context);
}
}
faces-config configuration
...
<application>
...
<view-handler>com.sun.faces.application.view.CustomViewHandler</view-handler>
...
</application>
<factory>
<partial-view-context-factory>
com.sun.faces.application.view.CustomPartialViewContextFactory
</partial-view-context-factory>
<view-declaration-language-factory>
com.sun.faces.application.view.CustomViewDeclarationLanguageFactoryImpl
</view-declaration-language-factory>
</factory>
...
-
ajax and viewState patch.zip 10.4 KB
-
-
2. Re: ajax request and view scope lost
fgergaud Dec 9, 2011 10:11 AM (in response to fgergaud)I don't know if anybody is interested in this thread.
I encountered some problemes when using richfaces above jsf. The ajax request wheren't processed by my partialViewContextImpl. It is because of the way factories work.
I had to write again the richfaces' factory hierarchy in the faces-config, by specifying mine in first:
<factory>
...
<partial-view-context-factory>com.sun.faces.application.view.CustomPartialViewContextFactoryImpl</partial-view-context-factory>
<partial-view-context-factory>org.richfaces.context.FileUploadPartialViewContextFactory</partial-view-context-factory>
<partial-view-context-factory>org.richfaces.context.ExtendedPartialViewContextFactoryImpl</partial-view-context-factory>
...
</factory>
This way the final wrapped class by richfaces is mine, not the jsf built-in one.