View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.application;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.concurrent.ConcurrentHashMap;
32  import java.util.logging.Level;
33  import java.util.logging.Logger;
34  import java.util.stream.Stream;
35  
36  import javax.faces.FacesException;
37  import javax.faces.FactoryFinder;
38  import javax.faces.application.Application;
39  import javax.faces.application.StateManager;
40  import javax.faces.application.ViewHandler;
41  import javax.faces.application.ViewVisitOption;
42  import javax.faces.component.UIViewParameter;
43  import javax.faces.component.UIViewRoot;
44  import javax.faces.context.ExternalContext;
45  import javax.faces.context.FacesContext;
46  import javax.faces.render.RenderKitFactory;
47  import javax.faces.render.ResponseStateManager;
48  import javax.faces.view.ViewDeclarationLanguage;
49  import javax.faces.view.ViewDeclarationLanguageFactory;
50  import javax.faces.view.ViewMetadata;
51  import javax.servlet.http.HttpServletResponse;
52  
53  import org.apache.myfaces.application.viewstate.StateCacheUtils;
54  import org.apache.myfaces.shared.application.DefaultViewHandlerSupport;
55  import org.apache.myfaces.shared.application.InvalidViewIdException;
56  import org.apache.myfaces.shared.application.ViewHandlerSupport;
57  import org.apache.myfaces.view.facelets.StateWriter;
58  
59  /**
60   * JSF 2.0 ViewHandler implementation 
61   *
62   * @since 2.0
63   */
64  public class ViewHandlerImpl extends ViewHandler
65  {
66      private static final Logger log = Logger.getLogger(ViewHandlerImpl.class.getName());
67      
68      public static final String FORM_STATE_MARKER = "<!--@@JSF_FORM_STATE_MARKER@@-->";
69      private ViewHandlerSupport _viewHandlerSupport;
70      private ViewDeclarationLanguageFactory _vdlFactory;
71      
72      private Set<String> _protectedViewsSet;
73      private Set<String> _unmodifiableProtectedViewsSet;
74      
75      /**
76       * Gets the current ViewHandler via FacesContext.getApplication().getViewHandler().
77       * We have to use this method to invoke any other specified ViewHandler-method
78       * in the code, because direct access (this.method()) will cause problems if
79       * the ViewHandler is wrapped.
80       * @param facesContext
81       * @return
82       */
83      public static ViewHandler getViewHandler(FacesContext facesContext)
84      {
85          return facesContext.getApplication().getViewHandler();
86      }
87  
88      public ViewHandlerImpl()
89      {
90          _protectedViewsSet = Collections.newSetFromMap(new ConcurrentHashMap<String,Boolean>());
91          _unmodifiableProtectedViewsSet = Collections.unmodifiableSet(_protectedViewsSet);
92          _vdlFactory = (ViewDeclarationLanguageFactory)
93                  FactoryFinder.getFactory(FactoryFinder.VIEW_DECLARATION_LANGUAGE_FACTORY);
94          if (log.isLoggable(Level.FINEST))
95          {
96              log.finest("New ViewHandler instance created");
97          }
98      }
99  
100     @Override
101     public String deriveViewId(FacesContext context, String input)
102     {
103         if(input != null)
104         {
105             try
106             {
107                 return getViewHandlerSupport(context).calculateAndCheckViewId(context, input);
108             }
109             catch (InvalidViewIdException e)
110             {
111                 sendSourceNotFound(context, e.getMessage());
112             }
113         }
114         return input;   // If the argument input is null, return null.
115     }
116     
117     @Override
118     public String deriveLogicalViewId(FacesContext context, String rawViewId)
119     {
120         if(rawViewId != null)
121         {
122             try
123             {
124                 return getViewHandlerSupport(context).calculateViewId(context, rawViewId);
125             }
126             catch (InvalidViewIdException e)
127             {
128                 sendSourceNotFound(context, e.getMessage());
129             }
130         }
131         return rawViewId;   // If the argument input is null, return null.
132     }
133 
134     @Override
135     public String getBookmarkableURL(FacesContext context, String viewId,
136             Map<String, List<String>> parameters, boolean includeViewParams)
137     {
138         Map<String, List<String>> viewParameters;
139         if (includeViewParams)
140         {
141             viewParameters = getViewParameterList(context, viewId, parameters);
142         }
143         else
144         {
145             viewParameters = parameters;
146         }
147         
148         // note that we cannot use this.getActionURL(), because this will
149         // cause problems if the ViewHandler is wrapped
150         String actionEncodedViewId = getViewHandler(context).getActionURL(context, viewId);
151         
152         ExternalContext externalContext = context.getExternalContext();
153         String bookmarkEncodedURL = externalContext.encodeBookmarkableURL(actionEncodedViewId, viewParameters);
154         return externalContext.encodeActionURL(bookmarkEncodedURL);
155     }
156 
157     @Override
158     public String getRedirectURL(FacesContext context, String viewId,
159             Map<String, List<String>> parameters, boolean includeViewParams)
160     {
161         Map<String, List<String>> viewParameters;
162         if (includeViewParams)
163         {
164             viewParameters = getViewParameterList(context, viewId, parameters);
165         }
166         else
167         {
168             viewParameters = parameters;
169         }
170         
171         // note that we cannot use this.getActionURL(), because this will
172         // cause problems if the ViewHandler is wrapped
173         String actionEncodedViewId = getViewHandler(context).getActionURL(context, viewId);
174         
175         ExternalContext externalContext = context.getExternalContext();
176         String redirectEncodedURL = externalContext.encodeRedirectURL(actionEncodedViewId, viewParameters);
177         return externalContext.encodeActionURL(redirectEncodedURL);
178     }
179 
180     @Override
181     public ViewDeclarationLanguage getViewDeclarationLanguage(
182             FacesContext context, String viewId)
183     {
184         // return a suitable ViewDeclarationLanguage implementation for the given viewId
185         return _vdlFactory.getViewDeclarationLanguage(viewId);
186     }
187 
188     @Override
189     public void initView(FacesContext context) throws FacesException
190     {
191         if(context.getExternalContext().getRequestCharacterEncoding() == null)
192         {
193             super.initView(context);    
194         }
195     }
196 
197     /**
198      * Get the locales specified as acceptable by the original request, compare them to the
199      * locales supported by this Application and return the best match.
200      */
201     @Override
202     public Locale calculateLocale(FacesContext facesContext)
203     {
204         Application application = facesContext.getApplication();
205         for (Iterator<Locale> requestLocales = facesContext.getExternalContext().getRequestLocales(); requestLocales
206                 .hasNext();)
207         {
208             Locale requestLocale = requestLocales.next();
209             for (Iterator<Locale> supportedLocales = application.getSupportedLocales(); supportedLocales.hasNext();)
210             {
211                 Locale supportedLocale = supportedLocales.next();
212                 // higher priority to a language match over an exact match
213                 // that occurs further down (see JSTL Reference 1.0 8.3.1)
214                 if (requestLocale.getLanguage().equals(supportedLocale.getLanguage())
215                         && (supportedLocale.getCountry() == null || supportedLocale.getCountry().length() == 0))
216                 {
217                     return supportedLocale;
218                 }
219                 else if (supportedLocale.equals(requestLocale))
220                 {
221                     return supportedLocale;
222                 }
223             }
224         }
225 
226         Locale defaultLocale = application.getDefaultLocale();
227         return defaultLocale != null ? defaultLocale : Locale.getDefault();
228     }
229 
230     @Override
231     public String calculateRenderKitId(FacesContext facesContext)
232     {
233         Object renderKitId = facesContext.getExternalContext().getRequestMap().get(
234                 ResponseStateManager.RENDER_KIT_ID_PARAM);
235         if (renderKitId == null)
236         {
237             renderKitId = facesContext.getApplication().getDefaultRenderKitId();
238         }
239         if (renderKitId == null)
240         {
241             renderKitId = RenderKitFactory.HTML_BASIC_RENDER_KIT;
242         }
243         return renderKitId.toString();
244     }
245     
246     @Override
247     public UIViewRoot createView(FacesContext context, String viewId)
248     {
249        checkNull(context, "facesContext");
250        String calculatedViewId = getViewHandlerSupport(context).calculateViewId(context, viewId);
251        
252        // we cannot use this.getVDL() directly (see getViewHandler())
253        //return getViewHandler(context)
254        //        .getViewDeclarationLanguage(context, calculatedViewId)
255        //            .createView(context, calculatedViewId);
256        // -= Leonardo Uribe =- Temporally reverted by TCK issues.
257        ViewDeclarationLanguage vdl = getViewDeclarationLanguage(context,calculatedViewId);
258        if (vdl == null)
259        {
260            // If there is no VDL that can handle the view, throw 404 response.
261            sendSourceNotFound(context, viewId);
262            return null;
263        }
264        return vdl.createView(context,calculatedViewId);
265     }
266 
267     @Override
268     public String getActionURL(FacesContext context, String viewId)
269     {
270         checkNull(context, "facesContext");
271         checkNull(viewId, "viewId");
272         return getViewHandlerSupport(context).calculateActionURL(context, viewId);
273     }
274 
275     @Override
276     public String getResourceURL(FacesContext facesContext, String path)
277     {
278         checkNull(facesContext, "facesContext");
279         checkNull(path, "path");
280         if (path.length() > 0 && path.charAt(0) == '/')
281         {
282             String contextPath = facesContext.getExternalContext().getRequestContextPath();
283             if (contextPath == null)
284             {
285                 return path;
286             }
287             else if (contextPath.length() == 1 && contextPath.charAt(0) == '/')
288             {
289                 // If the context path is root, it is not necessary to append it, otherwise
290                 // and extra '/' will be set.
291                 return path;
292             }
293             else
294             {
295                 return  contextPath + path;
296             }
297         }
298         return path;
299 
300     }
301 
302     @Override
303     public void renderView(FacesContext context, UIViewRoot viewToRender)
304             throws IOException, FacesException
305     {
306 
307         checkNull(context, "context");
308         checkNull(viewToRender, "viewToRender");
309 
310         // we cannot use this.getVDL() directly (see getViewHandler())
311         //String viewId = viewToRender.getViewId();
312         //getViewHandler(context).getViewDeclarationLanguage(context, viewId)
313         //        .renderView(context, viewToRender);
314         // -= Leonardo Uribe =- Temporally reverted by TCK issues.
315         getViewDeclarationLanguage(context,viewToRender.getViewId()).renderView(context, viewToRender);
316     }
317 
318     @Override
319     public UIViewRoot restoreView(FacesContext context, String viewId)
320     {
321         checkNull(context, "context");
322     
323         String calculatedViewId = getViewHandlerSupport(context).calculateViewId(context, viewId);
324         
325         // we cannot use this.getVDL() directly (see getViewHandler())
326         //return getViewHandler(context)
327         //        .getViewDeclarationLanguage(context,calculatedViewId)
328         //            .restoreView(context, calculatedViewId);
329         // -= Leonardo Uribe =- Temporally reverted by TCK issues.
330         ViewDeclarationLanguage vdl = getViewDeclarationLanguage(context,calculatedViewId);
331         if (vdl == null)
332         {
333             // If there is no VDL that can handle the view, throw 404 response.
334             sendSourceNotFound(context, viewId);
335             return null;
336             
337         }
338         return vdl.restoreView(context, calculatedViewId); 
339     }
340     
341     @Override
342     public void writeState(FacesContext context) throws IOException
343     {
344         checkNull(context, "context");
345 
346         if(context.getPartialViewContext().isAjaxRequest())
347         {
348             return;
349         }
350 
351         ResponseStateManager responseStateManager = context.getRenderKit().getResponseStateManager();
352         
353         setWritingState(context, responseStateManager);
354 
355         StateManager stateManager = context.getApplication().getStateManager();
356         
357         // By the spec, it is necessary to use a writer to write FORM_STATE_MARKER, 
358         // after the view is rendered, to preserve changes done on the component tree
359         // on rendering time. But if server side state saving is used, this is not 
360         // really necessary, because a token could be used and after the view is
361         // rendered, a simple call to StateManager.saveState() could do the trick.
362         // The code below check if we are using MyFacesResponseStateManager and if
363         // that so, check if the current one support the trick.
364         if (StateCacheUtils.isMyFacesResponseStateManager(responseStateManager))
365         {
366             if (StateCacheUtils.getMyFacesResponseStateManager(responseStateManager).
367                     isWriteStateAfterRenderViewRequired(context))
368             {
369                 // Only write state marker if javascript view state is disabled
370                 context.getResponseWriter().write(FORM_STATE_MARKER);
371             }
372             else
373             {
374                 stateManager.writeState(context, new Object[2]);
375             }
376         }
377         else
378         {
379             // Only write state marker if javascript view state is disabled
380             context.getResponseWriter().write(FORM_STATE_MARKER);
381         }
382     }
383 
384     @Override
385     public void addProtectedView(String urlPattern)
386     {
387         _protectedViewsSet.add(urlPattern);
388     }
389 
390     @Override
391     public boolean removeProtectedView(String urlPattern)
392     {
393         return _protectedViewsSet.remove(urlPattern);
394     }
395 
396     @Override
397     public Set<String> getProtectedViewsUnmodifiable()
398     {
399         return _unmodifiableProtectedViewsSet;
400     }
401     
402     private void setWritingState(FacesContext context, ResponseStateManager rsm)
403     {
404         // Facelets specific hack:
405         // Tell the StateWriter that we're about to write state
406         StateWriter stateWriter = StateWriter.getCurrentInstance(context);
407         if (stateWriter != null)
408         {
409             // Write the STATE_KEY out. Unfortunately, this will
410             // be wasteful for pure server-side state managers where nothing
411             // is actually written into the output, but this cannot
412             // programatically be discovered
413             // -= Leonardo Uribe =- On MyFacesResponseStateManager was added
414             // some methods to discover it programatically.
415             if (StateCacheUtils.isMyFacesResponseStateManager(rsm))
416             {
417                 if (StateCacheUtils.getMyFacesResponseStateManager(rsm).isWriteStateAfterRenderViewRequired(context))
418                 {
419                     stateWriter.writingState();
420                 }
421                 else
422                 {
423                     stateWriter.writingStateWithoutWrapper();
424                 }
425             }
426             else
427             {
428                 stateWriter.writingState();
429             }
430             
431             
432         }
433         //else
434         //{
435             //we're in a JSP, let the JSPStatemanager know that we need to actually write the state
436         //}        
437     }
438     
439     private Map<String, List<String>> getViewParameterList(FacesContext context,
440             String viewId, Map<String, List<String>> parametersFromArg)
441     {
442         UIViewRoot viewRoot = context.getViewRoot();
443         String currentViewId = viewRoot.getViewId();
444         Collection<UIViewParameter> toViewParams = null;
445         Collection<UIViewParameter> currentViewParams = ViewMetadata.getViewParameters(viewRoot);
446 
447         if (currentViewId.equals(viewId))
448         {
449             toViewParams = currentViewParams;
450         }
451         else
452         {
453             String calculatedViewId = getViewHandlerSupport(context).calculateViewId(context, viewId);  
454             // we cannot use this.getVDL() directly (see getViewHandler())
455             //ViewDeclarationLanguage vdl = getViewHandler(context).
456             //        getViewDeclarationLanguage(context, calculatedViewId);
457             // -= Leonardo Uribe =- Temporally reverted by TCK issues.
458             ViewDeclarationLanguage vdl = getViewDeclarationLanguage(context,calculatedViewId);
459             ViewMetadata viewMetadata = vdl.getViewMetadata(context, viewId);
460             // getViewMetadata() returns null on JSP
461             if (viewMetadata != null)
462             {
463                 UIViewRoot viewFromMetaData = viewMetadata.createMetadataView(context);
464                 toViewParams = ViewMetadata.getViewParameters(viewFromMetaData);
465             }
466         }
467 
468         if (toViewParams == null || toViewParams.isEmpty())
469         {
470             return parametersFromArg;
471         }
472         
473         // we need to use a custom Map to add the view parameters,
474         // otherwise the current value of the view parameter will be added to
475         // the navigation case as a static (!!!) parameter, thus the value
476         // won't be updated on any following request
477         // (Note that parametersFromArg is the Map from the NavigationCase)
478         // Also note that we don't have to copy the Lists, because they won't be changed
479         Map<String, List<String>> parameters = new HashMap<String, List<String>>();
480         parameters.putAll(parametersFromArg);
481 
482         for (UIViewParameter viewParameter : toViewParams)
483         {
484             if (!parameters.containsKey(viewParameter.getName()))
485             {
486                 String parameterValue = viewParameter.getStringValueFromModel(context);
487                 if (parameterValue == null)
488                 {
489                     if(currentViewId.equals(viewId))
490                     {
491                         parameterValue = viewParameter.getStringValue(context);
492                     }
493                     else
494                     {
495                         if (viewParameter.getName() != null)
496                         {
497                             for (UIViewParameter curParam : currentViewParams)
498                             {
499                                 if (viewParameter.getName().equals(curParam.getName())) 
500                                 {
501                                     parameterValue = curParam.getStringValue(context);
502                                     break;
503                                 }
504                             }
505                         }
506                     }
507                 }
508 
509                 if (parameterValue != null)
510                 {
511                     // since we have checked !parameters.containsKey(viewParameter.getName())
512                     // here already, the parameters Map will never contain a List under the
513                     // key viewParameter.getName(), thus we do not have to check it here (again).
514                     List<String> parameterValueList = new ArrayList<String>(1);
515                     parameterValueList.add(parameterValue);
516                     parameters.put(viewParameter.getName(), parameterValueList);
517                 }
518             }
519         }        
520         return parameters;
521     }
522     
523     private void checkNull(final Object o, final String param)
524     {
525         if (o == null)
526         {
527             throw new NullPointerException(param + " can not be null.");
528         }
529     }
530     
531     private void sendSourceNotFound(FacesContext context, String message)
532     {
533         HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
534         try
535         {
536             context.responseComplete();
537             response.sendError(HttpServletResponse.SC_NOT_FOUND, message);
538         }
539         catch (IOException ioe)
540         {
541             throw new FacesException(ioe);
542         }
543     }
544     
545     public void setViewHandlerSupport(ViewHandlerSupport viewHandlerSupport)
546     {
547         _viewHandlerSupport = viewHandlerSupport;
548     }    
549     
550     protected ViewHandlerSupport getViewHandlerSupport()
551     {
552         return getViewHandlerSupport(FacesContext.getCurrentInstance());
553     }
554 
555     protected ViewHandlerSupport getViewHandlerSupport(FacesContext context)
556     {
557         if (_viewHandlerSupport == null)
558         {
559             _viewHandlerSupport = new DefaultViewHandlerSupport(context);
560         }
561         return _viewHandlerSupport;
562     }
563 
564     @Override
565     public Stream<String> getViews(FacesContext facesContext, String path, int maxDepth, ViewVisitOption... options)
566     {
567         Stream concatenatedStream = null;
568         for (ViewDeclarationLanguage vdl : _vdlFactory.getAllViewDeclarationLanguages())
569         {
570             Stream stream = vdl.getViews(facesContext, path, maxDepth, options);
571             if (concatenatedStream == null)
572             {
573                 concatenatedStream = stream;
574             }
575             else
576             {
577                 concatenatedStream = Stream.concat(concatenatedStream, stream);
578             }
579         }
580         return concatenatedStream == null ? Stream.empty() : concatenatedStream;
581     }
582     
583     @Override
584     public String getWebsocketURL(FacesContext context, String channelAndToken)
585     {
586         String url = context.getExternalContext().getRequestContextPath() + 
587                 "/javax.faces.push/"+channelAndToken;
588         return url;
589     }
590     
591 }