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