Uploaded image for project: 'XWork'
  1. XWork
  2. XW-832

Result referencing (to clean up config and remove duplicates),new ResultReferenceResult, <result-ref>?

    XMLWordPrintableJSON

Details

    • New Feature
    • Status: Open
    • Major
    • Resolution: Unresolved
    • None
    • 1.2
    • Configuration
    • None

    Description

      Result referencing (to clean up config and remove duplicates),new ResultReferenceResult, <result-ref>?

      First, I would say - WebWork2 is GREAT! I've used struts and other MVC frameworks last 3 years - and its never be so easy to write MODULAR application, to add (non-intrusively) some extra functionality. But XW/WW2 requires a lot of work, especially refactoring.
      ..

      In our project I have many groups of actions that share the same result, like this:

      ----begin cut some package-----
      <action name="create_item" type="my.super.CRUD" method="...">
      <param name="domain">my.model.Obj</param>
      <result name="success">some_location</result>
      ...
      </action>
      <action name="edit_item" ..>
      <param name="domain">my.model.Obj</param>
      <result name="input">some_location</result>
      <result name="error">some_location</result>
      ...
      </action>
      ----end cut some package-----

      We work pretty hard to minimise number of views, to make them reusable and just great. Note that "some_location" is "success" for one action and "input" for other. Our (great&reusable actions use this generic result codes to be separated from specific view, we dont want to be tied to some "item_form"!

      But we also want to be able to define this "some_location" in one place.

      This could be achiewed in couple of ways:
      -parametrising results (weird!)
      -action chaining (usable, but not so simple
      -by creating special Result type.

      So i developed custom Result type to be able to do something like this:

      ----begin cut some package-----
      <result-types>
      <result-type name="ref"
      class="neuro.util.xwork.ResultReferenceResult"/>
      </result-types>
      <global-results>
      <result name="item_form">some_location</result>
      </global-results>
      <action name="create_item" type="my.super.CRUD" method="...">
      <param name="domain" type="ref">my.model.Obj</param>
      <result name="success" type="ref">item_form</result>
      ...
      </action>
      <action name="edit_item" ..>
      <param name="domain">my.model.Obj</param>
      <result name="input" type="ref">item_form</result>
      <result name="error" type="ref">item_form</result>
      ...
      </action>
      ----end cut some package-----

      ResultReferenceResult.execute just finds ResultConfig in its Action's
      ActionConfig, creates Result and executes it with current ActionInvocation.

      compare to chanining version:

      ----begin cut some package-----
      <global-results>
      <result name="item_form">some_location</result>
      </global-results>

      <action name="item_form"
      type="MayBeSomeActionJustReturningSuccessBut...">
      <result name="success" type="ref">item_form</result>
      </action>

      <action name="create_item" type="my.super.CRUD" method="...">
      <param name="domain">my.model.Obj</param>
      <result name="success" type="chain">item_form</result>
      ...
      </action>
      <action name="edit_item" ..>
      <param name="domain">my.model.Obj</param>
      <result name="input" type="ref">item_form</result>
      <result name="error" type="ref">item_form</result>
      ...
      </action>
      ----end cut some package-----

      The difference is in Result execution - in this case new Action and its Interceptors are involved. Chaining may be very useful, but really not simple and straightforward. I think that result-referencing is more common case than action chaining.

      ResultReferenceResult is made in 5 minutes almost by cut-n-paste and some IntelliJ Rename-Refactorings from Chain Result and DefaultActionInvocation.createResult()

      [see ResultReferenceResult.java code at end]

      Note that I've had to almost copy DefaultActionInvocation.createResult() - XWork still lacks utity methid like this. I think this may be a static in DefaultActionInvocation.

      I CONTRIBUTE THIS CODE TO XWORK PROJECT.
      IF YOU FIND IT USEFUL FEEL FREE TO INTEGRATE THIS RESULT WITH XWORK CODEBASE. (probably move to appropriate package, do something with that createResult(...))

      Now look at XWork configuration files.
      We have <result-type> definition. We have named <global-results>
      but there (was) no simple and straightforward way to decouple our action from their names.


      I may suggest new <action> subelement
      <result-ref name="result_name">result_name_to_be_invoked</result-ref>
      Which will add new ResultReferenceResult to action config.

      I think this config extension is very minimalistic and consistent, and will not lead to series of new <result-xxxxxxxx> ideas.

      -----start cut ResultReferenceResult.java -----
      package neuro.util.xwork;

      import org.apache.commons.logging.Log;
      import org.apache.commons.logging.LogFactory;

      import com.opensymphony.xwork.*;
      import com.opensymphony.xwork.config.ConfigurationManager;
      import com.opensymphony.xwork.config.entities.ResultConfig;
      import com.opensymphony.xwork.util.OgnlValueStack;
      import com.opensymphony.xwork.util.TextParseUtil;
      import com.opensymphony.xwork.util.OgnlUtil;

      /**

      • A special kind of view that finds other result
      • and executes it instead. This view takes one required parameter:
      • <ul>
      • <li><b>resultName</b> - the name of the result that will be executed.
      • It can be any configured result visible to curren action:
      • action-specific or global in current or some parent package.
      • </li>
      • </ul>
        *
      • @author $Author: neuro $
      • @version $Revision: 1.0 $
        */
        public class ResultReferenceResult implements Result {
        //~ Static fields/initializers /////////////////////////////////////////////

      private static final Log log = LogFactory.getLog(ResultReferenceResult.class);
      public static final String DEFAULT_PARAM = "resultName";

      //~ Instance fields ////////////////////////////////////////////////////////

      protected boolean parse = true;
      private String resultName;

      //~ Methods ////////////////////////////////////////////////////////////////

      public void setResultName(String resultName)

      { this.resultName = resultName; }

      /**

      • @param invocation the DefaultActionInvocation calling the action call stack
        */
        public void execute(ActionInvocation invocation) throws Exception {
        String finalLocation = resultName;

      if (parse)

      { OgnlValueStack stack = ActionContext.getContext().getValueStack(); finalLocation = TextParseUtil.translateVariables(resultName, stack); }

      if(log.isDebugEnabled()) log.debug("finalLocation="+finalLocation);
      ResultConfig resultConfig = (ResultConfig) ConfigurationManager.getConfiguration().getRuntimeConfiguration().getActionConfig(invocation.getProxy().getNamespace(), invocation.getInvocationContext().getName()).getResults().get(finalLocation);
      if (resultConfig!=null) {
      Result result = createResult(resultConfig);
      if (resultConfig!=null)

      { result.execute(invocation); }


      }
      }

      /**

      • Creates new Result from given ResultConfig.
      • This piece of code almost "copied" from DefaultActionInvocation.createResult...
      • Just added dynamic ResultConfig parameter.
      • It probably should be refactored to avoid copy-paste "code reuse".
      • Probably public static method with this signature do the job.
      • @param resultConfig
      • @return
      • @throws Exception
        */
        static public Result createResult(ResultConfig resultConfig) throws Exception {
        Result newResult = null;

      if (resultConfig != null) {
      Class resultClass = resultConfig.getClazz();

      if (resultClass != null) {
      try

      { newResult = (Result) resultClass.newInstance(); }

      catch (Exception e)

      { log.error("There was an exception while instantiating the result of type " + resultClass, e); throw e; }

      OgnlUtil.setProperties(resultConfig.getParams(), newResult, ActionContext.getContext().getContextMap());
      }
      }
      return newResult;
      }

      /**

      • This piece of code "copied" from com.opensymphony.xwork.ActionChainResult.
        */
        public boolean equals(Object o) {
        if (this == o) { return true; }

      if (!(o instanceof ResultReferenceResult))

      { return false; }

      final ResultReferenceResult resultReferenceResult = (ResultReferenceResult) o;

      if ((resultName != null) ? (!resultName.equals(resultReferenceResult.resultName)) : (resultReferenceResult.resultName != null)) { return false; }

      return true;
      }

      /**

      • This piece of code "copied" from com.opensymphony.xwork.ActionChainResult.
        */
        public int hashCode() { return ((resultName != null) ? resultName.hashCode() : 0); }

        }
        -----end cut ResultReferenceResult.java -----

      Attachments

        Activity

          People

            Unassigned Unassigned
            neuro Aleksei Gopachenko
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

              Created:
              Updated: