1 /*
2 * $Id: ViewHandlerImpl.java,v 1.109.4.5 2008/06/11 18:03:03 rlubke Exp $
3 */
4
5
6 /*
7 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
8 *
9 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
10 *
11 * The contents of this file are subject to the terms of either the GNU
12 * General Public License Version 2 only ("GPL") or the Common Development
13 * and Distribution License("CDDL") (collectively, the "License"). You
14 * may not use this file except in compliance with the License. You can obtain
15 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
16 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
17 * language governing permissions and limitations under the License.
18 *
19 * When distributing the software, include this License Header Notice in each
20 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
21 * Sun designates this particular file as subject to the "Classpath" exception
22 * as provided by Sun in the GPL Version 2 section of the License file that
23 * accompanied this code. If applicable, add the following below the License
24 * Header, with the fields enclosed by brackets [] replaced by your own
25 * identifying information: "Portions Copyrighted [year]
26 * [name of copyright owner]"
27 *
28 * Contributor(s):
29 *
30 * If you wish your version of this file to be governed by only the CDDL or
31 * only the GPL Version 2, indicate your decision by adding "[Contributor]
32 * elects to include this software in this distribution under the [CDDL or GPL
33 * Version 2] license." If you don't indicate a single choice of license, a
34 * recipient has the option to distribute your version of this file under
35 * either the CDDL, the GPL Version 2 or to extend the choice of license to
36 * its licensees as provided above. However, if you add GPL Version 2 code
37 * and therefore, elected the GPL Version 2 license, then the option applies
38 * only if the new code is made subject to such option by the copyright
39 * holder.
40 */
41
42
43 // ViewHandlerImpl.java
44
45 package com.sun.faces.application;
46
47 import com.sun.faces.RIConstants;
48 import com.sun.faces.config.WebConfiguration;
49 import com.sun.faces.config.WebConfiguration.WebContextInitParameter;
50 import com.sun.faces.io.FastStringWriter;
51 import com.sun.faces.util.DebugUtil;
52 import com.sun.faces.util.MessageUtils;
53 import com.sun.faces.util.Util;
54 import com.sun.faces.util.FacesLogger;
55 import com.sun.faces.util.RequestStateManager;
56
57 import javax.faces.FacesException;
58 import javax.faces.FactoryFinder;
59 import javax.faces.application.StateManager;
60 import javax.faces.application.ViewHandler;
61 import javax.faces.component.UIViewRoot;
62 import javax.faces.context.ExternalContext;
63 import javax.faces.context.FacesContext;
64 import javax.faces.context.ResponseWriter;
65 import javax.faces.render.RenderKit;
66 import javax.faces.render.RenderKitFactory;
67 import javax.faces.render.ResponseStateManager;
68 import javax.servlet.ServletRequest;
69 import javax.servlet.ServletResponse;
70 import javax.servlet.http.HttpServletResponse;
71 import javax.servlet.jsp.jstl.core.Config;
72
73
74 import java.io.IOException;
75 import java.io.Writer;
76 import java.util.Iterator;
77 import java.util.Locale;
78 import java.util.Map;
79 import java.util.logging.Level;
80 import java.util.logging.Logger;
81
82 /**
83 * <B>ViewHandlerImpl</B> is the default implementation class for ViewHandler.
84 *
85 * @version $Id: ViewHandlerImpl.java,v 1.109.4.5 2008/06/11 18:03:03 rlubke Exp $
86 * @see javax.faces.application.ViewHandler
87 */
88 public class ViewHandlerImpl extends ViewHandler {
89
90 // Log instance for this class
91 private static final Logger logger = FacesLogger.APPLICATION.getLogger();
92
93 private ApplicationAssociate associate;
94
95 /**
96 * <p>Store the value of <code>DEFAULT_SUFFIX_PARAM_NAME</code>
97 * or, if that isn't defined, the value of <code>DEFAULT_SUFFIX</code>
98 */
99 private String contextDefaultSuffix;
100 private int bufSize = -1;
101
102 public ViewHandlerImpl() {
103 if (logger.isLoggable(Level.FINE)) {
104 logger.log(Level.FINE,"Created ViewHandler instance ");
105 }
106 }
107
108
109 /**
110 * Do not call the default implementation of {@link ViewHandler#initView(javax.faces.context.FacesContext)}
111 * if the {@link javax.faces.context.ExternalContext#getRequestCharacterEncoding()} returns a
112 * <code>non-null</code> result.
113 *
114 * @see ViewHandler#initView(javax.faces.context.FacesContext)
115 */
116 @Override
117 public void initView(FacesContext context) throws FacesException {
118
119 if (context.getExternalContext().getRequestCharacterEncoding() == null) {
120 super.initView(context);
121 }
122
123 }
124
125
126 public void renderView(FacesContext context,
127 UIViewRoot viewToRender) throws IOException,
128 FacesException {
129
130 // suppress rendering if "rendered" property on the component is
131 // false
132 if (!viewToRender.isRendered()) {
133 return;
134 }
135
136 ExternalContext extContext = context.getExternalContext();
137 ServletRequest request = (ServletRequest) extContext.getRequest();
138 ServletResponse response = (ServletResponse) extContext.getResponse();
139
140 try {
141 if (executePageToBuildView(context, viewToRender)) {
142 response.flushBuffer();
143 ApplicationAssociate associate = getAssociate(context);
144 if (associate != null) {
145 associate.responseRendered();
146 }
147 return;
148 }
149 } catch (IOException e) {
150 throw new FacesException(e);
151 }
152
153 if (logger.isLoggable(Level.FINE)) {
154 logger.log(Level.FINE, "Completed building view for : \n" +
155 viewToRender.getViewId());
156 }
157 if (logger.isLoggable(Level.FINEST)) {
158 logger.log(Level.FINEST, "+=+=+=+=+=+= Printout for " + viewToRender.getViewId() + " about to render.");
159 DebugUtil.printTree(viewToRender, logger, Level.FINEST);
160 }
161
162 // set up the ResponseWriter
163
164 RenderKitFactory renderFactory = (RenderKitFactory)
165 FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
166 RenderKit renderKit =
167 renderFactory.getRenderKit(context, viewToRender.getRenderKitId());
168
169 ResponseWriter oldWriter = context.getResponseWriter();
170
171 if (bufSize == -1) {
172 WebConfiguration webConfig =
173 WebConfiguration
174 .getInstance(context.getExternalContext());
175 try {
176 bufSize = Integer
177 .parseInt(webConfig.getOptionValue(
178 WebContextInitParameter.ResponseBufferSize));
179 } catch (NumberFormatException nfe) {
180 bufSize = Integer
181 .parseInt(WebContextInitParameter.ResponseBufferSize.getDefaultValue());
182 }
183 }
184
185
186 WriteBehindStateWriter stateWriter =
187 new WriteBehindStateWriter(response.getWriter(),
188 context,
189 bufSize);
190 ResponseWriter newWriter;
191 if (null != oldWriter) {
192 newWriter = oldWriter.cloneWithWriter(stateWriter);
193 } else {
194 newWriter = renderKit.createResponseWriter(stateWriter,
195 null,
196 request.getCharacterEncoding());
197 }
198 context.setResponseWriter(newWriter);
199
200 newWriter.startDocument();
201
202 doRenderView(context, viewToRender);
203
204 newWriter.endDocument();
205
206 // replace markers in the body content and write it to response.
207
208 // flush directly to the response
209 if (stateWriter.stateWritten()) {
210 stateWriter.flushToWriter();
211 }
212
213 // clear the ThreadLocal reference.
214 stateWriter.release();
215
216 if (null != oldWriter) {
217 context.setResponseWriter(oldWriter);
218 }
219
220 // write any AFTER_VIEW_CONTENT to the response
221 // side effect: AFTER_VIEW_CONTENT removed
222 ViewHandlerResponseWrapper wrapper = (ViewHandlerResponseWrapper)
223 RequestStateManager.remove(context, RequestStateManager.AFTER_VIEW_CONTENT);
224 if (null != wrapper) {
225 wrapper.flushToWriter(response.getWriter(),
226 response.getCharacterEncoding());
227 }
228
229 response.flushBuffer();
230
231 }
232
233 /**
234 * <p>This is a separate method to account for handling the content
235 * after the view tag.</p>
236 *
237 * <p>Create a new ResponseWriter around this response's Writer.
238 * Set it into the FacesContext, saving the old one aside.</p>
239 *
240 * <p>call encodeBegin(), encodeChildren(), encodeEnd() on the
241 * argument <code>UIViewRoot</code>.</p>
242 *
243 * <p>Restore the old ResponseWriter into the FacesContext.</p>
244 *
245 * <p>Write out the after view content to the response's writer.</p>
246 *
247 * <p>Flush the response buffer, and remove the after view content
248 * from the request scope.</p>
249 *
250 * @param context the <code>FacesContext</code> for the current request
251 * @param viewToRender the view to render
252 * @throws IOException if an error occurs rendering the view to the client
253 * @throws FacesException if some error occurs within the framework
254 * processing
255 */
256
257 private void doRenderView(FacesContext context,
258 UIViewRoot viewToRender)
259 throws IOException, FacesException {
260
261 ApplicationAssociate associate = getAssociate(context);
262
263 if (null != associate) {
264 associate.responseRendered();
265 }
266
267 if (logger.isLoggable(Level.FINE)) {
268 logger.log(Level.FINE, "About to render view " + viewToRender.getViewId());
269 }
270
271 viewToRender.encodeAll(context);
272 }
273
274
275 public UIViewRoot restoreView(FacesContext context, String viewId) {
276 if (context == null) {
277 String message = MessageUtils.getExceptionMessageString
278 (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
279 throw new NullPointerException(message);
280 }
281
282 ExternalContext extContext = context.getExternalContext();
283
284 String mapping = Util.getFacesMapping(context);
285 UIViewRoot viewRoot = null;
286
287 if (mapping != null) {
288 if (!Util.isPrefixMapped(mapping)) {
289 viewId = convertViewId(context, viewId);
290 } else {
291 viewId = normalizeRequestURI(viewId, mapping);
292 }
293 }
294
295 // maping could be null if a non-faces request triggered
296 // this response.
297 if (extContext.getRequestPathInfo() == null && mapping != null &&
298 Util.isPrefixMapped(mapping)) {
299 // this was probably an initial request
300 // send them off to the root of the web application
301 try {
302 context.responseComplete();
303 if (logger.isLoggable(Level.FINE)) {
304 logger.log(Level.FINE, "Response Complete for" + viewId);
305 }
306 extContext.redirect(extContext.getRequestContextPath());
307 } catch (IOException ioe) {
308 throw new FacesException(ioe);
309 }
310 } else {
311 // this is necessary to allow decorated impls.
312 ViewHandler outerViewHandler =
313 context.getApplication().getViewHandler();
314 String renderKitId =
315 outerViewHandler.calculateRenderKitId(context);
316 viewRoot = Util.getStateManager(context).restoreView(context,
317 viewId,
318 renderKitId);
319 }
320
321 return viewRoot;
322 }
323
324
325 public UIViewRoot createView(FacesContext context, String viewId) {
326 if (context == null) {
327 String message = MessageUtils.getExceptionMessageString
328 (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
329 throw new NullPointerException(message);
330 }
331
332 UIViewRoot result = (UIViewRoot)
333 context.getApplication().createComponent(UIViewRoot.COMPONENT_TYPE);
334
335 if (viewId != null) {
336 String mapping = Util.getFacesMapping(context);
337
338 if (mapping != null) {
339 if (!Util.isPrefixMapped(mapping)) {
340 viewId = convertViewId(context, viewId);
341 } else {
342 viewId = normalizeRequestURI(viewId, mapping);
343 if (viewId.equals(mapping)) {
344 // The request was to the FacesServlet only - no
345 // path info
346 // on some containers this causes a recursion in the
347 // RequestDispatcher and the request appears to hang.
348 // If this is detected, return status 404
349 send404Error(context);
350 }
351 }
352 }
353
354 result.setViewId(viewId);
355 }
356
357 Locale locale = null;
358 String renderKitId = null;
359
360 // use the locale from the previous view if is was one which will be
361 // the case if this is called from NavigationHandler. There wouldn't be
362 // one for the initial case.
363 if (context.getViewRoot() != null) {
364 locale = context.getViewRoot().getLocale();
365 renderKitId = context.getViewRoot().getRenderKitId();
366 }
367
368 if (logger.isLoggable(Level.FINE)) {
369 logger.log(Level.FINE, "Created new view for " + viewId);
370 }
371 // PENDING(): not sure if we should set the RenderKitId here.
372 // The UIViewRoot ctor sets the renderKitId to the default
373 // one.
374 // if there was no locale from the previous view, calculate the locale
375 // for this view.
376 if (locale == null) {
377 locale =
378 context.getApplication().getViewHandler().calculateLocale(
379 context);
380 if (logger.isLoggable(Level.FINE)) {
381 logger.fine("Locale for this view as determined by calculateLocale "
382 + locale.toString());
383 }
384 } else {
385 if (logger.isLoggable(Level.FINE)) {
386 logger.fine("Using locale from previous view "
387 + locale.toString());
388 }
389 }
390
391 if (renderKitId == null) {
392 renderKitId =
393 context.getApplication().getViewHandler().calculateRenderKitId(
394 context);
395 if (logger.isLoggable(Level.FINE)) {
396 logger.fine(
397 "RenderKitId for this view as determined by calculateRenderKitId "
398 + renderKitId);
399 }
400 } else {
401 if (logger.isLoggable(Level.FINE)) {
402 logger.fine("Using renderKitId from previous view "
403 + renderKitId);
404 }
405 }
406
407 result.setLocale(locale);
408 result.setRenderKitId(renderKitId);
409
410 return result;
411 }
412
413 /**
414 * Execute the target view. If the HTTP status code range is
415 * not 2xx, then return true to indicate the response should be
416 * immediately flushed by the caller so that conditions such as 404
417 * are properly handled.
418 * @param context the <code>FacesContext</code> for the current request
419 * @param viewToExecute the view to build
420 * @return <code>true</code> if the response should be immediately flushed
421 * to the client, otherwise <code>false</code>
422 * @throws IOException if an error occurs executing the page
423 */
424 private boolean executePageToBuildView(FacesContext context,
425 UIViewRoot viewToExecute)
426 throws IOException {
427
428 if (null == context) {
429 String message = MessageUtils.getExceptionMessageString
430 (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
431 throw new NullPointerException(message);
432 }
433 if (null == viewToExecute) {
434 String message = MessageUtils.getExceptionMessageString
435 (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "viewToExecute");
436 throw new NullPointerException(message);
437 }
438
439 ExternalContext extContext = context.getExternalContext();
440
441 if ("/*".equals(RequestStateManager.get(context, RequestStateManager.INVOCATION_PATH))) {
442 throw new FacesException(MessageUtils.getExceptionMessageString(
443 MessageUtils.FACES_SERVLET_MAPPING_INCORRECT_ID));
444 }
445
446 String requestURI = viewToExecute.getViewId();
447
448 if (logger.isLoggable(Level.FINE)) {
449 logger.fine("About to execute view " + requestURI);
450 }
451
452 // update the JSTL locale attribute in request scope so that JSTL
453 // picks up the locale from viewRoot. This attribute must be updated
454 // before the JSTL setBundle tag is called because that is when the
455 // new LocalizationContext object is created based on the locale.
456 if (extContext.getRequest() instanceof ServletRequest) {
457 Config.set((ServletRequest)
458 extContext.getRequest(),
459 Config.FMT_LOCALE, context.getViewRoot().getLocale());
460 }
461 if (logger.isLoggable(Level.FINE)) {
462 logger.fine("Before dispacthMessage to viewId " + requestURI);
463 }
464
465 // save the original response
466 Object originalResponse = extContext.getResponse();
467
468 // replace the response with our wrapper
469 ViewHandlerResponseWrapper wrapped = getWrapper(extContext);
470 extContext.setResponse(wrapped);
471
472 // build the view by executing the page
473 extContext.dispatch(requestURI);
474
475 if (logger.isLoggable(Level.FINE)) {
476 logger.fine("After dispacthMessage to viewId " + requestURI);
477 }
478
479 // replace the original response
480 extContext.setResponse(originalResponse);
481
482 // Follow the JSTL 1.2 spec, section 7.4,
483 // on handling status codes on a forward
484 if (wrapped.getStatus() < 200 || wrapped.getStatus() > 299) {
485 // flush the contents of the wrapper to the response
486 // this is necessary as the user may be using a custom
487 // error page - this content should be propagated
488 wrapped.flushContentToWrappedResponse();
489 return true;
490 }
491
492 // Put the AFTER_VIEW_CONTENT into request scope
493 // temporarily
494 RequestStateManager.set(context,
495 RequestStateManager.AFTER_VIEW_CONTENT,
496 wrapped);
497
498 return false;
499
500 }
501
502
503 public Locale calculateLocale(FacesContext context) {
504
505 if (context == null) {
506 String message = MessageUtils.getExceptionMessageString
507 (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
508 throw new NullPointerException(message);
509 }
510
511 Locale result = null;
512 // determine the locales that are acceptable to the client based on the
513 // Accept-Language header and the find the best match among the
514 // supported locales specified by the client.
515 Iterator<Locale> locales = context.getExternalContext().getRequestLocales();
516 while (locales.hasNext()) {
517 Locale perf = locales.next();
518 result = findMatch(context, perf);
519 if (result != null) {
520 break;
521 }
522 }
523 // no match is found.
524 if (result == null) {
525 if (context.getApplication().getDefaultLocale() == null) {
526 result = Locale.getDefault();
527 } else {
528 result = context.getApplication().getDefaultLocale();
529 }
530 }
531 return result;
532 }
533
534
535 public String calculateRenderKitId(FacesContext context) {
536
537 if (context == null) {
538 String message = MessageUtils.getExceptionMessageString
539 (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
540 throw new NullPointerException(message);
541 }
542
543 Map<String,String> requestParamMap = context.getExternalContext()
544 .getRequestParameterMap();
545 String result = requestParamMap.get(
546 ResponseStateManager.RENDER_KIT_ID_PARAM);
547
548 if (result == null) {
549 if (null ==
550 (result = context.getApplication().getDefaultRenderKitId())) {
551 result = RenderKitFactory.HTML_BASIC_RENDER_KIT;
552 }
553 }
554 return result;
555 }
556
557
558 /**
559 * Attempts to find a matching locale based on <code>pref</code> and
560 * list of supported locales, using the matching algorithm
561 * as described in JSTL 8.3.2.
562 * @param context the <code>FacesContext</code> for the current request
563 * @param pref the preferred locale
564 * @return the Locale based on pref and the matching alogritm specified
565 * in JSTL 8.3.2
566 */
567 protected Locale findMatch(FacesContext context, Locale pref) {
568 Locale result = null;
569 Iterator<Locale> it = context.getApplication().getSupportedLocales();
570 while (it.hasNext()) {
571 Locale supportedLocale = it.next();
572
573 if (pref.equals(supportedLocale)) {
574 // exact match
575 result = supportedLocale;
576 break;
577 } else {
578 // Make sure the preferred locale doesn't have country
579 // set, when doing a language match, For ex., if the
580 // preferred locale is "en-US", if one of supported
581 // locales is "en-UK", even though its language matches
582 // that of the preferred locale, we must ignore it.
583 if (pref.getLanguage().equals(supportedLocale.getLanguage()) &&
584 supportedLocale.getCountry().length() == 0) {
585 result = supportedLocale;
586 }
587 }
588 }
589 // if it's not in the supported locales,
590 if (null == result) {
591 Locale defaultLocale = context.getApplication().getDefaultLocale();
592 if (defaultLocale != null) {
593 if ( pref.equals(defaultLocale)) {
594 // exact match
595 result = defaultLocale;
596 } else {
597 // Make sure the preferred locale doesn't have country
598 // set, when doing a language match, For ex., if the
599 // preferred locale is "en-US", if one of supported
600 // locales is "en-UK", even though its language matches
601 // that of the preferred locale, we must ignore it.
602 if (pref.getLanguage().equals(defaultLocale.getLanguage()) &&
603 defaultLocale.getCountry().length() == 0) {
604 result = defaultLocale;
605 }
606 }
607 }
608 }
609
610 return result;
611 }
612
613
614 public void writeState(FacesContext context) throws IOException {
615 if (context == null) {
616 String message = MessageUtils.getExceptionMessageString
617 (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
618 throw new NullPointerException(message);
619 }
620
621 if (logger.isLoggable(Level.FINE)) {
622 logger.fine("Begin writing marker for viewId " +
623 context.getViewRoot().getViewId());
624 }
625
626 WriteBehindStateWriter writer = WriteBehindStateWriter.getCurrentInstance();
627 if (writer != null) {
628 writer.writingState();
629 }
630 context.getResponseWriter().write(RIConstants.SAVESTATE_FIELD_MARKER);
631 if (logger.isLoggable(Level.FINE)) {
632 logger.fine("End writing marker for viewId " +
633 context.getViewRoot().getViewId());
634 }
635
636 }
637
638
639 public String getActionURL(FacesContext context, String viewId) {
640
641 if (context == null) {
642 String message = MessageUtils.getExceptionMessageString
643 (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
644 throw new NullPointerException(message);
645 }
646 if (viewId == null) {
647 String message = MessageUtils.getExceptionMessageString
648 (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "viewId");
649 throw new NullPointerException(message);
650 }
651
652 if (viewId.charAt(0) != '/') {
653 String message =
654 MessageUtils.getExceptionMessageString(
655 MessageUtils.ILLEGAL_VIEW_ID_ID,
656 viewId);
657 if (logger.isLoggable(Level.SEVERE)) {
658 logger.log(Level.SEVERE, "jsf.illegal_view_id_error", viewId);
659 }
660 throw new IllegalArgumentException(message);
661 }
662
663 // Acquire the context path, which we will prefix on all results
664 ExternalContext extContext = context.getExternalContext();
665 String contextPath = extContext.getRequestContextPath();
666
667 // Acquire the mapping used to execute this request (if any)
668 String mapping = Util.getFacesMapping(context);
669
670 // If no mapping can be identified, just return a server-relative path
671 if (mapping == null) {
672 return (contextPath + viewId);
673 }
674
675 // Deal with prefix mapping
676 if (Util.isPrefixMapped(mapping)) {
677 if (mapping.equals("/*")) {
678 return (contextPath + viewId);
679 } else {
680 return (contextPath + mapping + viewId);
681 }
682 }
683
684 // Deal with extension mapping
685 int period = viewId.lastIndexOf('.');
686 if (period < 0) {
687 return (contextPath + viewId + mapping);
688 } else if (!viewId.endsWith(mapping)) {
689 return (contextPath + viewId.substring(0, period) + mapping);
690 } else {
691 return (contextPath + viewId);
692 }
693
694 }
695
696
697 public String getResourceURL(FacesContext context, String path) {
698 ExternalContext extContext = context.getExternalContext();
699 if (path.startsWith("/")) {
700 return (extContext.getRequestContextPath() + path);
701 } else {
702 return path;
703 }
704
705 }
706
707
708 /**
709 * <p>if the specified mapping is a prefix mapping, and the provided
710 * request URI (usually the value from <code>ExternalContext.getRequestServletPath()</code>)
711 * starts with <code>mapping + '/'</code>, prune the mapping from the
712 * URI and return it, otherwise, return the original URI.
713 * @param uri the servlet request path
714 * @param mapping the FacesServlet mapping used for this request
715 * @return the URI without additional FacesServlet mappings
716 * @since 1.2
717 */
718 private String normalizeRequestURI(String uri, String mapping) {
719
720 if (mapping == null || !Util.isPrefixMapped(mapping)) {
721 return uri;
722 } else {
723 int length = mapping.length() + 1;
724 StringBuilder builder = new StringBuilder(length);
725 builder.append(mapping).append('/');
726 String mappingMod = builder.toString();
727 boolean logged = false;
728 while (uri.startsWith(mappingMod)) {
729 if (!logged && logger.isLoggable(Level.WARNING)) {
730 logged = true;
731 logger.log(Level.WARNING,
732 "jsf.viewhandler.requestpath.recursion",
733 new Object[] {uri, mapping});
734 }
735 uri = uri.substring(length - 1);
736 }
737 return uri;
738 }
739 }
740
741 private void send404Error(FacesContext context) {
742 HttpServletResponse response = (HttpServletResponse)
743 context.getExternalContext().getResponse();
744 try {
745 context.responseComplete();
746 response.sendError(HttpServletResponse.SC_NOT_FOUND);
747 } catch (IOException ioe) {
748 throw new FacesException(ioe);
749 }
750 }
751
752
753 /**
754 * <p>Adjust the viewID per the requirements of {@link #renderView}.</p>
755 *
756 * @param context current {@link FacesContext}
757 * @param viewId incoming view ID
758 * @return the view ID with an altered suffix mapping (if necessary)
759 */
760 private String convertViewId(FacesContext context, String viewId) {
761
762 if (contextDefaultSuffix == null) {
763 contextDefaultSuffix =
764 WebConfiguration
765 .getInstance(context.getExternalContext())
766 .getOptionValue(WebContextInitParameter.JspDefaultSuffix);
767 if (contextDefaultSuffix == null) {
768 contextDefaultSuffix = ViewHandler.DEFAULT_SUFFIX;
769 }
770 if (logger.isLoggable(Level.FINE)) {
771 logger.fine("contextDefaultSuffix "
772 + contextDefaultSuffix);
773 }
774 }
775
776 String convertedViewId = viewId;
777 // if the viewId doesn't already use the above suffix,
778 // replace or append.
779 if (!convertedViewId.endsWith(contextDefaultSuffix)) {
780 StringBuilder buffer = new StringBuilder(convertedViewId);
781 int extIdx = convertedViewId.lastIndexOf('.');
782 if (extIdx != -1) {
783 buffer.replace(extIdx, convertedViewId.length(),
784 contextDefaultSuffix);
785 } else {
786 // no extension in the provided viewId, append the suffix
787 buffer.append(contextDefaultSuffix);
788 }
789 convertedViewId = buffer.toString();
790 if (logger.isLoggable(Level.FINE)) {
791 logger.fine( "viewId after appending the context suffix " +
792 convertedViewId);
793 }
794
795 }
796 return convertedViewId;
797 }
798
799
800 private ApplicationAssociate getAssociate(FacesContext context) {
801 if (associate == null) {
802 associate = ApplicationAssociate.getInstance(context.getExternalContext());
803 }
804 return associate;
805 }
806
807
808 private static ViewHandlerResponseWrapper getWrapper(ExternalContext extContext) {
809 Object response = extContext.getResponse();
810 if (response instanceof HttpServletResponse) {
811 return new ViewHandlerResponseWrapper((HttpServletResponse) response);
812 }
813 throw new IllegalArgumentException();
814
815 }
816
817 // ----------------------------------------------------------- Inner Classes
818
819 /**
820 * Thanks to the Facelets folks for some of the concepts incorporated
821 * into this class.
822 */
823 private static final class WriteBehindStateWriter extends Writer {
824 // length of the state marker
825 private static final int STATE_MARKER_LEN =
826 RIConstants.SAVESTATE_FIELD_MARKER.length();
827
828 private static final ThreadLocal<WriteBehindStateWriter> CUR_WRITER =
829 new ThreadLocal<WriteBehindStateWriter>();
830 private Writer out;
831 private Writer orig;
832 private FastStringWriter fWriter;
833 private boolean stateWritten;
834 private int bufSize;
835 private char[] buf;
836 private FacesContext context;
837
838
839 // -------------------------------------------------------- Constructors
840
841
842 public WriteBehindStateWriter(Writer out, FacesContext context, int bufSize) {
843 this.out = out;
844 this.orig = out;
845 this.context = context;
846 this.bufSize = bufSize;
847 this.buf = new char[bufSize];
848 CUR_WRITER.set(this);
849 }
850
851
852 // ------------------------------------------------- Methods from Writer
853
854
855
856 public void write(int c) throws IOException {
857 out.write(c);
858 }
859
860
861 public void write(char cbuf[]) throws IOException {
862 out.write(cbuf);
863 }
864
865
866 public void write(String str) throws IOException {
867 out.write(str);
868 }
869
870
871 public void write(String str, int off, int len) throws IOException {
872 out.write(str, off, len);
873 }
874
875
876 public void write(char cbuf[], int off, int len) throws IOException {
877 out.write(cbuf, off, len);
878 }
879
880
881 public void flush() throws IOException {
882 // no-op
883 }
884
885
886 public void close() throws IOException {
887 // no-op
888 }
889
890
891 // ------------------------------------------------------ Public Methods
892
893
894 public static WriteBehindStateWriter getCurrentInstance() {
895 return CUR_WRITER.get();
896 }
897
898
899 public void release() {
900 CUR_WRITER.remove();
901 }
902
903
904 public void writingState() {
905 if (!stateWritten) {
906 this.stateWritten = true;
907 out = fWriter = new FastStringWriter(1024);
908 }
909 }
910
911 public boolean stateWritten() {
912 return stateWritten;
913 }
914
915 /**
916 * <p> Write directly from our FastStringWriter to the provided
917 * writer.</p>
918 * @throws IOException if an error occurs
919 */
920 public void flushToWriter() throws IOException {
921 // Save the state to a new instance of StringWriter to
922 // avoid multiple serialization steps if the view contains
923 // multiple forms.
924 StateManager stateManager = Util.getStateManager(context);
925 ResponseWriter origWriter = context.getResponseWriter();
926 FastStringWriter state =
927 new FastStringWriter((stateManager.isSavingStateInClient(
928 context)) ? bufSize : 128);
929 context.setResponseWriter(origWriter.cloneWithWriter(state));
930 stateManager.writeState(context, stateManager.saveView(context));
931 context.setResponseWriter(origWriter);
932 StringBuilder builder = fWriter.getBuffer();
933 // begin writing...
934 int totalLen = builder.length();
935 StringBuilder stateBuilder = state.getBuffer();
936 int stateLen = stateBuilder.length();
937 int pos = 0;
938 int tildeIdx = getNextDelimiterIndex(builder, pos);
939 while (pos < totalLen) {
940 if (tildeIdx != -1) {
941 if (tildeIdx > pos && (tildeIdx - pos) > bufSize) {
942 // there's enough content before the first ~
943 // to fill the entire buffer
944 builder.getChars(pos, (pos + bufSize), buf, 0);
945 orig.write(buf);
946 pos += bufSize;
947 } else {
948 // write all content up to the first '~'
949 builder.getChars(pos, tildeIdx, buf, 0);
950 int len = (tildeIdx - pos);
951 orig.write(buf, 0, len);
952 // now check to see if the state saving string is
953 // at the begining of pos, if so, write our
954 // state out.
955 if (builder.indexOf(
956 RIConstants.SAVESTATE_FIELD_MARKER,
957 pos) == tildeIdx) {
958 // buf is effectively zero'd out at this point
959 int statePos = 0;
960 while (statePos < stateLen) {
961 if ((stateLen - statePos) > bufSize) {
962 // enough state to fill the buffer
963 stateBuilder.getChars(statePos,
964 (statePos + bufSize),
965 buf,
966 0);
967 orig.write(buf);
968 statePos += bufSize;
969 } else {
970 int slen = (stateLen - statePos);
971 stateBuilder.getChars(statePos,
972 stateLen,
973 buf,
974 0);
975 orig.write(buf, 0, slen);
976 statePos += slen;
977 }
978
979 }
980 // push us past the last '~' at the end of the marker
981 pos += (len + STATE_MARKER_LEN);
982 tildeIdx = getNextDelimiterIndex(builder, pos);
983 } else {
984 pos = tildeIdx;
985 tildeIdx = getNextDelimiterIndex(builder,
986 tildeIdx + 1);
987
988 }
989 }
990 } else {
991 // we've written all of the state field markers.
992 // finish writing content
993 if (totalLen - pos > bufSize) {
994 // there's enough content to fill the buffer
995 builder.getChars(pos, (pos + bufSize), buf, 0);
996 orig.write(buf);
997 pos += bufSize;
998 } else {
999 // we're near the end of the response
1000 builder.getChars(pos, totalLen, buf, 0);
1001 int len = (totalLen - pos);
1002 orig.write(buf, 0, len);
1003 pos += (len + 1);
1004 }
1005 }
1006 }
1007
1008 // all state has been written. Have 'out' point to the
1009 // response so that all subsequent writes will make it to the
1010 // browser.
1011 out = orig;
1012 }
1013
1014 private static int getNextDelimiterIndex(StringBuilder builder,
1015 int offset) {
1016 return builder.indexOf(RIConstants.SAVESTATE_FIELD_DELIMITER,
1017 offset);
1018 }
1019
1020 }
1021
1022
1023 }