View Javadoc

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