Coverage Report - org.apache.myfaces.application.jsp.JspViewHandlerImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
JspViewHandlerImpl
0%
0/146
0%
0/68
0
JspViewHandlerImpl$StateMarkerAwareWriter
0%
0/46
0%
0/22
0
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *   http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package org.apache.myfaces.application.jsp;
 20  
 
 21  
 import org.apache.commons.logging.Log;
 22  
 import org.apache.commons.logging.LogFactory;
 23  
 import org.apache.myfaces.application.DefaultViewHandlerSupport;
 24  
 import org.apache.myfaces.application.InvalidViewIdException;
 25  
 import org.apache.myfaces.application.ViewHandlerSupport;
 26  
 import org.apache.myfaces.shared_impl.config.MyfacesConfig;
 27  
 import org.apache.myfaces.shared_impl.renderkit.html.util.JavascriptUtils;
 28  
 
 29  
 import javax.faces.FacesException;
 30  
 import javax.faces.FactoryFinder;
 31  
 import javax.faces.application.Application;
 32  
 import javax.faces.application.StateManager;
 33  
 import javax.faces.application.ViewHandler;
 34  
 import javax.faces.component.UIViewRoot;
 35  
 import javax.faces.context.ExternalContext;
 36  
 import javax.faces.context.FacesContext;
 37  
 import javax.faces.context.ResponseWriter;
 38  
 import javax.faces.render.RenderKit;
 39  
 import javax.faces.render.RenderKitFactory;
 40  
 import javax.faces.render.ResponseStateManager;
 41  
 import javax.servlet.ServletRequest;
 42  
 import javax.servlet.ServletResponse;
 43  
 import javax.servlet.http.HttpServletRequest;
 44  
 import javax.servlet.http.HttpServletResponse;
 45  
 import javax.servlet.http.HttpSession;
 46  
 import javax.servlet.jsp.jstl.core.Config;
 47  
 import java.io.IOException;
 48  
 import java.io.StringWriter;
 49  
 import java.io.Writer;
 50  
 import java.util.Iterator;
 51  
 import java.util.Locale;
 52  
 
 53  
 /**
 54  
  * Implementation of the ViewHandler interface that knows how to use JSP pages
 55  
  * as the view templating mechanism.
 56  
  * <p>
 57  
  * This implementation works tightly together with the various JSP TagHandler classes
 58  
  * to implement the behaviour mandated by the ViewHandler specification. 
 59  
  * <p>
 60  
  * Rendering of a view is done in two parts: first a jsp-generated servlet is invoked
 61  
  * to create or refresh a jsf component tree, then the component tree is walked to generate
 62  
  * the output to send to the user.
 63  
  * <p>
 64  
  * The invoked servlet is the one generated from the jsp file which corresponds to the
 65  
  * viewId of the view being rendered. As is normal for jsp, this servlet alternates between
 66  
  * writing literal text to the response output stream and invoking "tag handler" classes
 67  
  * representing the jsp tags that were present in the page. This servlet is not aware of
 68  
  * JSF at all.
 69  
  * <p>
 70  
  * On the first visit to a view, when each JSF taghandler is invoked, the corresponding
 71  
  * JSF component will not yet exist so it is created and added to the current view tree.
 72  
  * Each JSF taghandler also marks itself as having "buffered body content", which means that
 73  
  * after the start-tag is executed a temporary output stream is installed for the response.
 74  
  * Any output generated by the jsp-servlet therefore gets written into a memory buffer
 75  
  * rather than sent via the network socket to the sender of the request. When the end
 76  
  * of the JSF tag is encountered, the JSF tag checks whether any such body text did exist,
 77  
  * and if so it creates a transient f:verbatim component and inserts it into the component
 78  
  * tree. The final result is that after this "first pass" a component tree exists which has
 79  
  * all the JSF components in it, plus a bunch of auto-generated f:verbatim components that
 80  
  * hold all plain text, or output generated by non-jsf jsp tags. Absolutely NO output has
 81  
  * yet been sent to the real response stream.
 82  
  * <p>
 83  
  * On later visits to the same view, the component tree already exists (has been restored).
 84  
  * However the "verbatim" components holding static text are not present as they were
 85  
  * marked "transient" (not keeping them reduces the amount of memory required to "save state").
 86  
  * Note that these components are not needed for any phase prior to RENDER because they
 87  
  * are not "input" components. When the jsp-generated servlet is executed, JSF taghandlers
 88  
  * that are invoked will simply verify that a corresponding component already exists in the
 89  
  * view-tree rather than creating a new one. However the "body buffering" occurs again, so
 90  
  * that the appropriate transient verbatim components are once again created and inserted into
 91  
  * the tree.
 92  
  * <p>
 93  
  * Regardless of whether the view is new or restored, the rendered output can now be generated
 94  
  * simply by walking the component tree and invoking the encodeBegin/encodeChildren/encodeEnd
 95  
  * methods on each component. The static components simply output their contained text.
 96  
  * <p>
 97  
  * Notes for JSF1.1 users: the new two-phase approach that uses "output buffering" to capture
 98  
  * non-JSF output is rather like wrapping all non-jsf components in an invisible f:verbatim tag.
 99  
  * Although that doesn't sound like a big change, it allows processing to occur in two passes
 100  
  * rather than one. And that means that before any component is rendered the entire component
 101  
  * tree already exists. This solves a number of JSF1.1 problems, including output-ordering
 102  
  * problems between text and jsf components, and errors when using the "for" attribute of a
 103  
  * label to reference a component later in the page. It does introduce a performance penalty;
 104  
  * non-JSF-generated output now gets buffered rather than being streamed directly to the
 105  
  * user.
 106  
  * <p>
 107  
  * @author Thomas Spiegl (latest modification by $Author: bommel $)
 108  
  * @author Bruno Aranda
 109  
  * @version $Revision: 693059 $ $Date: 2008-09-08 06:42:28 -0500 (Mon, 08 Sep 2008) $
 110  
  */
 111  
 public class JspViewHandlerImpl extends ViewHandler
 112  
 {
 113  0
     private static final Log log = LogFactory.getLog(JspViewHandlerImpl.class);
 114  
     public static final String FORM_STATE_MARKER = "<!--@@JSF_FORM_STATE_MARKER@@-->";
 115  0
     public static final int FORM_STATE_MARKER_LEN = FORM_STATE_MARKER.length();
 116  
 
 117  0
     private static final String AFTER_VIEW_TAG_CONTENT_PARAM = JspViewHandlerImpl.class + ".AFTER_VIEW_TAG_CONTENT";
 118  
 
 119  
     private ViewHandlerSupport _viewHandlerSupport;
 120  
 
 121  
     public JspViewHandlerImpl()
 122  0
     {
 123  0
         if (log.isTraceEnabled())
 124  0
             log.trace("New ViewHandler instance created");
 125  0
     }
 126  
 
 127  
     /**
 128  
      * @param viewHandlerSupport
 129  
      *            the viewHandlerSupport to set
 130  
      */
 131  
     public void setViewHandlerSupport(ViewHandlerSupport viewHandlerSupport)
 132  
     {
 133  0
         _viewHandlerSupport = viewHandlerSupport;
 134  0
     }
 135  
 
 136  
     /**
 137  
      * @return the viewHandlerSupport
 138  
      */
 139  
     protected ViewHandlerSupport getViewHandlerSupport()
 140  
     {
 141  0
         if (_viewHandlerSupport == null)
 142  
         {
 143  0
             _viewHandlerSupport = new DefaultViewHandlerSupport();
 144  
         }
 145  0
         return _viewHandlerSupport;
 146  
     }
 147  
 
 148  
     /**
 149  
      * Get the locales specified as acceptable by the original request, compare them to the
 150  
      * locales supported by this Application and return the best match.
 151  
      */
 152  
     public Locale calculateLocale(FacesContext facesContext)
 153  
     {
 154  0
         Application application = facesContext.getApplication();
 155  0
         for (Iterator<Locale> requestLocales = facesContext.getExternalContext().getRequestLocales(); requestLocales
 156  0
                 .hasNext();)
 157  
         {
 158  0
             Locale requestLocale = requestLocales.next();
 159  0
             for (Iterator<Locale> supportedLocales = application.getSupportedLocales(); supportedLocales.hasNext();)
 160  
             {
 161  0
                 Locale supportedLocale = supportedLocales.next();
 162  
                 // higher priority to a language match over an exact match
 163  
                 // that occures further down (see Jstl Reference 1.0 8.3.1)
 164  0
                 if (requestLocale.getLanguage().equals(supportedLocale.getLanguage())
 165  
                         && (supportedLocale.getCountry() == null || supportedLocale.getCountry().length() == 0))
 166  
                 {
 167  0
                     return supportedLocale;
 168  
                 }
 169  0
                 else if (supportedLocale.equals(requestLocale))
 170  
                 {
 171  0
                     return supportedLocale;
 172  
                 }
 173  0
             }
 174  0
         }
 175  
 
 176  0
         Locale defaultLocale = application.getDefaultLocale();
 177  0
         return defaultLocale != null ? defaultLocale : Locale.getDefault();
 178  
     }
 179  
 
 180  
     public String calculateRenderKitId(FacesContext facesContext)
 181  
     {
 182  0
         Object renderKitId = facesContext.getExternalContext().getRequestMap().get(
 183  
                 ResponseStateManager.RENDER_KIT_ID_PARAM);
 184  0
         if (renderKitId == null)
 185  
         {
 186  0
             renderKitId = facesContext.getApplication().getDefaultRenderKitId();
 187  
         }
 188  0
         if (renderKitId == null)
 189  
         {
 190  0
             renderKitId = RenderKitFactory.HTML_BASIC_RENDER_KIT;
 191  
         }
 192  0
         return renderKitId.toString();
 193  
     }
 194  
 
 195  
     /**
 196  
      * Create a UIViewRoot object and return it; the returned object has no children.
 197  
      * <p>
 198  
      * As required by the spec, the returned object inherits locale and renderkit settings from
 199  
      * the viewRoot currently configured for the facesContext (if any). This means that on navigation
 200  
      * from one view to another these settings are "inherited".
 201  
      */
 202  
     public UIViewRoot createView(FacesContext facesContext, String viewId)
 203  
     {
 204  0
         String calculatedViewId = viewId;
 205  
         try
 206  
         {
 207  0
             calculatedViewId = getViewHandlerSupport().calculateViewId(facesContext, viewId);
 208  
         }
 209  0
         catch (InvalidViewIdException e)
 210  
         {
 211  0
             sendSourceNotFound(facesContext, e.getMessage());
 212  0
         }
 213  
 
 214  0
         Application application = facesContext.getApplication();
 215  0
         ViewHandler applicationViewHandler = application.getViewHandler();
 216  
 
 217  0
         Locale currentLocale = null;
 218  0
         String currentRenderKitId = null;
 219  0
         UIViewRoot uiViewRoot = facesContext.getViewRoot();
 220  0
         if (uiViewRoot != null)
 221  
         {
 222  
             // Remember current locale and renderKitId
 223  0
             currentLocale = uiViewRoot.getLocale();
 224  0
             currentRenderKitId = uiViewRoot.getRenderKitId();
 225  
         }
 226  
 
 227  0
         uiViewRoot = (UIViewRoot) application.createComponent(UIViewRoot.COMPONENT_TYPE);
 228  
 
 229  0
         uiViewRoot.setViewId(calculatedViewId);
 230  
 
 231  0
         if (currentLocale != null)
 232  
         {
 233  
             // set old locale
 234  0
             uiViewRoot.setLocale(currentLocale);
 235  
         }
 236  
         else
 237  
         {
 238  
             // calculate locale
 239  0
             uiViewRoot.setLocale(applicationViewHandler.calculateLocale(facesContext));
 240  
         }
 241  
 
 242  0
         if (currentRenderKitId != null)
 243  
         {
 244  
             // set old renderKit
 245  0
             uiViewRoot.setRenderKitId(currentRenderKitId);
 246  
         }
 247  
         else
 248  
         {
 249  
             // calculate renderKit
 250  0
             uiViewRoot.setRenderKitId(applicationViewHandler.calculateRenderKitId(facesContext));
 251  
         }
 252  
 
 253  0
         if (log.isTraceEnabled())
 254  0
             log.trace("Created view " + viewId);
 255  0
         return uiViewRoot;
 256  
     }
 257  
 
 258  
     private void sendSourceNotFound(FacesContext context, String message)
 259  
     {
 260  0
         HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
 261  
         try
 262  
         {
 263  0
             context.responseComplete();
 264  0
             response.sendError(HttpServletResponse.SC_NOT_FOUND, message);
 265  
         }
 266  0
         catch (IOException ioe)
 267  
         {
 268  0
             throw new FacesException(ioe);
 269  0
         }
 270  0
     }
 271  
 
 272  
     /**
 273  
      * Return a string containing a webapp-relative URL that the user can invoke
 274  
      * to render the specified view.
 275  
      * <p>
 276  
      * URLs and ViewIds are not quite the same; for example a url of "/foo.jsf"
 277  
      * or "/faces/foo.jsp" may be needed to access the view "/foo.jsp". 
 278  
      * <p>
 279  
      * This method simply delegates to ViewHandlerSupport.calculateActionURL.
 280  
      */
 281  
     public String getActionURL(FacesContext facesContext, String viewId)
 282  
     {
 283  0
         return getViewHandlerSupport().calculateActionURL(facesContext, viewId);
 284  
     }
 285  
 
 286  
     public String getResourceURL(FacesContext facesContext, String path)
 287  
     {
 288  0
         if (path.length() > 0 && path.charAt(0) == '/')
 289  
         {
 290  0
             return facesContext.getExternalContext().getRequestContextPath() + path;
 291  
         }
 292  
 
 293  0
         return path;
 294  
 
 295  
     }
 296  
 
 297  
     /**
 298  
      * Generate output to the user by combining the data in the jsp-page specified by viewToRender
 299  
      * with the existing JSF component tree (if any).
 300  
      * <p>
 301  
      * As described in the class documentation, this first runs the jsp-generated servlet to
 302  
      * create or enhance the JSF component tree - including verbatim nodes for any non-jsf
 303  
      * data in that page.
 304  
      * <p>
 305  
      * The component tree is then walked to generate the appropriate output for each component.
 306  
      */
 307  
     public void renderView(FacesContext facesContext, UIViewRoot viewToRender) throws IOException, FacesException
 308  
     {
 309  0
         if (viewToRender == null)
 310  
         {
 311  0
             log.fatal("viewToRender must not be null");
 312  0
             throw new NullPointerException("viewToRender must not be null");
 313  
         }
 314  
 
 315  
         // do not render the view if the rendered attribute for the view is false
 316  0
         if (!viewToRender.isRendered())
 317  
         {
 318  0
             if (log.isTraceEnabled())
 319  0
                 log.trace("View is not rendered");
 320  0
             return;
 321  
         }
 322  
 
 323  0
         ExternalContext externalContext = facesContext.getExternalContext();
 324  
 
 325  0
         String viewId = facesContext.getViewRoot().getViewId();
 326  
 
 327  0
         if (log.isTraceEnabled())
 328  0
             log.trace("Rendering JSP view: " + viewId);
 329  
 
 330  0
         ServletResponse response = (ServletResponse) externalContext.getResponse();
 331  0
         ServletRequest request = (ServletRequest) externalContext.getRequest();
 332  
 
 333  0
         Locale locale = viewToRender.getLocale();
 334  0
         response.setLocale(locale);
 335  0
         Config.set(request, Config.FMT_LOCALE, facesContext.getViewRoot().getLocale());
 336  
 
 337  0
         if(!buildView(response, externalContext, viewId)) {
 338  
             //building the view was unsuccessful - an exception occurred during rendering
 339  
             //we need to jump out
 340  0
             return;
 341  
         }
 342  
 
 343  
         // handle character encoding as of section 2.5.2.2 of JSF 1.1
 344  0
         if (externalContext.getRequest() instanceof HttpServletRequest)
 345  
         {
 346  0
             HttpServletRequest httpServletRequest = (HttpServletRequest) externalContext.getRequest();
 347  0
             HttpSession session = httpServletRequest.getSession(false);
 348  
 
 349  0
             if (session != null)
 350  
             {
 351  0
                 session.setAttribute(ViewHandler.CHARACTER_ENCODING_KEY, response.getCharacterEncoding());
 352  
             }
 353  
         }
 354  
 
 355  
         // render the view in this method (since JSF 1.2)
 356  0
         RenderKitFactory renderFactory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
 357  0
         RenderKit renderKit = renderFactory.getRenderKit(facesContext, viewToRender.getRenderKitId());
 358  
 
 359  0
         ResponseWriter responseWriter = facesContext.getResponseWriter();
 360  0
         if (responseWriter == null)
 361  
         {
 362  0
             responseWriter = renderKit.createResponseWriter(response.getWriter(), null,
 363  
                     ((HttpServletRequest) externalContext.getRequest()).getCharacterEncoding());
 364  0
             facesContext.setResponseWriter(responseWriter);
 365  
         }
 366  
 
 367  0
         ResponseWriter oldResponseWriter = responseWriter;
 368  0
         StateMarkerAwareWriter stateAwareWriter = null;
 369  
 
 370  0
         StateManager stateManager = facesContext.getApplication().getStateManager();
 371  0
         if (stateManager.isSavingStateInClient(facesContext))
 372  
         {
 373  0
             stateAwareWriter = new StateMarkerAwareWriter();
 374  
 
 375  
             // Create a new response-writer using as an underlying writer the stateAwareWriter
 376  
             // Effectively, all output will be buffered in the stateAwareWriter so that later
 377  
             // this writer can replace the state-markers with the actual state.
 378  0
             responseWriter = hookInStateAwareWriter(
 379  
                     oldResponseWriter, stateAwareWriter, renderKit, externalContext);
 380  0
             facesContext.setResponseWriter(responseWriter);
 381  
         }
 382  
 
 383  0
         actuallyRenderView(facesContext, viewToRender);
 384  
 
 385  0
         facesContext.setResponseWriter(oldResponseWriter);
 386  
 
 387  
         //We're done with the document - now we can write all content
 388  
         //to the response, properly replacing the state-markers on the way out
 389  
         //by using the stateAwareWriter
 390  0
         if (stateManager.isSavingStateInClient(facesContext))
 391  
         {
 392  0
             stateAwareWriter.flushToWriter(response.getWriter());
 393  
         }
 394  
         else
 395  
         {
 396  0
             stateManager.saveView(facesContext);
 397  
         }
 398  
 
 399  
         // Final step - we output any content in the wrappedResponse response from above to the response,
 400  
         // removing the wrappedResponse response from the request, we don't need it anymore
 401  0
         ViewResponseWrapper afterViewTagResponse = (ViewResponseWrapper) externalContext.getRequestMap().get(
 402  
                 AFTER_VIEW_TAG_CONTENT_PARAM);
 403  0
         externalContext.getRequestMap().remove(AFTER_VIEW_TAG_CONTENT_PARAM);
 404  
 
 405  0
         if (afterViewTagResponse != null)
 406  
         {
 407  0
             afterViewTagResponse.flushToWriter(response.getWriter(),
 408  
                     facesContext.getExternalContext().getResponseCharacterEncoding());
 409  
         }
 410  
 
 411  0
         response.flushBuffer();
 412  0
     }
 413  
 
 414  
     /**
 415  
      * Render the view now - properly setting and resetting the response writer
 416  
      */
 417  
     private void actuallyRenderView(FacesContext facesContext,
 418  
                                     UIViewRoot viewToRender) throws IOException {
 419  
         // Set the new ResponseWriter into the FacesContext, saving the old one aside.
 420  0
         ResponseWriter responseWriter = facesContext.getResponseWriter();
 421  
 
 422  
         //Now we actually render the document
 423  
         // Call startDocument() on the ResponseWriter.
 424  0
         responseWriter.startDocument();
 425  
 
 426  
         // Call encodeAll() on the UIViewRoot
 427  0
         viewToRender.encodeAll(facesContext);
 428  
 
 429  
         // Call endDocument() on the ResponseWriter
 430  0
         responseWriter.endDocument();
 431  
 
 432  0
         responseWriter.flush();
 433  0
     }
 434  
 
 435  
     /**Create a new response-writer using as an underlying writer the stateAwareWriter
 436  
      * Effectively, all output will be buffered in the stateAwareWriter so that later
 437  
      * this writer can replace the state-markers with the actual state.
 438  
      *
 439  
      * If the FacesContext has a non-null ResponseWriter create a new writer using its
 440  
      * cloneWithWriter() method, passing the response's Writer as the argument.
 441  
      * Otherwise, use the current RenderKit to create a new ResponseWriter.
 442  
      *
 443  
      * @param oldResponseWriter
 444  
      * @param stateAwareWriter
 445  
      * @param renderKit
 446  
      * @param externalContext
 447  
      * @return
 448  
      */
 449  
     private ResponseWriter hookInStateAwareWriter(ResponseWriter oldResponseWriter, StateMarkerAwareWriter stateAwareWriter, RenderKit renderKit, ExternalContext externalContext) {
 450  0
         return oldResponseWriter.cloneWithWriter(stateAwareWriter);
 451  
         /*
 452  
         ResponseWriter newResponseWriter;
 453  
         if (oldResponseWriter != null)
 454  
         {
 455  
             newResponseWriter = oldResponseWriter.cloneWithWriter(stateAwareWriter);
 456  
         }
 457  
         else
 458  
         {
 459  
             if (log.isTraceEnabled())
 460  
                 log.trace("Creating new ResponseWriter");
 461  
             newResponseWriter = renderKit.createResponseWriter(stateAwareWriter, null,
 462  
                     ((HttpServletRequest) externalContext.getRequest()).getCharacterEncoding());
 463  
         }
 464  
         return newResponseWriter;
 465  
         */
 466  
     }
 467  
 
 468  
     /**Build the view-tree before rendering.
 469  
      * This is done by dispatching to the underlying JSP-page, effectively processing it, creating
 470  
      * components out of any text in between JSF components (not rendering the text to the output of course, this
 471  
      * will happen later while rendering), attaching these components
 472  
      * to the component tree, and buffering any content after the view-root.
 473  
      *
 474  
      * @param response The current response - it will be replaced while the view-building happens (we want the text in the component tree, not on the actual servlet output stream)
 475  
      * @param externalContext The external context where the response will be replaced while building
 476  
      * @param viewId The view-id to dispatch to
 477  
      * @return true if successfull, false if an error occurred during rendering
 478  
      * @throws IOException
 479  
      */
 480  
     private boolean buildView(ServletResponse response, ExternalContext externalContext, String viewId) throws IOException {
 481  0
         ViewResponseWrapper wrappedResponse = new ViewResponseWrapper((HttpServletResponse) response);
 482  
 
 483  0
         externalContext.setResponse(wrappedResponse);
 484  
         try
 485  
         {
 486  0
             externalContext.dispatch(viewId);
 487  
         }
 488  
         finally
 489  
         {
 490  0
             externalContext.setResponse(response);
 491  0
         }
 492  
 
 493  0
         boolean errorResponse = wrappedResponse.getStatus() < 200 || wrappedResponse.getStatus() > 299;
 494  0
         if (errorResponse)
 495  
         {
 496  0
             wrappedResponse.flushToWrappedResponse();
 497  0
             return false;
 498  
         }
 499  
 
 500  
         // store the wrapped response in the request, so it is thread-safe
 501  0
         externalContext.getRequestMap().put(AFTER_VIEW_TAG_CONTENT_PARAM, wrappedResponse);
 502  
 
 503  0
         return true;
 504  
     }
 505  
 
 506  
     /**
 507  
      * Just invoke StateManager.restoreView.
 508  
      */
 509  
     public UIViewRoot restoreView(FacesContext facesContext, String viewId)
 510  
     {
 511  0
         Application application = facesContext.getApplication();
 512  0
         ViewHandler applicationViewHandler = application.getViewHandler();
 513  0
         String renderKitId = applicationViewHandler.calculateRenderKitId(facesContext);
 514  0
         String calculatedViewId = getViewHandlerSupport().calculateViewId(facesContext, viewId);
 515  0
         UIViewRoot viewRoot = application.getStateManager().restoreView(facesContext, calculatedViewId, renderKitId);
 516  0
         return viewRoot;
 517  
     }
 518  
 
 519  
     /**
 520  
      * Writes a state marker that is replaced later by one or more hidden form
 521  
      * inputs.
 522  
      * <p>
 523  
      * The problem with html is that the only place to encode client-side state is
 524  
      * in a hidden html input field. However when a form is submitted, only the fields
 525  
      * within a particular form are sent; fields in other forms are not sent. Therefore
 526  
      * the view tree state must be written into every form in the page. This method
 527  
      * is therefore invoked at the end of every form.
 528  
      * <p>
 529  
      * Theoretically the state of a component tree will not change after rendering
 530  
      * starts. Therefore it is possible to create a serialized representation of that
 531  
      * state at the start of the rendering phase (or when first needed) and output it
 532  
      * whenever needed as described above. However this is not currently implemented;
 533  
      * instead the entire page being generated is buffered, and a "marker" string is
 534  
      * output instead of the tree state. After the rendering pass is complete the component
 535  
      * final tree state is computed and the buffer is then post-processed to replace the
 536  
      * "marker" strings with the real data. 
 537  
      * <p>
 538  
      * This method also supports "javascript viewstate". TODO: document this.
 539  
      *  
 540  
      * @param facesContext
 541  
      * @throws IOException
 542  
      */
 543  
     public void writeState(FacesContext facesContext) throws IOException
 544  
     {
 545  0
         StateManager stateManager = facesContext.getApplication().getStateManager();
 546  0
         if (stateManager.isSavingStateInClient(facesContext))
 547  
         {
 548  
         // Only write state marker if javascript view state is disabled
 549  0
         ExternalContext extContext = facesContext.getExternalContext();
 550  0
         if (!(JavascriptUtils.isJavascriptAllowed(extContext) && MyfacesConfig.getCurrentInstance(extContext).isViewStateJavascript())) {
 551  0
             facesContext.getResponseWriter().write(FORM_STATE_MARKER);
 552  
         }
 553  0
         }
 554  
         else
 555  
         {
 556  0
             stateManager.writeState(facesContext, new Object[2]);
 557  
         }
 558  0
     }
 559  
 
 560  
     /**
 561  
      * Writes the response and replaces the state marker tags with the state information for the current context
 562  
      */
 563  
     private static class StateMarkerAwareWriter extends Writer
 564  
     {
 565  
         private StringBuilder buf;
 566  
 
 567  
         public StateMarkerAwareWriter()
 568  0
         {
 569  0
             this.buf = new StringBuilder();
 570  0
         }
 571  
 
 572  
         @Override
 573  
         public void close() throws IOException
 574  
         {
 575  0
         }
 576  
 
 577  
         @Override
 578  
         public void flush() throws IOException
 579  
         {
 580  0
         }
 581  
 
 582  
         @Override
 583  
         public void write(char[] cbuf, int off, int len) throws IOException
 584  
         {
 585  0
             if ((off < 0) || (off > cbuf.length) || (len < 0) ||
 586  
                     ((off + len) > cbuf.length) || ((off + len) < 0)) {
 587  0
                 throw new IndexOutOfBoundsException();
 588  0
             } else if (len == 0) {
 589  0
                 return;
 590  
             }
 591  0
             buf.append(cbuf, off, len);
 592  0
         }
 593  
 
 594  
         public StringBuilder getStringBuilder()
 595  
         {
 596  0
             return buf;
 597  
         }
 598  
 
 599  
         public void flushToWriter(Writer writer) throws IOException
 600  
         {
 601  0
             FacesContext facesContext = FacesContext.getCurrentInstance();
 602  0
             StateManager stateManager = facesContext.getApplication().getStateManager();
 603  
 
 604  0
             StringWriter stateWriter = new StringWriter();
 605  0
             ResponseWriter realWriter = facesContext.getResponseWriter();
 606  0
             facesContext.setResponseWriter(realWriter.cloneWithWriter(stateWriter));
 607  
 
 608  0
             Object serializedView = stateManager.saveView(facesContext);
 609  
 
 610  0
             stateManager.writeState(facesContext, serializedView);
 611  0
             facesContext.setResponseWriter(realWriter);
 612  
 
 613  0
             StringBuilder contentBuffer = getStringBuilder();
 614  0
             String state = stateWriter.getBuffer().toString();
 615  
 
 616  0
             ExternalContext extContext = facesContext.getExternalContext();
 617  0
             if (JavascriptUtils.isJavascriptAllowed(extContext) && MyfacesConfig.getCurrentInstance(extContext).isViewStateJavascript()) {
 618  
                 // If javascript viewstate is enabled no state markers were written
 619  0
                 write(contentBuffer, 0, contentBuffer.length(), writer);
 620  0
                 writer.write(state);
 621  
             } else {
 622  
                 // If javascript viewstate is disabled state markers must be replaced
 623  0
                 int lastFormMarkerPos = 0;
 624  0
                 int formMarkerPos = 0;
 625  
                 // Find all state markers and write out actual state instead
 626  0
                 while ((formMarkerPos = contentBuffer.indexOf(FORM_STATE_MARKER, formMarkerPos)) > -1)
 627  
                 {
 628  
                     // Write content before state marker
 629  0
                     write(contentBuffer, lastFormMarkerPos, formMarkerPos, writer);
 630  
                     // Write state and move position in buffer after marker
 631  0
                     writer.write(state);
 632  0
                     formMarkerPos += FORM_STATE_MARKER_LEN;
 633  0
                     lastFormMarkerPos = formMarkerPos;
 634  
                 }
 635  
                 // Write content after last state marker
 636  0
                 if (lastFormMarkerPos < contentBuffer.length()) {
 637  0
                     write(contentBuffer, lastFormMarkerPos, contentBuffer.length(), writer);
 638  
                 }
 639  
             }
 640  
 
 641  0
         }
 642  
 
 643  
         /**
 644  
          * Writes the content of the specified StringBuffer from index
 645  
          * <code>beginIndex</code> to index <code>endIndex - 1</code>.
 646  
          *
 647  
          * @param contentBuffer  the <code>StringBuffer</code> to copy content from
 648  
          * @param beginIndex  the beginning index, inclusive.
 649  
          * @param endIndex  the ending index, exclusive
 650  
          * @param writer  the <code>Writer</code> to write to
 651  
          * @throws IOException  if an error occurs writing to specified <code>Writer</code>
 652  
          */
 653  
         private void write(StringBuilder contentBuffer, int beginIndex, int endIndex, Writer writer) throws IOException {
 654  0
             int index = beginIndex;
 655  0
             int bufferSize = 2048;
 656  0
             char[] bufToWrite = new char[bufferSize];
 657  
 
 658  0
             while (index < endIndex)
 659  
             {
 660  0
                 int maxSize = Math.min(bufferSize, endIndex - index);
 661  
 
 662  0
                 contentBuffer.getChars(index, index + maxSize, bufToWrite, 0);
 663  0
                 writer.write(bufToWrite, 0, maxSize);
 664  
 
 665  0
                 index += bufferSize;
 666  0
             }
 667  0
         }
 668  
     }
 669  
 }