View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.context.servlet;
20  
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.io.UnsupportedEncodingException;
24  import java.io.Writer;
25  import java.net.MalformedURLException;
26  import java.net.URL;
27  import java.net.URLDecoder;
28  import java.net.URLEncoder;
29  import java.security.Principal;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.Locale;
37  import java.util.Map;
38  import java.util.Set;
39  import java.util.logging.Logger;
40  
41  import javax.faces.FacesException;
42  import javax.faces.FactoryFinder;
43  import javax.faces.context.FacesContext;
44  import javax.faces.context.Flash;
45  import javax.faces.context.FlashFactory;
46  import javax.faces.context.PartialResponseWriter;
47  import javax.faces.context.PartialViewContext;
48  import javax.faces.lifecycle.ClientWindow;
49  import javax.faces.render.ResponseStateManager;
50  import javax.servlet.RequestDispatcher;
51  import javax.servlet.ServletContext;
52  import javax.servlet.ServletException;
53  import javax.servlet.ServletRequest;
54  import javax.servlet.ServletResponse;
55  import javax.servlet.http.Cookie;
56  import javax.servlet.http.HttpServletRequest;
57  import javax.servlet.http.HttpServletResponse;
58  import javax.servlet.http.HttpSession;
59  
60  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
61  import org.apache.myfaces.shared.context.flash.FlashImpl;
62  import org.apache.myfaces.shared.util.WebConfigParamUtils;
63  import org.apache.myfaces.util.EnumerationIterator;
64  import org.apache.myfaces.util.ExternalSpecifications;
65  
66  /**
67   * Implements the external context for servlet request. JSF 1.2, 6.1.3
68   *
69   * @author Manfred Geiler (latest modification by $Author$)
70   * @author Anton Koinov
71   * @version $Revision$ $Date$
72   */
73  public final class ServletExternalContextImpl extends ServletExternalContextImplBase
74  {
75      private static final Logger log = Logger.getLogger(ServletExternalContextImpl.class.getName());
76  
77      private static final String URL_PARAM_SEPERATOR="&";
78      private static final char URL_QUERY_SEPERATOR='?';
79      private static final char URL_FRAGMENT_SEPERATOR='#';
80      private static final String URL_NAME_VALUE_PAIR_SEPERATOR="=";
81      private static final String PUSHED_RESOURCE_URLS = "oam.PUSHED_RESOURCE_URLS";
82      private static final String PUSH_SUPPORTED = "oam.PUSH_SUPPORTED";
83  
84      /**
85       * Indicates the port used for websocket connections.
86       */
87      @JSFWebConfigParam(since = "2.3")
88      public static final java.lang.String WEBSOCKET_ENDPOINT_PORT = "javax.faces.WEBSOCKET_ENDPOINT_PORT";
89  
90      private ServletRequest _servletRequest;
91      private ServletResponse _servletResponse;
92      private Map<String, Object> _sessionMap;
93      private Map<String, Object> _requestMap;
94      private Map<String, String> _requestParameterMap;
95      private Map<String, String[]> _requestParameterValuesMap;
96      private Map<String, String> _requestHeaderMap;
97      private Map<String, String[]> _requestHeaderValuesMap;
98      private Map<String, Object> _requestCookieMap;
99      private HttpServletRequest _httpServletRequest;
100     private HttpServletResponse _httpServletResponse;
101     private String _requestServletPath;
102     private String _requestPathInfo;
103     private FlashFactory _flashFactory;
104     private Flash _flash;
105     private FacesContext _currentFacesContext;
106 
107     public ServletExternalContextImpl(final ServletContext servletContext, 
108             final ServletRequest servletRequest,
109             final ServletResponse servletResponse)
110     {
111         super(servletContext); // initialize ServletExternalContextImplBase
112         
113         _servletRequest = servletRequest;
114         _servletResponse = servletResponse;
115         _sessionMap = null;
116         _requestMap = null;
117         _requestParameterMap = null;
118         _requestParameterValuesMap = null;
119         _requestHeaderMap = null;
120         _requestHeaderValuesMap = null;
121         _requestCookieMap = null;
122         _httpServletRequest = isHttpServletRequest(servletRequest) ? (HttpServletRequest) servletRequest : null;
123         _httpServletResponse = isHttpServletResponse(servletResponse) ? (HttpServletResponse) servletResponse : null;
124 
125         if (_httpServletRequest != null)
126         {
127             // HACK: MultipartWrapper scrambles the servletPath for some reason in Tomcat 4.1.29 embedded in JBoss
128             // 3.2.3!?
129             // (this was reported by frederic.auge [frederic.auge@laposte.net])
130             _requestServletPath = _httpServletRequest.getServletPath();
131             _requestPathInfo = _httpServletRequest.getPathInfo();
132         }
133     }
134     
135     public ServletExternalContextImpl(final ServletContext servletContext, 
136             final ServletRequest servletRequest,
137             final ServletResponse servletResponse,
138             final FlashFactory flashFactory)
139     {
140         this(servletContext, servletRequest, servletResponse);
141         _flashFactory = flashFactory;
142     }
143 
144     public void release()
145     {
146         super.release(); // releases fields on ServletExternalContextImplBase
147         
148         _currentFacesContext = null;
149         _servletRequest = null;
150         _servletResponse = null;
151         _sessionMap = null;
152         _requestMap = null;
153         _requestParameterMap = null;
154         _requestParameterValuesMap = null;
155         _requestHeaderMap = null;
156         _requestHeaderValuesMap = null;
157         _requestCookieMap = null;
158         _httpServletRequest = null;
159         _httpServletResponse = null;
160     }
161 
162     @Override
163     public Object getSession(boolean create)
164     {
165         checkHttpServletRequest();
166         return ((HttpServletRequest) _servletRequest).getSession(create);
167     }
168     
169     @Override
170     public String getSessionId(boolean create)
171     {
172         checkHttpServletRequest();
173         HttpSession session = ((HttpServletRequest) _servletRequest).getSession(create);
174         if (session != null)
175         {
176             return session.getId();
177         }
178         else
179         {
180             return "";
181         }
182     }
183     
184 
185     @Override
186     public Object getRequest()
187     {
188         return _servletRequest;
189     }
190 
191     /**
192      * @since 2.0
193      */
194     @Override
195     public int getRequestContentLength()
196     {
197         return _servletRequest.getContentLength();
198     }
199 
200     @Override
201     public Object getResponse()
202     {
203         return _servletResponse;
204     }
205 
206     /**
207      * @since 2.0
208      */
209     @Override
210     public int getResponseBufferSize()
211     {
212         return _servletResponse.getBufferSize();
213     }
214 
215     @Override
216     public String getResponseContentType()
217     {
218         return _servletResponse.getContentType();
219     }
220 
221     @Override
222     public OutputStream getResponseOutputStream() throws IOException
223     {
224         return _servletResponse.getOutputStream();
225     }
226 
227     /**
228      * @since JSF 2.0
229      */
230     @Override
231     public Writer getResponseOutputWriter() throws IOException
232     {
233         return _servletResponse.getWriter();
234     }
235 
236     @Override
237     public Map<String, Object> getSessionMap()
238     {
239         if (_sessionMap == null)
240         {
241             checkHttpServletRequest();
242             _sessionMap = new SessionMap(_httpServletRequest);
243         }
244         return _sessionMap;
245     }
246 
247     @Override
248     public Map<String, Object> getRequestMap()
249     {
250         if (_requestMap == null)
251         {
252             _requestMap = new RequestMap(_servletRequest);
253         }
254         return _requestMap;
255     }
256 
257     @Override
258     public Map<String, String> getRequestParameterMap()
259     {
260         if (_requestParameterMap == null)
261         {
262             _requestParameterMap = new RequestParameterMap(_servletRequest);
263         }
264         return _requestParameterMap;
265     }
266 
267     @Override
268     public Map<String, String[]> getRequestParameterValuesMap()
269     {
270         if (_requestParameterValuesMap == null)
271         {
272             _requestParameterValuesMap = new RequestParameterValuesMap(_servletRequest);
273         }
274         return _requestParameterValuesMap;
275     }
276 
277     @Override
278     public int getRequestServerPort()
279     {
280         return _servletRequest.getServerPort();
281     }
282 
283     @Override
284     @SuppressWarnings("unchecked")
285     public Iterator<String> getRequestParameterNames()
286     {
287         return new EnumerationIterator(_servletRequest.getParameterNames());
288     }
289 
290     @Override
291     public Map<String, String> getRequestHeaderMap()
292     {
293         if (_requestHeaderMap == null)
294         {
295             checkHttpServletRequest();
296             _requestHeaderMap = new RequestHeaderMap(_httpServletRequest);
297         }
298         return _requestHeaderMap;
299     }
300 
301     @Override
302     public Map<String, String[]> getRequestHeaderValuesMap()
303     {
304         if (_requestHeaderValuesMap == null)
305         {
306             checkHttpServletRequest();
307             _requestHeaderValuesMap = new RequestHeaderValuesMap(_httpServletRequest);
308         }
309         return _requestHeaderValuesMap;
310     }
311 
312     // FIXME: See with the EG if we can get the return value changed to Map<String, Cookie> as it
313     //        would be more elegant -= Simon Lessard =-
314     @Override
315     public Map<String, Object> getRequestCookieMap()
316     {
317         if (_requestCookieMap == null)
318         {
319             checkHttpServletRequest();
320             _requestCookieMap = new CookieMap(_httpServletRequest);
321         }
322 
323         return _requestCookieMap;
324     }
325 
326     @Override
327     public Locale getRequestLocale()
328     {
329         return _servletRequest.getLocale();
330     }
331 
332     @Override
333     public String getRequestPathInfo()
334     {
335         checkHttpServletRequest();
336         // return (_httpServletRequest).getPathInfo();
337         // HACK: see constructor
338         return _requestPathInfo;
339     }
340 
341     @Override
342     public String getRequestContentType()
343     {
344         return _servletRequest.getContentType();
345     }
346 
347     @Override
348     public String getRequestContextPath()
349     {
350         checkHttpServletRequest();
351         return _httpServletRequest.getContextPath();
352     }
353 
354     @Override
355     public String getRequestScheme()
356     {
357         return _servletRequest.getScheme();
358     }
359 
360     @Override
361     public String encodeActionURL(final String url)
362     {
363         checkNull(url, "url");
364         checkHttpServletRequest();
365         String encodedUrl = ((HttpServletResponse) _servletResponse).encodeURL(url);
366         encodedUrl = encodeURL(encodedUrl, null);
367         return encodedUrl;
368     }
369 
370     @Override
371     public String encodeBookmarkableURL(String baseUrl, Map<String,List<String>> parameters)
372     {
373         return encodeURL(baseUrl, parameters);
374     }
375 
376     @Override
377     public String encodeResourceURL(final String url)
378     {
379         checkNull(url, "url");
380         checkHttpServletRequest();
381         String encodedUrl = ((HttpServletResponse) _servletResponse).encodeURL(url);
382         
383         pushResource(encodedUrl);
384         
385         return encodedUrl;
386     }
387 
388     protected void pushResource(String resourceUrl)
389     {
390         if (!ExternalSpecifications.isServlet4Available())
391         {
392             return;
393         }
394         
395         FacesContext facesContext = FacesContext.getCurrentInstance();
396         Map<Object, Object> attributes = facesContext.getAttributes();
397 
398         javax.servlet.http.PushBuilder pushBuilder = null;
399         
400         if (!attributes.containsKey(PUSH_SUPPORTED))
401         {
402             Object request = getRequest();
403             boolean pushSupported = false;
404             if (request instanceof HttpServletRequest)
405             {
406                 HttpServletRequest servletRequest = (HttpServletRequest) request;
407                 pushBuilder = servletRequest.newPushBuilder();
408                 pushSupported = pushBuilder != null;
409             }
410             
411             attributes.put(PUSH_SUPPORTED, pushSupported);
412         }
413 
414         boolean pushSupported = (boolean) attributes.get(PUSH_SUPPORTED);
415         if (!pushSupported)
416         {
417             return;
418         }
419         
420         Set<String> pushedResourceUrls = (Set<String>) facesContext.getAttributes()
421                  .computeIfAbsent(PUSHED_RESOURCE_URLS, a -> new HashSet<>());
422         if (pushedResourceUrls.contains(resourceUrl))
423         {
424             return;
425         }
426 
427         // might be != null on the first hit
428         if (pushBuilder == null)
429         {
430             HttpServletRequest servletRequest = (HttpServletRequest) getRequest();
431             pushBuilder = servletRequest.newPushBuilder();
432         }
433         
434         if (pushBuilder != null)
435         {
436             pushBuilder.path(resourceUrl).push();
437         }
438     }
439     
440     @Override
441     public String encodeNamespace(final String s)
442     {
443         return s;
444     }
445 
446     @Override
447     public String encodePartialActionURL(String url)
448     {
449         checkNull(url, "url");
450         checkHttpServletRequest();
451         return encodeURL(((HttpServletResponse) _servletResponse).encodeURL(url), null);
452     }
453 
454     @Override
455     public String encodeRedirectURL(String baseUrl, Map<String,List<String>> parameters)
456     {
457         return _httpServletResponse.encodeRedirectURL(encodeURL(baseUrl, parameters));
458     }
459 
460     @Override
461     public String encodeWebsocketURL(String url)
462     {
463 
464         checkNull(url, "url");
465         FacesContext facesContext = getCurrentFacesContext();
466         Integer port = WebConfigParamUtils.getIntegerInitParameter(
467                 getCurrentFacesContext().getExternalContext(), WEBSOCKET_ENDPOINT_PORT);
468         port = (port == 0) ? null : port;
469         if (port != null && 
470             !port.equals(facesContext.getExternalContext().getRequestServerPort()))
471         {
472             String scheme = facesContext.getExternalContext().getRequestScheme();
473             String serverName = facesContext.getExternalContext().getRequestServerName();
474             String webSocketUrl;
475             try
476             {
477                 webSocketUrl = new URL(scheme, serverName, port, url).toExternalForm();
478                 webSocketUrl = webSocketUrl.replaceFirst("http", "ws");
479                 return ((HttpServletResponse) _servletResponse).encodeURL(webSocketUrl);
480             }
481             catch (MalformedURLException ex)
482             {
483                 //If cannot build the url, return the base one unchanged
484                 return url;
485             }
486         }
487         else
488         {
489             return url;
490         }
491     }
492 
493     @Override
494     public void dispatch(final String requestURI) throws IOException, FacesException
495     {
496         RequestDispatcher requestDispatcher = _servletRequest.getRequestDispatcher(requestURI);
497 
498         // If there is no dispatcher, send NOT_FOUND
499         if (requestDispatcher == null)
500         {
501             ((HttpServletResponse) _servletResponse).sendError(HttpServletResponse.SC_NOT_FOUND);
502 
503             return;
504         }
505 
506         try
507         {
508             requestDispatcher.forward(_servletRequest, _servletResponse);
509         }
510         catch (ServletException e)
511         {
512             if (e.getMessage() != null)
513             {
514                 throw new FacesException(e.getMessage(), e);
515             }
516 
517             throw new FacesException(e);
518 
519         }
520     }
521 
522     @Override
523     public String getRequestServerName()
524     {
525         return _servletRequest.getServerName();
526     }
527 
528     @Override
529     public String getRequestServletPath()
530     {
531         checkHttpServletRequest();
532         // return (_httpServletRequest).getServletPath();
533         // HACK: see constructor
534         return _requestServletPath;
535     }
536 
537     @Override
538     public String getAuthType()
539     {
540         checkHttpServletRequest();
541         return _httpServletRequest.getAuthType();
542     }
543 
544     @Override
545     public String getRemoteUser()
546     {
547         checkHttpServletRequest();
548         return _httpServletRequest.getRemoteUser();
549     }
550 
551     @Override
552     public boolean isUserInRole(final String role)
553     {
554         checkNull(role, "role");
555         checkHttpServletRequest();
556         return _httpServletRequest.isUserInRole(role);
557     }
558 
559     @Override
560     public Principal getUserPrincipal()
561     {
562         checkHttpServletRequest();
563         return _httpServletRequest.getUserPrincipal();
564     }
565 
566     @Override
567     public void invalidateSession()
568     {
569         HttpSession session = (HttpSession) getSession(false);
570 
571         if (session != null)
572         {
573             session.invalidate();
574         }
575     }
576 
577     /**
578      * @since 2.0
579      */
580     @Override
581     public boolean isResponseCommitted()
582     {
583         return _httpServletResponse.isCommitted();
584     }
585 
586     @Override
587     public void redirect(final String url) throws IOException
588     {
589         FacesContext facesContext = getCurrentFacesContext();
590         PartialViewContext partialViewContext = facesContext.getPartialViewContext(); 
591         if (partialViewContext.isPartialRequest())
592         {
593             PartialResponseWriter writer = partialViewContext.getPartialResponseWriter();
594             this.setResponseContentType("text/xml");
595             this.setResponseCharacterEncoding("UTF-8");
596             this.addResponseHeader("Cache-control", "no-cache");
597             writer.startDocument();
598             writer.redirect(url);
599             writer.endDocument();
600             facesContext.responseComplete();
601         }
602         else if (_servletResponse instanceof HttpServletResponse)
603         {
604             ((HttpServletResponse) _servletResponse).sendRedirect(url);
605             facesContext.responseComplete();
606         }
607         else
608         {
609             throw new IllegalArgumentException("Only HttpServletResponse supported");
610         }
611     }
612 
613     /**
614      * @since 2.0
615      */
616     @Override
617     public void responseFlushBuffer() throws IOException
618     {
619         checkHttpServletResponse();
620         _httpServletResponse.flushBuffer();
621     }
622 
623     /**
624      * @since 2.0
625      */
626     @Override
627     public void responseReset()
628     {
629         checkHttpServletResponse();
630         _httpServletResponse.reset();
631     }
632 
633     /**
634      * @since 2.0
635      */
636     @Override
637     public void responseSendError(int statusCode, String message) throws IOException
638     {
639         checkHttpServletResponse();
640         if (message == null)
641         {
642             _httpServletResponse.sendError(statusCode);
643         }
644         else
645         {
646             _httpServletResponse.sendError(statusCode, message);
647         }
648     }
649 
650     @Override
651     @SuppressWarnings("unchecked")
652     public Iterator<Locale> getRequestLocales()
653     {
654         checkHttpServletRequest();
655         return new EnumerationIterator(_httpServletRequest.getLocales());
656     }
657 
658     /**
659      * @since JSF 1.2
660      * @param request
661      */
662     @Override
663     public void setRequest(final java.lang.Object request)
664     {
665         this._servletRequest = (ServletRequest) request;
666         this._httpServletRequest = isHttpServletRequest(_servletRequest) ? (HttpServletRequest) _servletRequest : null;
667         this._httpServletRequest = isHttpServletRequest(_servletRequest) ? (HttpServletRequest) _servletRequest : null;
668         this._requestHeaderMap = null;
669         this._requestHeaderValuesMap = null;
670         this._requestMap = null;
671         this._requestParameterMap = null;
672         this._requestParameterValuesMap = null;
673         this._requestCookieMap = null;
674         this._sessionMap = null;
675     }
676 
677     /**
678      * @since JSF 1.2
679      * @param encoding
680      * @throws java.io.UnsupportedEncodingException
681      */
682     @Override
683     public void setRequestCharacterEncoding(final java.lang.String encoding) throws java.io.UnsupportedEncodingException
684     {
685 
686         this._servletRequest.setCharacterEncoding(encoding);
687 
688     }
689 
690     /**
691      * @since JSF 1.2
692      */
693     @Override
694     public String getRequestCharacterEncoding()
695     {
696         return _servletRequest.getCharacterEncoding();
697     }
698 
699     /**
700      * @since JSF 1.2
701      */
702     @Override
703     public String getResponseCharacterEncoding()
704     {
705         return _servletResponse.getCharacterEncoding();
706     }
707 
708     /**
709      * @since JSF 1.2
710      * @param response
711      */
712     @Override
713     public void setResponse(final java.lang.Object response)
714     {
715         this._servletResponse = (ServletResponse) response;
716     }
717 
718     /**
719      * @since 2.0
720      */
721     @Override
722     public void setResponseBufferSize(int size)
723     {
724         checkHttpServletResponse();
725         _httpServletResponse.setBufferSize(size);
726     }
727 
728     /**
729      * @since JSF 1.2
730      * @param encoding
731      */
732     @Override
733     public void setResponseCharacterEncoding(final java.lang.String encoding)
734     {
735         this._servletResponse.setCharacterEncoding(encoding);
736     }
737 
738     /**
739      * @since 2.0
740      */
741     @Override
742     public void setResponseContentLength(int length)
743     {
744         checkHttpServletResponse();
745         _httpServletResponse.setContentLength(length);
746     }
747 
748     @Override
749     public void setResponseContentType(String contentType)
750     {
751         // If the response has not been committed yet.
752         if (!_servletResponse.isCommitted())
753         {
754             // Sets the content type of the response being sent to the client
755             _servletResponse.setContentType(contentType);
756         }
757         else
758         {
759             // I did not throw an exception just to be sure nothing breaks.
760             log.severe("Cannot set content type. Response already committed");
761         }
762     }
763 
764     /**
765      * @since 2.0
766      */
767     @Override
768     public void setResponseHeader(String name, String value)
769     {
770         checkHttpServletResponse();
771         _httpServletResponse.setHeader(name, value);
772     }
773 
774     @Override
775     public void setResponseStatus(int statusCode)
776     {
777         checkHttpServletResponse();
778         _httpServletResponse.setStatus(statusCode);
779     }
780 
781     private void checkHttpServletRequest()
782     {
783         if (_httpServletRequest == null)
784         {
785             throw new UnsupportedOperationException("Only HttpServletRequest supported");
786         }
787     }
788 
789     private boolean isHttpServletRequest(final ServletRequest servletRequest)
790     {
791         return servletRequest instanceof HttpServletRequest;
792     }
793 
794     private void checkHttpServletResponse()
795     {
796         if (_httpServletRequest == null)
797         {
798             throw new UnsupportedOperationException("Only HttpServletResponse supported");
799         }
800     }
801     private boolean isHttpServletResponse(final ServletResponse servletResponse)
802     {
803         return servletResponse instanceof HttpServletResponse;
804     }
805 
806     /**
807      * @since JSF 2.0
808      */
809     @Override
810     public void addResponseCookie(final String name,
811             final String value, final Map<String, Object> properties)
812     {
813         checkHttpServletResponse();
814         Cookie cookie = new Cookie(name, value);
815         if (properties != null)
816         {
817             for (Map.Entry<String, Object> entry : properties.entrySet())
818             {
819                 String propertyKey = entry.getKey();
820                 Object propertyValue = entry.getValue();
821                 if ("comment".equals(propertyKey))
822                 {
823                     cookie.setComment((String) propertyValue);
824                     continue;
825                 }
826                 else if ("domain".equals(propertyKey))
827                 {
828                     cookie.setDomain((String)propertyValue);
829                     continue;
830                 }
831                 else if ("maxAge".equals(propertyKey))
832                 {
833                     cookie.setMaxAge((Integer) propertyValue);
834                     continue;
835                 }
836                 else if ("secure".equals(propertyKey))
837                 {
838                     cookie.setSecure((Boolean) propertyValue);
839                     continue;
840                 }
841                 else if ("path".equals(propertyKey))
842                 {
843                     cookie.setPath((String) propertyValue);
844                     continue;
845                 }
846                 else if ("httpOnly".equals(propertyKey))
847                 {
848                     cookie.setHttpOnly((Boolean) propertyValue);
849                     continue;
850                 }
851                 throw new IllegalArgumentException("Unused key when creating Cookie");
852             }
853         }
854         _httpServletResponse.addCookie(cookie);
855     }
856 
857     @Override
858     public void addResponseHeader(String name, String value)
859     {
860         _httpServletResponse.addHeader(name, value);
861     }
862 
863     private String encodeURL(String baseUrl, Map<String, List<String>> parameters)
864     {
865         checkNull(baseUrl, "url");
866         checkHttpServletRequest();
867 
868         String fragment = null;
869         String queryString = null;
870         Map<String, List<String>> paramMap = null;
871 
872         //extract any URL fragment
873         int index = baseUrl.indexOf(URL_FRAGMENT_SEPERATOR);
874         if (index != -1)
875         {
876             fragment = baseUrl.substring(index+1);
877             baseUrl = baseUrl.substring(0,index);
878         }
879 
880         //extract the current query string and add the params to the paramMap
881         index = baseUrl.indexOf(URL_QUERY_SEPERATOR);
882         if (index != -1)
883         {
884             queryString = baseUrl.substring(index + 1);
885             baseUrl = baseUrl.substring(0, index);
886             String[] nameValuePairs = queryString.split(URL_PARAM_SEPERATOR);
887             for (int i = 0; i < nameValuePairs.length; i++)
888             {
889                 String[] currentPair = nameValuePairs[i].split(URL_NAME_VALUE_PAIR_SEPERATOR);
890                 String currentName = currentPair[0];
891 
892                 if (paramMap == null)
893                 {
894                     paramMap = new HashMap<String, List<String>>();
895                 }
896                 
897                 List<String> values = paramMap.get(currentName);
898                 if (values == null)
899                 {
900                     values = new ArrayList<>(1);
901                     paramMap.put(currentName, values);
902                 }
903  
904                 try
905                 {
906                     values.add(currentPair.length > 1
907                                 ? URLDecoder.decode(currentPair[1], getResponseCharacterEncoding())
908                                 : "");
909                 }
910                 catch (UnsupportedEncodingException e)
911                 {
912                     //shouldn't ever get here
913                     throw new UnsupportedOperationException("Encoding type=" + getResponseCharacterEncoding()
914                                                             + " not supported", e);
915                 }
916             }
917         }
918 
919         //add/update with new params on the paramMap
920         if (parameters != null && parameters.size() > 0)
921         {
922             for (Map.Entry<String, List<String>> pair : parameters.entrySet())
923             {
924                 if (pair.getKey() != null && pair.getKey().trim().length() != 0)
925                 {
926                     if (paramMap == null)
927                     {
928                         paramMap = new HashMap<String, List<String>>();
929                     }
930                     paramMap.put(pair.getKey(), pair.getValue());
931                 }
932             }
933         }
934         
935         FacesContext facesContext = getCurrentFacesContext();
936         ClientWindow window = facesContext.getExternalContext().getClientWindow();
937         if (window != null && window.isClientWindowRenderModeEnabled(facesContext))
938         {
939             if (paramMap == null)
940             {
941                 paramMap = new HashMap<String, List<String>>();
942             }
943 
944             if (!paramMap.containsKey(ResponseStateManager.CLIENT_WINDOW_URL_PARAM))
945             {
946                 paramMap.put(ResponseStateManager.CLIENT_WINDOW_URL_PARAM, Arrays.asList(window.getId()));
947             }
948 
949             Map<String, String> additionalQueryURLParameters = window.getQueryURLParameters(facesContext);
950             if (additionalQueryURLParameters != null)
951             {
952                 for (Map.Entry<String , String> entry : additionalQueryURLParameters.entrySet())
953                 {
954                     paramMap.put(entry.getKey(), Arrays.asList(entry.getValue()));
955                 }
956             }
957         }
958 
959         boolean hasParams = paramMap != null && paramMap.size()>0;
960 
961         if (!hasParams && fragment == null) 
962         {
963             return baseUrl;
964         }
965 
966         // start building the new URL
967         StringBuilder newUrl = new StringBuilder(baseUrl);
968 
969         //now add the updated param list onto the url
970         if (hasParams)
971         {
972             boolean isFirstPair = true;
973             for (Map.Entry<String, List<String>> pair : paramMap.entrySet())
974             {
975                 for (int i = 0; i < pair.getValue().size(); i++)
976                 {
977                     String value = pair.getValue().get(i);
978                     
979                     if (!isFirstPair)
980                     {
981                         newUrl.append(URL_PARAM_SEPERATOR);
982                     }
983                     else
984                     {
985                         newUrl.append(URL_QUERY_SEPERATOR);
986                         isFirstPair = false;
987                     }
988 
989                     newUrl.append(pair.getKey());
990                     newUrl.append(URL_NAME_VALUE_PAIR_SEPERATOR);
991                     try
992                     {
993                         newUrl.append(URLEncoder.encode(value, getResponseCharacterEncoding()));
994                     }
995                     catch (UnsupportedEncodingException e)
996                     {
997                         //shouldn't ever get here
998                         throw new UnsupportedOperationException("Encoding type=" + getResponseCharacterEncoding()
999                                                                 + " not supported", e);
1000                     }
1001                 }
1002             }
1003         }
1004 
1005         //add the fragment back on (if any)
1006         if (fragment != null)
1007         {
1008             newUrl.append(URL_FRAGMENT_SEPERATOR);
1009             newUrl.append(fragment);
1010         }
1011 
1012         return newUrl.toString();
1013     }
1014     
1015     /**
1016      * @since 2.0
1017      */
1018     public Flash getFlash()
1019     {
1020         if (_flash == null)
1021         {
1022             if (_flashFactory == null)
1023             {
1024                 _flashFactory = (FlashFactory) FactoryFinder.getFactory(
1025                     FactoryFinder.FLASH_FACTORY);
1026                 if (_flashFactory == null)
1027                 {
1028                     //Fallback to servlet default flash
1029                     _flash = FlashImpl.getCurrentInstance(this);
1030                 }
1031                 else
1032                 {
1033                     _flash = _flashFactory.getFlash(true);
1034                 }
1035             }
1036             else
1037             {
1038                 _flash = _flashFactory.getFlash(true);
1039             }
1040         }
1041         return _flash;
1042         //return FlashImpl.getCurrentInstance(this);
1043     }
1044 
1045     @Override
1046     public boolean isSecure()
1047     {
1048         return _servletRequest.isSecure();
1049     }
1050 
1051     @Override
1052     public int getSessionMaxInactiveInterval()
1053     {
1054         HttpSession session = _httpServletRequest.getSession();
1055         return session.getMaxInactiveInterval();
1056     }
1057     
1058     @Override
1059     public void setSessionMaxInactiveInterval(int interval)
1060     {
1061         HttpSession session = _httpServletRequest.getSession();
1062         session.setMaxInactiveInterval(interval);
1063     }
1064     
1065     protected FacesContext getCurrentFacesContext()
1066     {
1067         if (_currentFacesContext == null)
1068         {
1069             _currentFacesContext = FacesContext.getCurrentInstance();
1070         }
1071         return _currentFacesContext;
1072     }
1073 }