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