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 javax.faces.application;
20  
21  import java.io.IOException;
22  import java.io.UnsupportedEncodingException;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Locale;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import javax.faces.FacesException;
30  import javax.faces.component.UIViewRoot;
31  import javax.faces.context.ExternalContext;
32  import javax.faces.context.FacesContext;
33  import javax.faces.view.ViewDeclarationLanguage;
34  
35  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
36  
37  /**
38   * A ViewHandler manages the component-tree-creation and component-tree-rendering parts of a request lifecycle (ie
39   * "create view", "restore view" and "render response").
40   * <p>
41   * A ViewHandler is responsible for generating the component tree when a new view is requested; see method "createView".
42   * <p>
43   * When the user performs a "postback", ie activates a UICommand component within a view, then the ViewHandler is
44   * responsible for recreating a view tree identical to the one used previously to render that view; see method
45   * "restoreView".
46   * <p>
47   * And the ViewHandler is also responsible for rendering the final output to be sent to the user by invoking the
48   * rendering methods on components; see method "renderView".
49   * <p>
50   * This class also isolates callers from the underlying request/response system. In particular, this class does not
51   * explicitly depend upon the javax.servlet apis. This allows JSF to be used on servers that do not implement the
52   * servlet API (for example, plain CGI).
53   * <p>
54   * Examples:
55   * <ul>
56   * <li>A JSP ViewHandler exists for using "jsp" pages as the presentation technology. This class then works together
57   * with a taghandler class and a jsp servlet class to implement the methods on this abstract class definition.
58   * <li>A Facelets ViewHandler instead uses an xml file to define the components and non-component data that make up a
59   * specific view.
60   * </ul>
61   * Of course there is no reason why the "template" needs to be a textual file. A view could be generated based on data
62   * in a database, or many other mechanisms.
63   * <p>
64   * This class is expected to be invoked via the concrete implementation of {@link javax.faces.lifecycle.Lifecycle}.
65   * <p>
66   * For the official specification for this class, see <a
67   * href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/index.html">JSF Specification</a>.
68   */
69  public abstract class ViewHandler
70  {
71      public static final String CHARACTER_ENCODING_KEY = "javax.faces.request.charset";
72      public static final String DEFAULT_FACELETS_SUFFIX = ".xhtml";
73      public static final String DEFAULT_SUFFIX = ".xhtml .view.xml .jsp";
74      
75      /**
76       * Indicate the default suffixes, separated by spaces to derive the default file URI 
77       * used by JSF to create views and render pages. 
78       */
79      @JSFWebConfigParam(defaultValue=".xhtml .view.xml .jsp", since="1.1", group="viewhandler")
80      public static final String DEFAULT_SUFFIX_PARAM_NAME = "javax.faces.DEFAULT_SUFFIX";
81      
82      /**
83       * The default extension used to handle facelets pages.
84       */
85      @JSFWebConfigParam(defaultValue=".xhtml", since="2.0", group="viewhandler")
86      public static final String FACELETS_SUFFIX_PARAM_NAME = "javax.faces.FACELETS_SUFFIX";
87      
88      /**
89       * Set of extensions handled by facelets, separated by ';'.
90       */
91      @JSFWebConfigParam(since="2.0", group="viewhandler")
92      public static final String FACELETS_VIEW_MAPPINGS_PARAM_NAME = "javax.faces.FACELETS_VIEW_MAPPINGS";
93      // TODO: Notify EG on that last constant. Using the Facelets' param as well for backward compatiblity is 
94      //       silly. If an application uses Facelets then they'll be using facelets.jar. Once they chose to 
95      //       remove that JAR, they ought to be aware that some changes could be needed, like fixing their 
96      //       context-param. -= Simon Lessard =-
97  
98      @JSFWebConfigParam(since="2.2")
99      public static final java.lang.String DISABLE_FACELET_JSF_VIEWHANDLER_PARAM_NAME = 
100         "DISABLE_FACELET_JSF_VIEWHANDLER";
101     
102     /**
103      * Define the default buffer size value passed to ExternalContext.setResponseBufferResponse() and in a
104      * servlet environment to HttpServletResponse.setBufferSize().
105      */
106     @JSFWebConfigParam(since = "2.0", alias = "facelets.BUFFER_SIZE", classType = "java.lang.Integer",
107             tags = "performance", defaultValue="1024",
108             desc = "Define the default buffer size value passed to ExternalContext.setResponseBufferResponse() and in "
109                    + "a servlet environment to HttpServletResponse.setBufferSize()")
110     public static final java.lang.String FACELETS_BUFFER_SIZE_PARAM_NAME = "javax.faces.FACELETS_BUFFER_SIZE";
111     
112     /**
113      * Set of class names, separated by ';', implementing TagDecorator interface, used to transform
114      * a view definition in a facelet abstract syntax tree, that is used later to generate a component tree.
115      */
116     @JSFWebConfigParam(since = "2.0", alias = "facelets.DECORATORS")
117     public static final java.lang.String FACELETS_DECORATORS_PARAM_NAME = "javax.faces.FACELETS_DECORATORS";
118     
119     /**
120      * Set of .taglib.xml files, separated by ';' that should be loaded by facelet engine.
121      */
122     @JSFWebConfigParam(since = "2.0",
123             desc = "Set of .taglib.xml files, separated by ';' that should be loaded by facelet engine.",
124             alias = "facelets.LIBRARIES")
125     public static final java.lang.String FACELETS_LIBRARIES_PARAM_NAME = "javax.faces.FACELETS_LIBRARIES";
126     
127     /**
128      * Define the period used to refresh the facelet abstract syntax tree from the view definition file. 
129      *
130      * <p>By default is infinite (no active).</p>
131      */
132     @JSFWebConfigParam(since = "2.0", defaultValue = "-1", alias = "facelets.REFRESH_PERIOD",
133             classType = "java.lang.Long", tags = "performance")
134     public static final java.lang.String FACELETS_REFRESH_PERIOD_PARAM_NAME = "javax.faces.FACELETS_REFRESH_PERIOD";
135 
136     /**
137      * Skip comments found on a facelet file.
138      */
139     @JSFWebConfigParam(since = "2.0", alias = "facelets.SKIP_COMMENTS")
140     public static final java.lang.String FACELETS_SKIP_COMMENTS_PARAM_NAME = "javax.faces.FACELETS_SKIP_COMMENTS";
141     
142     /**
143      * @since JSF 1.2
144      */
145     public String calculateCharacterEncoding(FacesContext context)
146     {
147         String encoding = null;
148         ExternalContext externalContext = context.getExternalContext();
149         String contentType = externalContext.getRequestHeaderMap().get("Content-Type");
150         int indexOf = contentType == null ? -1 : contentType.indexOf("charset");
151         if (indexOf != -1)
152         {
153             String tempEnc = contentType.substring(indexOf); // charset=UTF-8
154             encoding = tempEnc.substring(tempEnc.indexOf("=") + 1); // UTF-8
155             if (encoding.length() == 0)
156             {
157                 encoding = null;
158             }
159         }
160         if (encoding == null)
161         {
162             boolean sessionAvailable = externalContext.getSession(false) != null;
163             if (sessionAvailable)
164             {
165                 Object sessionParam = externalContext.getSessionMap().get(CHARACTER_ENCODING_KEY);
166                 if (sessionParam != null)
167                 {
168                     encoding = sessionParam.toString();
169                 }
170             }
171         }
172 
173         return encoding;
174     }
175 
176     /**
177      * Return the Locale object that should be used when rendering this view to the current user.
178      * <p>
179      * Some request protocols allow an application user to specify what locale they prefer the response to be in. For
180      * example, HTTP requests can specify the "accept-language" header.
181      * <p>
182      * Method {@link javax.faces.application.Application#getSupportedLocales()} defines what locales this JSF
183      * application is capable of supporting.
184      * <p>
185      * This method should match such sources of data up and return the Locale object that is the best choice for
186      * rendering the current application to the current user.
187      */
188     public abstract Locale calculateLocale(FacesContext context);
189 
190     /**
191      * Return the id of an available render-kit that should be used to map the JSF components into user presentation.
192      * <p>
193      * The render-kit selected (eg html, xhtml, pdf, xul, ...) may depend upon the user, properties associated with the
194      * request, etc.
195      */
196     public abstract String calculateRenderKitId(FacesContext context);
197 
198     /**
199      * Build a root node for a component tree.
200      * <p>
201      * When a request is received, this method is called if restoreView returns null, ie this is not a "postback". In
202      * this case, a root node is created and then renderView is invoked. It is the responsibility of the renderView
203      * method to build the full component tree (ie populate the UIViewRoot with descendant nodes).
204      * <p>
205      * This method is also invoked when navigation occurs from one view to another, where the viewId passed is the id of
206      * the new view to be displayed. Again it is the responsibility of renderView to then populate the viewroot with
207      * descendants.
208      * <p>
209      * The locale and renderKit settings are inherited from the current UIViewRoot that is configured before this method
210      * is called. That means of course that they do NOT get set for GET requests, including navigation that has the
211      * redirect flag set.
212      */
213     public abstract UIViewRoot createView(FacesContext context, String viewId);
214 
215     /**
216      * @param context
217      * @param input
218      * @return
219      * 
220      * @since 2.0
221      */
222     public String deriveViewId(FacesContext context, String input)
223     {
224         //The default implementation of this method simply returns rawViewId unchanged.
225         return input;
226     }
227     
228     /**
229      * 
230      * @param context
231      * @param rawViewId
232      * @return
233      * @since 2.1
234      */
235     public String deriveLogicalViewId(FacesContext context, String rawViewId)
236     {
237         return rawViewId;
238     }
239 
240     /**
241      * Returns a URL, suitable for encoding and rendering, that (if activated) will cause the JSF
242      * request processing lifecycle for the specified viewId to be executed
243      */
244     public abstract String getActionURL(FacesContext context, String viewId);
245 
246     /**
247      * Return a JSF action URL derived from the viewId argument that is suitable to be used as
248      * the target of a link in a JSF response. Compiliant implementations must implement this method
249      * as specified in section JSF.7.5.2. The default implementation simply calls through to
250      * getActionURL(javax.faces.context.FacesContext, java.lang.String), passing the arguments context and viewId.
251      * 
252      * @param context
253      * @param viewId
254      * @param parameters
255      * @param includeViewParams
256      * @return
257      * 
258      * @since 2.0
259      */
260     public String getBookmarkableURL(FacesContext context, String viewId, Map<String, List<String>> parameters,
261                                      boolean includeViewParams)
262     {
263         return getActionURL(context, viewId);
264     }
265 
266     /**
267      * Return the ViewDeclarationLanguage instance used for this ViewHandler  instance.
268      * <P>
269      * The default implementation of this method returns null.
270      * 
271      * @param context
272      * @param viewId
273      * @return
274      * 
275      * @since 2.0
276      */
277     public ViewDeclarationLanguage getViewDeclarationLanguage(FacesContext context, String viewId)
278     {
279         // TODO: In some places like RestoreViewExecutor, we are calling deriveViewId
280         // TODO: after call restoreViewSupport.calculateViewId
281         // Maybe this method should be called from here, because it is supposed that
282         // calculateViewId "calculates the view id!"
283         
284         // here we return null to support pre jsf 2.0 ViewHandlers (e.g. com.sun.facelets.FaceletViewHandler),
285         // because they don't provide any ViewDeclarationLanguage,
286         // but in the default implementation (ViewHandlerImpl) we return vdlFactory.getViewDeclarationLanguage(viewId)
287         return null;
288     }
289 
290     /**
291      * Return a JSF action URL derived from the viewId argument that is suitable to be used by
292      * the NavigationHandler to issue a redirect request to the URL using a NonFaces request.
293      * Compiliant implementations must implement this method as specified in section JSF.7.5.2.
294      * The default implementation simply calls through to
295      * getActionURL(javax.faces.context.FacesContext, java.lang.String), passing the arguments context and viewId.
296      * 
297      * @param context
298      * @param viewId
299      * @param parameters
300      * @param includeViewParams
301      * @return
302      * 
303      * @since 2.0
304      */
305     public String getRedirectURL(FacesContext context, String viewId, Map<String, List<String>> parameters,
306                                  boolean includeViewParams)
307     {
308         return getActionURL(context, viewId);
309     }
310 
311     /**
312      * Returns a URL, suitable for encoding and rendering, that (if activated)
313      * will retrieve the specified web application resource.
314      */
315     public abstract String getResourceURL(FacesContext context, String path);
316 
317     /**
318      * Initialize the view for the request processing lifecycle.
319      * <P>
320      * This method must be called at the beginning of the Restore View Phase of the Request
321      * Processing Lifecycle. It is responsible for performing any per-request initialization
322      * necessary to the operation of the lifycecle.
323      * <P>
324      * The default implementation must perform the following actions. If
325      * ExternalContext.getRequestCharacterEncoding() returns null, call
326      * calculateCharacterEncoding(javax.faces.context.FacesContext) and pass the result,
327      * if non-null, into the ExternalContext.setRequestCharacterEncoding(java.lang.String) method.
328      * If ExternalContext.getRequestCharacterEncoding() returns non-null take no action.
329      * 
330      * @since JSF 1.2
331      */
332     public void initView(FacesContext context) throws FacesException
333     {
334         String encoding = this.calculateCharacterEncoding(context);
335         if (encoding != null)
336         {
337             try
338             {
339                 context.getExternalContext().setRequestCharacterEncoding(encoding);
340             }
341             catch (UnsupportedEncodingException uee)
342             {
343                 throw new FacesException(uee);
344             }
345         }
346     }
347 
348     /**
349      *  Perform whatever actions are required to render the response view to the
350      *  response object associated with the current FacesContext.
351      *  <P>
352      *  Otherwise, the default implementation must obtain a reference to the
353      *  ViewDeclarationLanguage for the viewId of the argument viewToRender and call its
354      *  ViewDeclarationLanguage.renderView(javax.faces.context.FacesContext, javax.faces.component.UIViewRoot)
355      *  method, returning the result and not swallowing any exceptions thrown by that method.
356      */
357     public abstract void renderView(FacesContext context, UIViewRoot viewToRender) throws IOException, FacesException;
358 
359     /**
360      * Perform whatever actions are required to restore the view associated with the
361      * specified FacesContext and viewId. It may delegate to the restoreView of the
362      * associated StateManager to do the actual work of restoring the view. If there
363      * is no available state for the specified viewId, return null.
364      * <P>
365      * Otherwise, the default implementation must obtain a reference to the
366      * ViewDeclarationLanguage for this viewId and call its
367      * ViewDeclarationLanguage.restoreView(javax.faces.context.FacesContext, java.lang.String)
368      * method, returning the result and not swallowing any exceptions thrown by that method.
369      */
370     public abstract UIViewRoot restoreView(FacesContext context, String viewId);
371 
372     /**
373      * Take any appropriate action to either immediately write out the current state information
374      * (by calling StateManager.writeState(javax.faces.context.FacesContext, java.lang.Object),
375      * or noting where state information should later be written.
376      * <P>
377      * This method must do nothing if the current request is an Ajax request. When responding
378      * to Ajax requests, the state is obtained by calling StateManager.getViewState(javax.faces.context.FacesContext)
379      * and then written into the Ajax response during
380      * final encoding (UIViewRoot.encodeEnd(javax.faces.context.FacesContext).
381      */
382     public abstract void writeState(FacesContext context) throws IOException;
383 
384     /**
385      * @since 2.2
386      * @param urlPattern 
387      */
388     public void addProtectedView(String urlPattern)
389     {
390     }
391     
392     /**
393      * @since 2.2
394      * @param urlPattern 
395      */
396     public boolean removeProtectedView(String urlPattern)
397     {
398         return false;
399     }
400 
401     /**
402      * @since 2.2
403      * @return 
404      */
405     public Set<String> getProtectedViewsUnmodifiable()
406     {
407         Set<String> set = Collections.emptySet();
408         return Collections.unmodifiableSet(set);
409     }
410 }