2 Replies Latest reply on Dec 9, 2011 10:11 AM by fgergaud

    ajax request and view scope lost

    fgergaud

      Hi ,

       

      I am new to JSF 2 and I encounter some problme with managing th eview scope. Here is what I want to do:

      I have 2 pages, both with view scope (call view A and view B). When I click on a button on view A, I want the bean method to be processed; depending on the user entry, I want to go to the view B, or stay in the same page and display a popup.

      I do it with a <h:commandLink..> by inserting <f:ajax render=":popup">. The bean method return null if the popup must be displayed, the identifier of the view B otherwise.

      It works well when displaying the popup. But when going directly to the view B, the view doesn't contain the hidden viewstate hidden fields, making it impossible to restore the view B previously initialized in scope when an action is trigged.

      Looking closer to jsf implementation:

      MultiViewHandler class:

       

      public void writeState(FacesContext context) throws IOException {

       

              Util.notNull("context", context);

              if (!context.getPartialViewContext().isAjaxRequest()

                    && !context.getViewRoot().isTransient()) {

                  if (logger.isLoggable(Level.FINE)) {

                      logger.fine("Begin writing marker for viewId " +

                                  context.getViewRoot().getViewId());

                  }

       

                  WriteBehindStateWriter writer =

                        WriteBehindStateWriter.getCurrentInstance();

                  if (writer != null) {

                      writer.writingState();

                  }

                  context.getResponseWriter()

                        .write(RIConstants.SAVESTATE_FIELD_MARKER);

                  if (logger.isLoggable(Level.FINE)) {

                      logger.fine("End writing marker for viewId " +

                                  context.getViewRoot().getViewId());

                  }

              }

       

          }

       

      if request is of ajax type, the view state is not rendered.

      Why is that?

      How can I solve my problem?

       

      Thanks for your answer

        • 1. Re: ajax request and view scope lost
          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>
          ...
           
           
          • 2. Re: ajax request and view scope lost
            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.