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