How to Integrate Struts Agreements with Tiles to Maintain the Benefits of Agreements

How to integrate Struts agreements with Tiles while maintaining the benefits of conventions?

The problem is that the conventions bind url-to-action-to-result automatically and do it nicely for jsp, velocity and freemarker results. He does not expect the result of the tile to be processed.

When using fragments, we usually want all of our user interface actions (as opposed to the json / xml service actions) to be used for the use of plates, but at the same time we lose agreement for the result component and must use annotations. Annotations allow us to deviate from what is expected, but in a large application, expecting to use tiles, this is annoying. Further agreements allow us to create actions only by specifying a view. We would like to preserve such benefits when using tiles. To fix this, we need to establish an agreement that transfers at least the result of the tile, so that we do not need to use annotations to bind the action to the result of the tile, and that we can continue to create JSPs without action classes that will benefit (without xml) and the benefits of the tile (all boiler plates are counted into tiles).

How to do it?

This is an independent answer to help others who want to solve this problem.

+4
source share
1 answer

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 { //location = "test.definition"; //for test log.log(Level.INFO, "TilesResult doExecute() location: {0}", location); //Start simple conventions // if (/** tiles && **/location == null) { String namespace = invocation.getProxy().getNamespace(); String actionName = invocation.getProxy().getActionName(); location = namespace + "#" + actionName + ".jsp"; //Warning forcing extension log.log(Level.INFO, "TilesResult namespace: {0}", namespace); log.log(Level.INFO, "TilesResult actionName: {0}", actionName); log.log(Level.INFO, "TilesResult location: {0}", location); } //End simple conventions setLocation(location); ServletContext context = ServletActionContext.getServletContext(); ApplicationContext applicationContext = ServletUtil.getApplicationContext(context); TilesContainer container = TilesAccess.getContainer(applicationContext); HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); ServletRequest servletRequest = new ServletRequest(applicationContext, request, response); container.render(location, servletRequest); } } 

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); //chop off "/WEB-INF/content" int count = StringUtils.countMatches(subString, "/");//TODO: maybe check for "//", although I don't know why it would be in the string if (count == 1){//empty namespace tilesResultString = subString.replaceFirst("/", "#"); //TODO: because I am doing a straight replacement of the last element the else can probably be removed }else{ //replace the last slash between the namespace and the file with "#" int lastIndex = subString.lastIndexOf("/"); //subString.substring(lastIndex, lastIndex); String nameSpace = subString.substring(0, lastIndex); String file = subString.substring(lastIndex + 1); tilesResultString = nameSpace + "#" + file; } } } Map<String, String> myParams = new LinkedHashMap<String, String>(); myParams.put("location", tilesResultString); builder.addParams(myParams); ResultConfig build = builder.build(); Map<String, ResultConfig> myMap = new LinkedHashMap<String, ResultConfig>(); myMap.put("success", build); log.log(Level.INFO, "\n\n...results: {0}\n\n", (new JSONSerializer().serialize(results))); actionConfig = new ActionConfig.Builder(handleUnknownAction).addResultConfigs(myMap).build(); //className("com.kenmcwilliams.tiles.result.TilesResult") return actionConfig; } @Override public Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig, String resultCode) throws XWorkException { log.info("TilesUnknownHandler: before handleUnknownResult"); Result handleUnknownResult = super.handleUnknownResult(actionContext, actionName, actionConfig, resultCode); log.info("TilesUnknownHandler: after handleUnknownResult, returning with:"); log.log(Level.INFO, "...Result value: {0}", (new JSONSerializer().serialize(handleUnknownResult))); return handleUnknownResult; } } 

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.
+3
source

All Articles