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