Here are the necessary steps:
- Create a custom table result that dynamically creates the "location" string (the location string is the value passed to the tile) that takes into account the namespace, actionName.
- Create a package that uses this result (named "tiles") and has conventions that use it as the parent package
- To implement and register "com.opensymphony.xwork2.UnknownHandler", this step is the most critical since this handler is called when the result cannot be resolved.
- Identify tiles that use the "location" passed from step one
The above steps require the following in struts.xml
<struts> <constant name="struts.convention.default.parent.package" value="tiles-package"/> <bean type="com.opensymphony.xwork2.UnknownHandler" name="tilesUnknownHandler" class="com.kenmcwilliams.tiles.result.TilesUnknownHandler"/> <package name="tiles-package" extends="convention-default"> <result-types> <result-type default="true" name="tiles" class="com.kenmcwilliams.tiles.result.TilesResult"/> </result-types> </package> </struts>
Custom implementation of result type:
package com.kenmcwilliams.tiles.result; import com.opensymphony.xwork2.ActionInvocation; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts2.ServletActionContext; import org.apache.struts2.dispatcher.ServletDispatcherResult; import org.apache.tiles.TilesContainer; import org.apache.tiles.access.TilesAccess; import org.apache.tiles.request.ApplicationContext; import org.apache.tiles.request.servlet.ServletRequest; import org.apache.tiles.request.servlet.ServletUtil; public class TilesResult extends ServletDispatcherResult { private static final Logger log = Logger.getLogger(TilesResult.class.getName()); public TilesResult() { super(); } public TilesResult(String location) { super(location); } @Override public void doExecute(String location, ActionInvocation invocation) throws Exception {
TilesUnknownHandler implementation:
package com.kenmcwilliams.tiles.result; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ObjectFactory; import com.opensymphony.xwork2.Result; import com.opensymphony.xwork2.XWorkException; import com.opensymphony.xwork2.config.Configuration; import com.opensymphony.xwork2.config.entities.ActionConfig; import com.opensymphony.xwork2.config.entities.ResultConfig; import com.opensymphony.xwork2.config.entities.ResultConfig.Builder; import com.opensymphony.xwork2.inject.Container; import com.opensymphony.xwork2.inject.Inject; import flexjson.JSONSerializer; import java.util.LinkedHashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletContext; import org.apache.commons.lang.StringUtils; import org.apache.struts2.convention.ConventionUnknownHandler; public class TilesUnknownHandler extends ConventionUnknownHandler { private static final Logger log = Logger.getLogger(TilesUnknownHandler.class.getName()); private static final String conventionBase = "/WEB-INF/content"; @Inject public TilesUnknownHandler(Configuration configuration, ObjectFactory objectFactory, ServletContext servletContext, Container container, @Inject("struts.convention.default.parent.package") String defaultParentPackageName, @Inject("struts.convention.redirect.to.slash") String redirectToSlash, @Inject("struts.convention.action.name.separator") String nameSeparator) { super(configuration, objectFactory, servletContext, container, defaultParentPackageName, redirectToSlash, nameSeparator); log.info("Constructed TilesUnknownHandler"); } @Override public ActionConfig handleUnknownAction(String namespace, String actionName) throws XWorkException { ActionConfig actionConfig; log.info("TilesUnknownHandler: before handleUnknownAction"); ActionConfig handleUnknownAction = super.handleUnknownAction(namespace, actionName); log.info("TilesUnknownHandler: after handleUnknownAction, returning with:"); log.log(Level.INFO, "...ActionConfig value: {0}", (new JSONSerializer().serialize(handleUnknownAction))); log.log(Level.INFO, "Modifying handleUnknowAction result handler"); Map<String, ResultConfig> results = handleUnknownAction.getResults(); ResultConfig resultConfig = results.get("success"); Builder builder = new ResultConfig.Builder("com.opensymphony.xwork2.config.entities.ResultConfig", "com.kenmcwilliams.tiles.result.TilesResult"); Map<String, String> params = resultConfig.getParams(); String tilesResultString = null; String location = params.get("location"); if (location != null && !location.isEmpty()) { int length = conventionBase.length(); if(StringUtils.startsWith(location, conventionBase)){ String subString = location.substring(length);
An example of using our "location" line, which looks like: NameSpace + "#" + ActionName + ".jsp", pay attention to this definition <definition name="REGEXP:(.*)#(.*)" extends="default"> in the following:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" "http://tiles.apache.org/dtds/tiles-config_3_0.dtd"> <tiles-definitions> <definition name="default" template="/WEB-INF/template/template.jsp"> <put-list-attribute name="cssList" cascade="true"> <add-attribute value="/style/cssreset-min.css" /> <add-attribute value="/style/cssfonts-min.css" /> <add-attribute value="/style/cssbase-min.css" /> <add-attribute value="/style/grids-min.css" /> <add-attribute value="/script/jquery-ui-1.8.24.custom/css/ui-lightness/jquery-ui-1.8.24.custom.css" /> <add-attribute value="/style/style.css" /> </put-list-attribute> <put-list-attribute name="jsList" cascade="true"> <add-attribute value="/script/jquery/1.8.1/jquery.min.js" /> <add-attribute value="/script/jquery-ui-1.8.24.custom/js/jquery-ui-1.8.24.custom.min.js" /> <add-attribute value="/script/jquery.sort.js" /> <add-attribute value="/script/custom/jquery-serialize.js" /> </put-list-attribute> <put-attribute name="title" value="defaults-name" cascade="true" type="string"/> <put-attribute name="head" value="/WEB-INF/template/head.jsp"/> <put-attribute name="header" value="/WEB-INF/template/header.jsp"/> <put-attribute name="body" value="/WEB-INF/template/body.jsp"/> <put-attribute name="footer" value="/WEB-INF/template/footer.jsp"/> </definition> <definition name="REGEXP:(.*)#(.*)" extends="default"> <put-attribute name="title" cascade="true" expression="OGNL:@ com.opensymphony.xwork2.ActionContext@getContext ().name"/> <put-attribute name="body" value="/WEB-INF/content{1}/{2}"/> </definition> </tiles-definitions>
With this, you can create a JSP in / WEB -INF / content / someplace / my-action.jsp
As with the legend, the tiles will appropriately decorate it if you create an action class named com.myapp.action.someplace.MyAction without any type of result that this code will execute, and the result is /WEB-INF/content/someplace/my-action.jsp will be displayed.
There you have agreements + tiles without comment (well, for a normal case).
NOTES :
- This answer, of course, is not ideal, but it serves as a working example of a strategy that can be applied to other viewing technologies (sitemesh, others).
- Currently, you can see that “.jsp” is being added to the result of alternating NOT in fragment definitions that are inflexible. A specific extension should be indicated inside the fragments, that is, the body attribute in the definition should add a specific type of view (.jsp, .fml, .vm), because at that time you better know.
- It is important to note that the definitions are checked in the order in which they are defined, so you can redefine the normal case of
REGEXP:(.*)#(.*) By placing the definitions between the default and REGEXP:(.*)#(.*) definitions REGEXP:(.*)#(.*) . For example, a definition called authenticated\(.*) Can be placed between the two definitions. In the end, if you could not do this, and all the pages should have been engraved in the same way, we would not use tiles! - Just to let you know that when using tiles3 (the struts2 tiles3 plugin) you can use all three types of viewing technologies (jsp, freemarker, velocity) to create one tile. It is working. You will probably use one viewing technology, but it's nice to know that this is possible.