Here is a simple tiles servlet I wrote to help my tiles and jsf integration. I got sick of writing 2 jsps for each body so this is the result. I'm not much of a tiles or jsf expert but this servlet seems to work for my simple cases.
Here is the servlet.
DispatchTilesServlet:
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.tiles.ComponentContext;
import org.apache.struts.tiles.ComponentDefinition;
import org.apache.struts.tiles.Controller;
import org.apache.struts.tiles.DefinitionsFactoryConfig;
import org.apache.struts.tiles.DefinitionsFactoryException;
import org.apache.struts.tiles.FactoryNotFoundException;
import org.apache.struts.tiles.TilesUtil;
public class DispatchTilesServlet extends HttpServlet {
/** Commons Logging instance. */
protected static Log log = LogFactory.getLog(DispatchTilesServlet.class);
public static final String FALLBACK_EXTENTION_ATTR = "DispatchTilesServlet.FALLBACK_EXTENTION";
public static final String TILE_DEFINITION_DELIMITER = "DispatchTilesServlet.TILE_DEFINITION_DELIMITER";
public static final String DEFAULT_FALLBACK_EXTENTION_ATTR = ".jsp";
public static final String DEFAULT_TILE_DEFINITION_DELIMITER = ".";
public String tileDefinitionDelimiter = DEFAULT_TILE_DEFINITION_DELIMITER;
public String fallbackExtention = DEFAULT_FALLBACK_EXTENTION_ATTR;
/**
* Initialize this servlet
*
* @exception ServletException
* if we cannot configure ourselves correctly
*/
public void init(ServletConfig config) throws ServletException {
super.init(config);
if(config.getServletContext().getAttribute(FALLBACK_EXTENTION_ATTR) != null) {
fallbackExtention = config.getServletContext().getAttribute(FALLBACK_EXTENTION_ATTR).toString();
}
if(config.getServletContext().getAttribute(TILE_DEFINITION_DELIMITER) != null) {
tileDefinitionDelimiter = config.getServletContext().getAttribute(TILE_DEFINITION_DELIMITER).toString();
}
if (log.isInfoEnabled())
log.debug("Start Tiles initialization");
// Create tiles definitions config object
DefinitionsFactoryConfig factoryConfig = new DefinitionsFactoryConfig();
// Get init parameters from web.xml files
try {
Enumeration enum = config.getInitParameterNames();
HashMap map = new HashMap();
while (enum.hasMoreElements()) {
String key = (String) enum.nextElement();
map.put(key, config.getInitParameter(key));
}
factoryConfig.populate(map);
} catch (Exception ex) {
String msg = "Can't populate DefinitionsFactoryConfig class from 'web.xml'";
if (log.isErrorEnabled()) log.error(msg, ex);
throw new ServletException(msg + ex.getMessage());
}
try {
if (log.isInfoEnabled())
log.debug("Try to load Tiles factory");
TilesUtil.createDefinitionsFactory(getServletContext(), factoryConfig);
if (log.isInfoEnabled())
log.debug("Tiles Factory successfully loaded");
} catch (DefinitionsFactoryException ex) {
if (log.isErrorEnabled())
log.error("Tiles Factory load fail !", ex);
throw new ServletException(ex);
}
}
/**
* @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
//obtain the request uri and eliminate the context path and any extention it may have plus leading '/'
String uri = StringUtils.substringAfter(request.getRequestURI(), request.getContextPath()+"/");
StringBuffer buffer = new StringBuffer(uri);
//eliminate the extention
int extIdx = uri.lastIndexOf('.');
if (extIdx != -1) {
buffer.replace(extIdx, uri.length(), "");
}
uri = buffer.toString();
//find a component definition from the remaining uri replacing the '/' with the specified delimiter
ComponentDefinition definition = TilesUtil.getDefinition(StringUtils.replace(uri, "/", tileDefinitionDelimiter), request, getServletContext());
boolean setContext = false;
if(definition == null) {
//if definition not found then replace delimiter and append fallback extention
uri = "/"+uri+fallbackExtention;
} else {
//get the path of the found definition
uri = definition.getPath();
ComponentContext tileContext = ComponentContext.getContext(request);
if (tileContext == null) {
//if no current context then create a new one
tileContext = new ComponentContext(definition.getAttributes());
ComponentContext.setContext(tileContext, request);
setContext = true;
}
//get and execute a controller if it exists
Controller controller = definition.getOrCreateController();
if (controller != null)
controller.perform(tileContext, request, response, getServletContext());
}
//forward to the tile path specified for it's descriptor or forward to the fallbackurl if it doesn't exist
RequestDispatcher dispatcher = request.getRequestDispatcher(uri);
dispatcher.forward(request, response);
if(setContext) {
//clear tile context if we created one.
ComponentContext.setContext(null, request);
}
} catch (FactoryNotFoundException e) {
throw new ServletException(e.getMessage(), e);
} catch (DefinitionsFactoryException e) {
throw new ServletException(e.getMessage(), e);
} catch (InstantiationException e) {
throw new ServletException(e.getMessage(), e);
}
}
}
Here is an example web.xml configuration
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.tile</param-value>
</context-param>
<servlet>
<servlet-name>FacesServlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>TilesServlet</servlet-name>
<servlet-class>DispatchTilesServlet</servlet-class>
<init-param>
<param-name>definitions-config</param-name>
<param-value>/WEB-INF/tiles-defs.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>TilesServlet</servlet-name>
<url-pattern>*.tile</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>FacesServlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
Example tile-def.xml
<tiles-definitions>
<definition name="secure.index" path="/WEB-INF/jsps/layout/main.jsp">
<put name="title" value="Main Home" />
<put name="body" value="/secure/index.jsp" />
</definition>
</tiles-definitions>
So if you go to the url "/secure/index.tile" then the tile "secure.index" will be displayed.
Or if you go to "/secure/index.jsf" then the tile will be displayed within the scope of a FacesContext.
I also added some context parameters:
DispatchTilesServlet.TILE_DEFINITION_DELIMITER To identify a new delimiter char. I chose '.' for the default
DispatchTilesServlet.FALLBACK_EXTENTION To identify the extention of a fallback url that /secure/index.tile will revert to if a tile 'secure.index' does not exist. The default is .jsp.
This servlet will probably not work for people using a /faces/* servlet mapping for the FacesServlet.
This solution seems too simple to be true....so if anyone knows of a problem with my using tiles in this way please let me know.
Regards,
Mike