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