2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to you under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shale.tiles;
19  
20  import java.io.IOException;
21  import java.text.MessageFormat;
22  import java.util.Locale;
23  import java.util.MissingResourceException;
24  import java.util.ResourceBundle;
25  
26  import javax.faces.FacesException;
27  import javax.faces.application.ViewHandler;
28  import javax.faces.component.UIViewRoot;
29  import javax.faces.context.ExternalContext;
30  import javax.faces.context.FacesContext;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.tiles.ComponentContext;
35  import org.apache.tiles.ComponentDefinition;
36  import org.apache.tiles.DefinitionsFactoryException;
37  import org.apache.tiles.NoSuchDefinitionException;
38  import org.apache.tiles.TilesRequestContext;
39  import org.apache.tiles.TilesUtil;
40  import org.apache.tiles.context.BasicTilesContextFactory;
41  
42  /***
43   * This view handler strips the suffix off of the view ID and looks
44   * for a tile whose name matches the resulting string. For example, if the
45   * view ID is /tiles/test.jsp, this view handler will look for a tile named
46   * /tiles/test. If the tile is found, it is rendered; otherwise, this view handler 
47   * delegates to the default JSF view handler.
48   * <p/>
49   * To render a tile, this view handler first locates the tile by name. Then it
50   * creates or accesses the Tile Context, and stores the tile's attributes
51   * in the context. Finally, it dispatches the request to the tile's layout
52   * by calling JSF's <code>ExternalContext.dispatch()</code>. Layouts typically
53   * contain &lt;tiles:insert&gt; tags that include dynamic content.
54   * <p/>
55   * If the request does not reference a tile, this view handler delegates
56   * view rendering to the default view handler. That means that URLs like this:
57   * <code>http://localhost:8080/example/index.faces</code> will work as 
58   * expected.
59   *<p/>
60   * Most of the methods in this class simply delegate to the default view
61   * handler, which JSF passes to this view handler's constructor. The only
62   * method that has a meaningful implementation is <code>void 
63   * renderView(FacesContext, UIViewRoot)</code>, which renders the current
64   * view in accordance with the algorithm discussed above.
65   *
66   * <strong>Note:</strong> This Tiles view handler is tied to the standalone 
67   * version of Tiles, which resides in the Struts sandbox. This view handler
68   * will not work with Struts Tiles.
69   */
70  public class TilesViewHandler extends ViewHandler {
71  
72  
73     // ------------------------------------------------------------- Constructor
74  
75  
76     /***
77      * <p>Stores the reference to the default view handler for later use.</p>
78      * 
79      * @param defaultViewHandler The default view handler
80      */
81     public TilesViewHandler(ViewHandler defaultViewHandler) {
82        this.defaultViewHandler = defaultViewHandler;
83     }
84  
85  
86     // -------------------------------------------------------- Static Variables
87  
88      
89     /***
90      * <p><code>MessageFormat</code> used to perform parameter substitution.</p>
91      */
92     private MessageFormat format = new MessageFormat("");
93  
94  
95     /***
96      * <p>Log instance for this class.</p>
97      */
98     private static final Log log = LogFactory.getLog(
99                                              TilesViewHandler.class.getName());
100    /***
101     * <p>Message resources for this class.</p>
102     */
103    private static ResourceBundle bundle =
104       ResourceBundle.getBundle("org.apache.shale.tiles.Bundle",
105                                Locale.getDefault(),
106                                TilesViewHandler.class.getClassLoader());
107 
108     /***
109      * <p>The default JSF view handler.</p>
110      */
111    private ViewHandler defaultViewHandler = null;
112 
113 
114    // ----------------------------------------------------- ViewHandler Methods
115 
116 
117    /***
118     * <p>Render a view according to the algorithm described in this class's
119     * description: Based on the view Id of the <code>viewToRender</code>, 
120     * this method either renders a tile or delegates rendering to the default 
121     * view handler, which takes care of business as usual.</p>
122     *
123     * @param facesContext The faces context object for this request
124     * @param viewToRender The view that we're rendering
125     */
126    public void renderView(FacesContext facesContext, UIViewRoot viewToRender) 
127                                         throws IOException, FacesException {
128       String viewId = viewToRender.getViewId();
129       String tileName = getTileName(viewId);
130       ComponentDefinition tile = getTile(tileName);
131 
132       if (log.isDebugEnabled()) {
133          String message = null;
134          try {
135              message = bundle.getString("tiles.renderingView");
136          } catch (MissingResourceException e) {
137              message = "Rendering view {0}, looking for tile {1}";
138          }
139          synchronized(format) {
140             format.applyPattern(message);
141             message = format.format(new Object[] { viewId, tileName });
142          }
143          log.debug(message);
144       }
145 
146       if (tile != null) {
147          if (log.isDebugEnabled()) {
148             String message = null;
149             try {
150                 message = bundle.getString("tiles.dispatchingToTile");
151             } catch (MissingResourceException e) {
152                 message = "Dispatching to tile {0}";
153             }
154             synchronized(format) {
155                format.applyPattern(message);
156                message = format.format(new Object[] { tileName });
157             }
158             log.debug(message);
159          }
160          dispatchToTile(facesContext.getExternalContext(), tile);
161       }
162       else {
163          if (log.isDebugEnabled()) {
164             String message = null;
165             try {
166                 message = bundle.getString("tiles.dispatchingToViewHandler");
167             } catch (MissingResourceException e) {
168                 message = "Dispatching {0} to the default view handler";
169             }
170             synchronized(format) {
171                format.applyPattern(message);
172                message = format.format(new Object[] { viewId });
173             }
174             log.debug(message);
175          }
176          defaultViewHandler.renderView(facesContext, viewToRender);
177       }
178    }
179 
180    /***
181     * <p>Pass through to the default view handler.</p>
182     * 
183     */
184    public UIViewRoot createView(FacesContext context, String viewId) {
185       return defaultViewHandler.createView(context, viewId);
186    }
187 
188 
189    /***
190     * <p>Pass through to the default view handler.</p>
191     * 
192     */
193    public Locale calculateLocale(FacesContext context) {
194       return defaultViewHandler.calculateLocale(context);
195    }
196     
197 
198    /***
199     * <p>Pass through to the default view handler.</p>
200     * 
201     */
202    public String calculateRenderKitId(FacesContext context) {
203       return defaultViewHandler.calculateRenderKitId(context);
204    }
205     
206 
207    /***
208     * <p>Pass through to the default view handler.</p>
209     * 
210     */
211    public String getActionURL(FacesContext context, String viewId) {
212       return defaultViewHandler.getActionURL(context, viewId);
213    }
214     
215 
216    /***
217     * <p>Pass through to the default view handler.</p>
218     * 
219     */
220    public String getResourceURL(FacesContext context, String path) {
221       return defaultViewHandler.getResourceURL(context, path);
222    }
223     
224 
225    /***
226     * <p>Pass through to the default view handler.</p>
227     * 
228     */
229    public UIViewRoot restoreView(FacesContext context, String viewId) {
230       return defaultViewHandler.restoreView(context, viewId);
231    }
232     
233 
234    /***
235     * <p>Pass through to the default view handler.</p>
236     * 
237     */
238    public void writeState(FacesContext context) throws IOException {
239       defaultViewHandler.writeState(context);
240    }
241 
242 
243    // --------------------------------------------------------- Private Methods
244 
245 
246    /***
247     * <p>Looks up a tile, given a name. If the tile does not exist, and the
248     * <code>name</code> begins with a slash ('/'), look for a tile
249     * without the slash. If no tile is found, return <code>null</code>.</p>
250     * 
251     * @param name The tile to lookup
252     */
253    private ComponentDefinition getTile(String name) {
254       if (name == null) 
255          return null;
256 
257       ExternalContext externalContext = FacesContext.getCurrentInstance()
258                                                     .getExternalContext();
259       Object request = externalContext.getRequest();
260       Object context = externalContext.getContext();
261       Object response = externalContext.getResponse();
262       ComponentDefinition tile = null;
263       try {
264         TilesRequestContext tilesContext =
265             new BasicTilesContextFactory().createRequestContext(context,
266             request, response);
267         tile = TilesUtil.getDefinition(name, tilesContext);
268       } catch (NoSuchDefinitionException nsex) {
269           log.error("Couldn't find Tiles definition.", nsex);
270       } catch (DefinitionsFactoryException dex) {
271           log.error("Tiles error", dex);
272       }
273       return tile;
274 
275    }
276 
277    /***
278     * <p>Given a view ID, returns the name of the corresponding tile. For 
279     * example, for a view ID of /tiles/example/main.jsp, the tile name 
280     * returned by this method would be /tiles/example/main.</p>
281     * 
282     * @param viewId The view ID
283     */
284    private String getTileName(String viewId) {
285       int suffixIndex = viewId.lastIndexOf('.');
286       return suffixIndex != -1 ? viewId.substring(0, suffixIndex)
287                                : viewId;
288    }
289 
290    /***
291     * <p>Dispatches to a tile's layout. Layouts typically contain
292     * &lt;tiles:insert&gt; tags that include content, so dispatching
293     * to the tile's layout will automatically build the tile.</p>
294     * <p>
295     * Before dispatching to the tile, this method sets up the Tile
296     * context.</p>
297     * 
298     * @param externalContext The JSF external context
299     * @param tile The tile definition
300     */
301    private void dispatchToTile(ExternalContext externalContext,
302                                ComponentDefinition tile) 
303                                throws java.io.IOException {
304       Object request = externalContext.getRequest();
305       Object context = externalContext.getContext();
306       Object response = externalContext.getResponse();
307       TilesRequestContext tilesContext =
308           new BasicTilesContextFactory().createRequestContext(context,
309           request, response);
310       ComponentContext tileContext = ComponentContext.getContext(tilesContext);
311       if (tileContext == null) {
312          tileContext = new ComponentContext(tile.getAttributes());
313          ComponentContext.setContext(tileContext, tilesContext);
314       }
315       else
316          tileContext.addMissing(tile.getAttributes());
317    
318       // dispatch to the tile's layout
319       externalContext.dispatch(tile.getPath());
320    }
321 }