Recently I’ve found that using Tiles2 is not all that fun if your domain objects tend to throw exceptions at rendering time. I know it not right thing to do of course, but anyway - is there a recipe to make Tiles show the errors in individual tiles?
Thank’s god it’s all open source and you can plug and extend nearly everything.
So here is how I did this with Spring MVC, JSP’s and Tiles2.
First we need to create a custom TilesContextFactory. Since I am using JSP for rendering on my project I will naturally extend the JspTilesContextFactory:
package com.anydoby.web;
import java.io.IOException;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.jsp.PageContext;
import org.apache.tiles.TilesApplicationContext;
import org.apache.tiles.context.TilesRequestContext;
import org.apache.tiles.jsp.context.JspTilesContextFactory;
import org.apache.tiles.jsp.context.JspTilesRequestContext;
import org.apache.tiles.jsp.context.JspUtil;
/**
* This context factory will delegate execution to the {@link JspTilesContextFactory} except for
* creation of request context. The request context this factory generates is able to handle
* exceptions by forwarding an exception to a pre-configured web page for rendering.
*
* @author SergeyZ
*
*/
public class ExceptionExposingContextFactory extends JspTilesContextFactory {
private static final String ERROR_PAGE_PATH = "errorPagePath";
private String errorPage;
@Override
public void init(Map<String, String> configParameters) {
super.init(configParameters);
errorPage = configParameters.get(ERROR_PAGE_PATH);
}
@Override
public TilesRequestContext createRequestContext(TilesApplicationContext context,
Object... requestItems) {
if (requestItems.length == 1) {
ServletContext servletContext = getServletContext(context);
if (servletContext != null) {
final PageContext ctx = (PageContext)requestItems[0];
return new JspTilesRequestContext(servletContext, ctx) {
@Override
public void include(String path) throws IOException {
try {
JspUtil.doInclude(ctx, path, false);
} catch (Exception e) {
ServletRequest rq = ctx.getRequest();
rq.setAttribute("exception", e);
super.include(errorPage);
rq.removeAttribute("exception");
}
}
};
}
}
return null;
}
}This would be enough if only the init method were invoked at factory creation. In fact the default Tiles2 initialization pipeline omits initialization of underlying rendering factories that it creates. That’s a shame :(
However it is very simple to extend from the basic configurer:
package com.anydoby.web;
import java.lang.reflect.Field;
import java.util.Map;
import org.apache.tiles.context.ChainedTilesContextFactory;
import org.apache.tiles.context.TilesContextFactory;
/**
* This context factory will initialize the factories which it created using the
* {@link TilesContextFactory#init(java.util.Map)}.
* @author SergeyZ
*
*/
public class InitalizingChainedContextFactory extends ChainedTilesContextFactory {
@Override
public void init(Map<String, String> configParameters) {
super.init(configParameters);
try {
Field field = getClass().getSuperclass().getDeclaredField("factories");
field.setAccessible(true);
TilesContextFactory[] factories = (TilesContextFactory[])field.get(this);
field.setAccessible(false);
for (TilesContextFactory tilesContextFactory : factories) {
tilesContextFactory.init(configParameters);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}Unfortunately the ChainedTilesContextFactory was not designed for extensibility, so I have to make an ugly hack - lookup factories using the private field access.
And now the easiest part - Tiles2 configuration with Spring:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/config/tiles-defs.xml</value>
</list>
</property>
<property name="useMutableTilesContainer" value="true"></property>
<property name="tilesProperties">
<props>
<prop key="org.apache.tiles.context.TilesContextFactory">
com.anydoby.web.InitalizingChainedContextFactory
</prop>
<prop key="org.apache.tiles.context.ChainTilesContextFactory.FACTORY_CLASS_NAMES">
org.apache.tiles.servlet.context.ServletTilesContextFactory,
com.anydoby.web.ExceptionExposingContextFactory
</prop>
<prop key="errorPagePath">/WEB-INF/views/error.jsp</prop>
</props>
</property>
</bean>The error page is hidden from users in WEB-INF/views.
First published on the old anydoby.com blog on 2008-02-20.