Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
JspViewHandlerImpl |
|
| 0.0;0 | ||||
JspViewHandlerImpl$StateMarkerAwareWriter |
|
| 0.0;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 | } |