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