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        return getViewDeclarationLanguage(context,calculatedViewId).createView(context,calculatedViewId);
252     }
253 
254     @Override
255     public String getActionURL(FacesContext context, String viewId)
256     {
257         return getViewHandlerSupport(context).calculateActionURL(context, viewId);
258     }
259 
260     @Override
261     public String getResourceURL(FacesContext facesContext, String path)
262     {
263         if (path.length() > 0 && path.charAt(0) == '/')
264         {
265             return facesContext.getExternalContext().getRequestContextPath() + path;
266         }
267 
268         return path;
269 
270     }
271 
272     @Override
273     public void renderView(FacesContext context, UIViewRoot viewToRender)
274             throws IOException, FacesException
275     {
276 
277         checkNull(context, "context");
278         checkNull(viewToRender, "viewToRender");
279 
280         // we cannot use this.getVDL() directly (see getViewHandler())
281         //String viewId = viewToRender.getViewId();
282         //getViewHandler(context).getViewDeclarationLanguage(context, viewId)
283         //        .renderView(context, viewToRender);
284         // -= Leonardo Uribe =- Temporally reverted by TCK issues.
285         getViewDeclarationLanguage(context,viewToRender.getViewId()).renderView(context, viewToRender);
286     }
287 
288     @Override
289     public UIViewRoot restoreView(FacesContext context, String viewId)
290     {
291         checkNull(context, "context");
292     
293         String calculatedViewId = getViewHandlerSupport(context).calculateViewId(context, viewId);
294         
295         // we cannot use this.getVDL() directly (see getViewHandler())
296         //return getViewHandler(context)
297         //        .getViewDeclarationLanguage(context,calculatedViewId)
298         //            .restoreView(context, calculatedViewId);
299         // -= Leonardo Uribe =- Temporally reverted by TCK issues.
300         return getViewDeclarationLanguage(context,calculatedViewId).restoreView(context, calculatedViewId); 
301     }
302     
303     @Override
304     public void writeState(FacesContext context) throws IOException
305     {
306         checkNull(context, "context");
307 
308         if(context.getPartialViewContext().isAjaxRequest())
309         {
310             return;
311         }
312 
313         ResponseStateManager responseStateManager = context.getRenderKit().getResponseStateManager();
314         
315         setWritingState(context, responseStateManager);
316 
317         StateManager stateManager = context.getApplication().getStateManager();
318         
319         // By the spec, it is necessary to use a writer to write FORM_STATE_MARKER, 
320         // after the view is rendered, to preserve changes done on the component tree
321         // on rendering time. But if server side state saving is used, this is not 
322         // really necessary, because a token could be used and after the view is
323         // rendered, a simple call to StateManager.saveState() could do the trick.
324         // The code below check if we are using MyFacesResponseStateManager and if
325         // that so, check if the current one support the trick.
326         if (StateCacheUtils.isMyFacesResponseStateManager(responseStateManager))
327         {
328             if (StateCacheUtils.getMyFacesResponseStateManager(responseStateManager).
329                     isWriteStateAfterRenderViewRequired(context))
330             {
331                 // Only write state marker if javascript view state is disabled
332                 ExternalContext extContext = context.getExternalContext();
333                 if (!(JavascriptUtils.isJavascriptAllowed(extContext) &&
334                         MyfacesConfig.getCurrentInstance(extContext).isViewStateJavascript()))
335                 {
336                     context.getResponseWriter().write(FORM_STATE_MARKER);
337                 }
338             }
339             else
340             {
341                 stateManager.writeState(context, new Object[2]);
342             }
343         }
344         else
345         {
346             // Only write state marker if javascript view state is disabled
347             ExternalContext extContext = context.getExternalContext();
348             if (!(JavascriptUtils.isJavascriptAllowed(extContext) &&
349                     MyfacesConfig.getCurrentInstance(extContext).isViewStateJavascript()))
350             {
351                 context.getResponseWriter().write(FORM_STATE_MARKER);
352             }
353         }
354     }
355     
356     private void setWritingState(FacesContext context, ResponseStateManager rsm)
357     {
358         // Facelets specific hack:
359         // Tell the StateWriter that we're about to write state
360         StateWriter stateWriter = StateWriter.getCurrentInstance(context);
361         if (stateWriter != null)
362         {
363             // Write the STATE_KEY out. Unfortunately, this will
364             // be wasteful for pure server-side state managers where nothing
365             // is actually written into the output, but this cannot
366             // programatically be discovered
367             // -= Leonardo Uribe =- On MyFacesResponseStateManager was added
368             // some methods to discover it programatically.
369             if (StateCacheUtils.isMyFacesResponseStateManager(rsm))
370             {
371                 if (StateCacheUtils.getMyFacesResponseStateManager(rsm).isWriteStateAfterRenderViewRequired(context))
372                 {
373                     stateWriter.writingState();
374                 }
375                 else
376                 {
377                     stateWriter.writingStateWithoutWrapper();
378                 }
379             }
380             else
381             {
382                 stateWriter.writingState();
383             }
384             
385             
386         }
387         else
388         {
389             //we're in a JSP, let the JSPStatemanager know that we need to actually write the state
390         }        
391     }
392     
393     private Map<String, List<String>> getViewParameterList(FacesContext context,
394             String viewId, Map<String, List<String>> parametersFromArg)
395     {
396         UIViewRoot viewRoot = context.getViewRoot();
397         String currentViewId = viewRoot.getViewId();
398         Collection<UIViewParameter> toViewParams = null;
399         Collection<UIViewParameter> currentViewParams = ViewMetadata.getViewParameters(viewRoot);
400 
401         if (currentViewId.equals(viewId))
402         {
403             toViewParams = currentViewParams;
404         }
405         else
406         {
407             String calculatedViewId = getViewHandlerSupport(context).calculateViewId(context, viewId);  
408             // we cannot use this.getVDL() directly (see getViewHandler())
409             //ViewDeclarationLanguage vdl = getViewHandler(context).
410             //        getViewDeclarationLanguage(context, calculatedViewId);
411             // -= Leonardo Uribe =- Temporally reverted by TCK issues.
412             ViewDeclarationLanguage vdl = getViewDeclarationLanguage(context,calculatedViewId);
413             ViewMetadata viewMetadata = vdl.getViewMetadata(context, viewId);
414             // getViewMetadata() returns null on JSP
415             if (viewMetadata != null)
416             {
417                 UIViewRoot viewFromMetaData = viewMetadata.createMetadataView(context);
418                 toViewParams = ViewMetadata.getViewParameters(viewFromMetaData);
419             }
420         }
421 
422         if (toViewParams == null || toViewParams.isEmpty())
423         {
424             return parametersFromArg;
425         }
426         
427         // we need to use a custom Map to add the view parameters,
428         // otherwise the current value of the view parameter will be added to
429         // the navigation case as a static (!!!) parameter, thus the value
430         // won't be updated on any following request
431         // (Note that parametersFromArg is the Map from the NavigationCase)
432         // Also note that we don't have to copy the Lists, because they won't be changed
433         Map<String, List<String>> parameters = new HashMap<String, List<String>>();
434         parameters.putAll(parametersFromArg);
435 
436         for (UIViewParameter viewParameter : toViewParams)
437         {
438             if (!parameters.containsKey(viewParameter.getName()))
439             {
440                 String parameterValue = viewParameter.getStringValueFromModel(context);
441                 if (parameterValue == null)
442                 {
443                     if(currentViewId.equals(viewId))
444                     {
445                         parameterValue = viewParameter.getStringValue(context);
446                     }
447                     else
448                     {
449                         if (viewParameter.getName() != null)
450                         {
451                             for (UIViewParameter curParam : currentViewParams)
452                             {
453                                 if (viewParameter.getName().equals(curParam.getName())) 
454                                 {
455                                     parameterValue = curParam.getStringValue(context);
456                                     break;
457                                 }
458                             }
459                         }
460                     }
461                 }
462 
463                 if (parameterValue != null)
464                 {
465                     // since we have checked !parameters.containsKey(viewParameter.getName())
466                     // here already, the parameters Map will never contain a List under the
467                     // key viewParameter.getName(), thus we do not have to check it here (again).
468                     List<String> parameterValueList = new ArrayList<String>();
469                     parameterValueList.add(parameterValue);
470                     parameters.put(viewParameter.getName(), parameterValueList);
471                 }
472             }
473         }        
474         return parameters;
475     }
476     
477     private void checkNull(final Object o, final String param)
478     {
479         if (o == null)
480         {
481             throw new NullPointerException(param + " can not be null.");
482         }
483     }
484     
485     private void sendSourceNotFound(FacesContext context, String message)
486     {
487         HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
488         try
489         {
490             context.responseComplete();
491             response.sendError(HttpServletResponse.SC_NOT_FOUND, message);
492         }
493         catch (IOException ioe)
494         {
495             throw new FacesException(ioe);
496         }
497     }
498     
499     public void setViewHandlerSupport(ViewHandlerSupport viewHandlerSupport)
500     {
501         _viewHandlerSupport = viewHandlerSupport;
502     }    
503     
504     protected ViewHandlerSupport getViewHandlerSupport()
505     {
506         return getViewHandlerSupport(FacesContext.getCurrentInstance());
507     }
508 
509     protected ViewHandlerSupport getViewHandlerSupport(FacesContext context)
510     {
511         if (_viewHandlerSupport == null)
512         {
513             _viewHandlerSupport = new DefaultViewHandlerSupport(context);
514         }
515         return _viewHandlerSupport;
516     }
517 }