Adding Tiles to a Struts 1.3 Project with a Custom Request Processor
In Struts, if you wanted to do funny shenanigans (e.g. override the Roles checking for authorization, or override the ActionForm population to parse a JSON object rather than standard parameters) with the Struts Controller, up until Struts 1.2, you had to extend the Request Processor and configure the Struts ActionServlet to use it instead. This would get rather complicated when you tried to layer multiple custom controllers together, such as with the Validator and the Tiles plugins.
Struts 1.3 changed this behavior. Rather than all of the heavy lifting being offloaded onto the RequestProcessor object by the ActionServlet, instead there’s a chain of commands executed by a class called “ComposableRequestProcessor, and it’s configured by chain-config.xml; which is in the struts-core JAR file. If you want to extend the controller to do out-of-the-ordinary stuff, you’re supposed to copy this file to somewhere else (like /WEB-inF/custom-chain-config.xml) edit it, and configure the ActionServlet to look at it instead like this
<servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>chainConfig</param-name> <param-value>/WEB-INF/custom-chain-config.xml</param-value> </init-param> </servlet>
The trouble however, is that the Struts Tiles plugin that shipped with 1.3 didn’t change from 1.2. It was written to automatically replace the default RequestProcessor and its chain of command. I’m pretty sure this was supposed to be a convenience, so that to use the Tiles plugin, all you had to do was flip a switch, and it would figure out its own hooks into Struts… because this is how the Struts Tiles plugin works; it essentially overrides and customizes the RequestProcessor so forwards goto it, rather than to some JSP file or somerthing somewhere. You can see how it’s doing this overriding by looking at the method TilesPlugin.initRequestProcessorClass; which is actually a little interesting, because it’s not mucking with any XML. But that’s all you really have to do, usually.
However, and this is why I’m posting this; if you have a custom chain-config.xml already in place that was based on the chain-config.xml in the struts-core JAR, then your ActionForwards going to Tiles layouts aren’t going to lookup correctly. For example, when Struts tries to goto the ActionForward “page.index”, we get the following exception because normally ActionForwards would goto a page like “/index.jsp”.
WARNING: Unhandled exception java.lang.IllegalArgumentException: Path page.index does not start with a "/" character at org.apache.catalina.core.ApplicationContext.getRequestDispatcher(ApplicationContext.java:374) at org.apache.catalina.core.ApplicationContextFacade.getRequestDispatcher(ApplicationContextFacade.java:196) at org.apache.struts.chain.commands.servlet.PerformForward.handleAsForward(PerformForward.java:107) at org.apache.struts.chain.commands.servlet.PerformForward.perform(PerformForward.java:96) at org.apache.struts.chain.commands.AbstractPerformForward.execute(AbstractPerformForward.java:54) at org.apache.struts.chain.commands.ActionCommandBase.execute(ActionCommandBase.java:51) at org.apache.commons.chain.impl.ChainBase.execute(ChainBase.java:191) at org.apache.commons.chain.generic.LookupCommand.execute(LookupCommand.java:305) at org.apache.commons.chain.impl.ChainBase.execute(ChainBase.java:191) at org.apache.struts.chain.ComposableRequestProcessor.process(ComposableRequestProcessor.java:283) at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1913) at org.apache.struts.action.ActionServlet.doGet(ActionServlet.java:449) ...
As far as cryptic errors go, this one is pretty far up there, and unless you actually know how Tiles works, it’s going to be a huge pain to debug. The remedy is actually to base your chain-config.xml file off of Tiles’ own chain-config.xml. And in it you can see how at the end it calls TilesPreProcessor right before it performs the forward.
The Tiles documentation indeed says you need to do this — but hardly anyone ever does, because everything will work unless you customize the request processor; and hardly any books or sites out there will tell you to do it.