/* * Copyright 2004-2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.shale.tiles; import java.io.IOException; import java.text.MessageFormat; import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; import javax.faces.FacesException; import javax.faces.application.ViewHandler; import javax.faces.component.UIViewRoot; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.tiles.ComponentContext; import org.apache.tiles.ComponentDefinition; import org.apache.tiles.DefinitionsFactoryException; import org.apache.tiles.NoSuchDefinitionException; import org.apache.tiles.TilesUtil; import org.apache.tiles.TilesContext; import org.apache.tiles.context.servlet.ServletTilesContext; /** * This view handler strips the suffix off of the view ID and looks * for a tile whose name matches the resulting string. For example, if the * view ID is /tiles/test.jsp, this view handler will look for a tile named * /tiles/test. If the tile is found, it is rendered; otherwise, this view handler * delegates to the default JSF view handler. *

* To render a tile, this view handler first locates the tile by name. Then it * creates or accesses the Tile Context, and stores the tile's attributes * in the context. Finally, it dispatches the request to the tile's layout * by calling JSF's ExternalContext.dispatch(). Layouts typically * contain <tiles:insert> tags that include dynamic content. *

* If the request does not reference a tile, this view handler delegates * view rendering to the default view handler. That means that URLs like this: * http://localhost:8080/example/index.faces will work as * expected. *

* Most of the methods in this class simply delegate to the default view * handler, which JSF passes to this view handler's constructor. The only * method that has a meaningful implementation is void * renderView(FacesContext, UIViewRoot), which renders the current * view in accordance with the algorithm discussed above. * * Note: This Tiles view handler is tied to the standalone * version of Tiles, which resides in the Struts sandbox. This view handler * will not work with Struts Tiles. */ public class TilesViewHandler extends ViewHandler { // ------------------------------------------------------------- Constructor /** *

Stores the reference to the default view handler for later use.

* * @param defaultViewHandler The default view handler */ public TilesViewHandler(ViewHandler defaultViewHandler) { this.defaultViewHandler = defaultViewHandler; } // -------------------------------------------------------- Static Variables /** *

MessageFormat used to perform parameter substitution.

*/ private MessageFormat format = new MessageFormat(""); /** *

Log instance for this class.

*/ private static final Log log = LogFactory.getLog( TilesViewHandler.class.getName()); /** *

Message resources for this class.

*/ private static ResourceBundle bundle = ResourceBundle.getBundle("org.apache.shale.tiles.Bundle", Locale.getDefault(), TilesViewHandler.class.getClassLoader()); /** *

The default JSF view handler.

*/ private ViewHandler defaultViewHandler = null; // ----------------------------------------------------- ViewHandler Methods /** *

Render a view according to the algorithm described in this class's * description: Based on the view Id of the viewToRender, * this method either renders a tile or delegates rendering to the default * view handler, which takes care of business as usual.

* * @param facesContext The faces context object for this request * @param viewToRender The view that we're rendering */ public void renderView(FacesContext facesContext, UIViewRoot viewToRender) throws IOException, FacesException { String viewId = viewToRender.getViewId(); String tileName = getTileName(viewId); ComponentDefinition tile = getTile(tileName); if (log.isDebugEnabled()) { String message = null; try { message = bundle.getString("tiles.renderingView"); } catch (MissingResourceException e) { message = "Rendering view {0}, looking for tile {1}"; } synchronized(format) { format.applyPattern(message); message = format.format(new Object[] { viewId, tileName }); } log.debug(message); } if (tile != null) { if (log.isDebugEnabled()) { String message = null; try { message = bundle.getString("tiles.dispatchingToTile"); } catch (MissingResourceException e) { message = "Dispatching to tile {0}"; } synchronized(format) { format.applyPattern(message); message = format.format(new Object[] { tileName }); } log.debug(message); } dispatchToTile(facesContext.getExternalContext(), tile); } else { if (log.isDebugEnabled()) { String message = null; try { message = bundle.getString("tiles.dispatchingToViewHandler"); } catch (MissingResourceException e) { message = "Dispatching {0} to the default view handler"; } synchronized(format) { format.applyPattern(message); message = format.format(new Object[] { viewId }); } log.debug(message); } defaultViewHandler.renderView(facesContext, viewToRender); } } /** *

Pass through to the default view handler.

* */ public UIViewRoot createView(FacesContext context, String viewId) { return defaultViewHandler.createView(context, viewId); } /** *

Pass through to the default view handler.

* */ public Locale calculateLocale(FacesContext context) { return defaultViewHandler.calculateLocale(context); } /** *

Pass through to the default view handler.

* */ public String calculateRenderKitId(FacesContext context) { return defaultViewHandler.calculateRenderKitId(context); } /** *

Pass through to the default view handler.

* */ public String getActionURL(FacesContext context, String viewId) { return defaultViewHandler.getActionURL(context, viewId); } /** *

Pass through to the default view handler.

* */ public String getResourceURL(FacesContext context, String path) { return defaultViewHandler.getResourceURL(context, path); } /** *

Pass through to the default view handler.

* */ public UIViewRoot restoreView(FacesContext context, String viewId) { return defaultViewHandler.restoreView(context, viewId); } /** *

Pass through to the default view handler.

* */ public void writeState(FacesContext context) throws IOException { defaultViewHandler.writeState(context); } // --------------------------------------------------------- Private Methods /** *

Looks up a tile, given a name. If the tile does not exist, and the * name begins with a slash ('/'), look for a tile * without the slash. If no tile is found, return null.

* * @param name The tile to lookup */ private ComponentDefinition getTile(String name) { if (name == null) return null; ExternalContext externalContext = FacesContext.getCurrentInstance() .getExternalContext(); Object request = externalContext.getRequest(); Object context = externalContext.getContext(); ComponentDefinition tile = null; if ((request instanceof HttpServletRequest) && (context instanceof ServletContext)) { HttpServletRequest servletRequest = (HttpServletRequest) request; ServletContext servletContext = (ServletContext) context; try { tile = TilesUtil.getDefinition(name, new ServletTilesContext(servletContext, servletRequest)); } catch (NoSuchDefinitionException nsex) { /* not here */ } catch (DefinitionsFactoryException dex) { /* not here */ } } else { if (log.isDebugEnabled()) { log.debug("Not a servlet request, skipping tile lookup"); } } return tile; } /** *

Given a view ID, returns the name of the corresponding tile. For * example, for a view ID of /tiles/example/main.jsp, the tile name * returned by this method would be /tiles/example/main.

* * @param viewId The view ID */ private String getTileName(String viewId) { int suffixIndex = viewId.lastIndexOf('.'); return suffixIndex != -1 ? viewId.substring(0, suffixIndex) : viewId; } /** *

Dispatches to a tile's layout. Layouts typically contain * <tiles:insert> tags that include content, so dispatching * to the tile's layout will automatically build the tile.

*

* Before dispatching to the tile, this method sets up the Tile * context.

* * @param externalContext The JSF external context * @param tile The tile definition */ private void dispatchToTile(ExternalContext externalContext, ComponentDefinition tile) throws java.io.IOException { HttpServletRequest servletRequest = (HttpServletRequest) externalContext.getRequest(); ServletContext servletContext = (ServletContext) externalContext.getContext(); TilesContext tilesContext = new ServletTilesContext(servletContext, servletRequest); ComponentContext tileContext = ComponentContext.getContext(tilesContext); if (tileContext == null) { tileContext = new ComponentContext(tile.getAttributes()); ComponentContext.setContext(tileContext, tilesContext); } else tileContext.addMissing(tile.getAttributes()); // dispatch to the tile's layout externalContext.dispatch(tile.getPath()); } }