struts2 - How to Integrate Struts Conventions with Tiles such that the benefit of conventions is maintained -


how integrate struts conventions tiles while maintaining conventions benefits?

the issue conventions links url-to-action-to-result automatically , nicely jsp, velocity , freemarker results. not expect deal tiles result.

when using tiles typically want our ui actions (as opposed json/xml service actions) use tiles in doing lose convention result component , need use annotations. annotations allow deviate expected, in large application when expecting use tiles annoyance. further conventions allows create actions specifying view. want retain such benefit when using tiles well. rectify need establish convention carries though tiles result such don't need use annotations tie action tiles result , can continue create jsps without actions classes gain benefits of conventions (no xml) , benefits of tiles (all boiler plate factored tiles).

how achieve this?

this self answer others wish address issue

here steps needed:

  • create custom tiles result dynamically builds "location" string (the location string value passed tiles) takes account namespace, actionname.
  • create package uses result (named "tiles") , have conventions use it's parent package
  • implement , register "com.opensymphony.xwork2.unknownhandler", step critical handler called when result can't resolved
  • tiles definition(s) make use of "location" passed in first step

the above steps require 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 result-type implementation:

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 "//", although don't know why in string                 if (count == 1){//empty namespace                     tilesresultstring = substring.replacefirst("/", "#"); //todo: because doing straight replacement of last element else can removed                 }else{ //replace last slash between namespace , file "#"                     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 how use our "location" string in form of: namespace + "#" + actionname + ".jsp", note definition <definition name="regexp:(.*)#(.*)" extends="default"> in 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 in place can create jsp's under /web-inf/content/someplace/my-action.jsp

just conventions , tiles decorate appropriately if create action class called com.myapp.action.someplace.myaction without result type code execute , /web-inf/content/someplace/my-action.jsp result still rendered.

there have conventions + tiles no more annotations (well normal case).

notes:

  • this answer isn't perfect provide working example of strategy can applied other view technologies (sitemesh, others).
  • currently can see ".jsp" being appended in tiles result not in tiles definitions inflexible. specific extension should specified within tiles, body attribute within definition should append specific view type (.jsp, .fml, .vm) because should know best @ time.
  • it important note definitions tried in order given,so can override normal case regexp:(.*)#(.*) placing definitions between default , regexp:(.*)#(.*) definitions. instance definition called authenticated\(.*) can placed between these 2 definitions. after if couldn't , pages had tiled same wouldn't using tiles!
  • just know when using tiles3 (the struts2 tiles3 plugin) can use 3 types of view technologies (jsp, freemarker, velocity) compose 1 tile. works. going use 1 view technology consistently it's nice know possible.

Comments

Popular posts from this blog

c# - Send Image in Json : 400 Bad request -

jquery - Fancybox - apply a function to several elements -

An easy way to program an Android keyboard layout app -