Uploaded image for project: 'Struts 2'
  1. Struts 2
  2. WW-1330

SAF2 Automatic Ajax Support

VotersWatch issueWatchersLinkCloneUpdate Comment AuthorReplace String in CommentUpdate Comment VisibilityDelete Comments
    XMLWordPrintableJSON

Details

    • New Feature
    • Status: Closed
    • Major
    • Resolution: Fixed
    • WW 2.2.2, 2.0.0
    • 2.1.2
    • Plugin - JSON
    • None
    • Any

    Description

      Note: Full sample/test app, ready to try in the app server of your choice, can be downloaded <a href="http://www.omnytex.com/saf2_auto_ajax.zip">here (http://www.omnytex.com/saf2_auto_ajax.zip)</a>

      Strictly speaking, this does not actually have anything specifically to do with AJAX, but since that is likely to be the most common use case, that's the name I went with.

      Two new Interceptors are introduced, both of which have the goal of automatically populating the target Action from the incoming request's POST body, either an XML message (AjaxXMLInterceptor) or a JSON message (AjaxJSONInterceptor).

      In addition, two new Results are introduced, both of which have the goal of automatically generating a response to the client in the form of an XML message (AjaxXMLResult) or JSON message (AjaxJSONResult) whether the message is automatically generated from the fields of the Action.

      Finally, two new marker interfaces are introduced, one to indicate that an Action can be populated via the AjaxJSONInterceptor (AjaxJSONAware) and one to indicate that the Action can be populated via the AjaxXMLInterceptor (AjaxXMLAware).

      The Interceptors and Results can of course be mixed and matched, so you can accept a JSON message, then generate XML to return to the client, or accept a regular request and generate JSON, or accept XML and generate XML.

      Both of the Interceptors interrogate the incoming request's "content-type" header to determine if it should do its work. The AjaxXMLInterceptor looks for the value "text/xml" and the AjaxJSONInterceptor looks for the value "application/json".

      Incoming JSON
      -----------------------------------------------------
      An incoming JSON message to be used by the AjaxJSONInterceptor must be in the form:

      { "aaa":"bbb", "ccc": [ "ddd", "eee" ], "fff": [

      { "ggg":"hhh" }

      ] }

      For example:

      {
      "firstName" : "Frank", "children" : [ "Ashley", "Andrew" ], "certifications" : [

      { "microsoft" : "MCSD", "sun" : "SCJP" }

      ] }

      The message may not have any elements nested any deeper than that. The array section in this format with the elements "ddd" and "eee" can be used to populate a List or an array. The array section in this format with the elements "ggg" and "hhh" will be used to populate a Map.

      Outgoing JSON
      -----------------------------------------------------
      An outgoing JSON message as generate by the AjaxJSONResult will be in essentially the same format as above, except that there will be marker values to indicate whether an array section came from a List or Map. For example:

      {"physicalAttributes":{"map":{"height":"5.10","weight":"Too much"}},"children":

      {"list":["Andrew","Ashley"]}

      ,"firstName":"Frank", "certifications":["SCJP","MCSD"]}

      Incoming XML
      -----------------------------------------------------
      An incoming XML message to be used by the AjaxXMLInterceptor must be in the form:

      <scalarName>value</scalarName>
      <listName>value</listName>
      <listName>value</listName>
      <mapName key="keyValue">value</mapName
      <mapName key="keyValue">value</mapName

      For example:

      <person>
      <firstName>Frank</firstName>
      <children>Andrew</children>
      <children>Ashley</children>
      <certifications key="microsoft">MCSF</certifications>
      <certifications key="sun">SCJP</certifications>
      </person>

      The message may not have elements nested any deeper than that. So, you could not for instance have:

      <children>
      <child>Andrew</child>
      <child>Ashley></child>
      </children>

      The presence of the "key" attribute on an element that would otherwise be part of a list, as in the <children> elements for example, determines whether the elements become part of a Map or not.

      Outgoing XML
      -----------------------------------------------------
      An outgoing XML message as generated by the AjaxXMLResult will be in the same form as the above incoming XML example.

      Incoming Request Content-Type
      -----------------------------------------------------
      In order for either of the Interceptors to do their work, the incoming request must have the appropriate "content-type" header set. For JSON, that value is "application/json", and for XML it's "text/xml". If the AjaxXMLInterceptor fires for instance, and the "content-type" is not "text/xml", the interceptor will do nothing.

      Marker Interfaces
      -----------------------------------------------------
      In order for an Action to be populated by one of the interceptors, it must implement the appropriate marker interface. These are empty interfaces which serve just to mark an Action as "aware" of either JSON or XML. If the AjaxXMLInterceptor fires for instance, and the Action does not implement the AjaxXMLAware interface, the Action will not be populated.

      Configuration
      -----------------------------------------------------
      To make use of the new Results, you will need to declare them in the package of the Action mappings that will use them. The following examples will all show how to set up the XML Result and Interceptor, but it is the same for the JSON versions, obviously just with XML replaced with JSON everywhere:

      <result-types>
      <result-type name="ajaxXML" class="com.opensymphony.webwork.result.AjaxXMLResult" default="false" />
      </result-types>

      To make use of the Interceptors, you will likewise need to declare them in the package of the Action mappings that will use them, and you will also need to add them to the Interceptor stack your Action will use. One way to do this is to create a new stack. For example, this can be accomplished as follows, using the default stack as a model:

      <interceptors>
      <interceptor name="ajaxXML" class="com.opensymphony.webwork.interceptor.AjaxXMLInterceptor" />
      <interceptor-stack name="xmlStack">
      <interceptor-ref name="exception" />
      <interceptor-ref name="alias" />
      <interceptor-ref name="prepare" />
      <interceptor-ref name="servlet-config" />
      <interceptor-ref name="i18n" />
      <interceptor-ref name="chain" />
      <interceptor-ref name="model-driven" />
      <interceptor-ref name="fileUpload" />
      <interceptor-ref name="static-params" />
      <interceptor-ref name="params" />
      <interceptor-ref name="ajaxXML" />
      <interceptor-ref name="conversionError" />
      <interceptor-ref name="validation" />
      <interceptor-ref name="workflow" />
      </interceptor-stack>
      </interceptors>

      Lastly, you will need to reference the new stack, and/or Result, in your Action mappings:

      <action name="sendXML_receiveXML" class="com.omnytex.TestAction">
      <interceptor-ref name="xmlStack"/>
      <result name="success" type="ajaxXML" />
      </action>

      Note that there are other ways to configure all of this, this is just one approach (the approach used in the sample app). Please refer to the SAF2 documentation for further details.

      How It All Works
      -----------------------------------------------------
      When either XML or JSON conforming to the above formats is received in the body of a POST request, and the "content-type" matches the value appropriate for a given Interceptor, the message is parsed, and a Map of elements is created. Each element of this Map is either a List or a Map. For any element in the XML that does not have the "key" attribute, it will be part of a List. For JSON, each array section is examined to determine if it represents a straight array (or List, same thing in this case) or a Map, and the appropriate type is inserted into the elements collection. So, given the above example messages, we would find that there are three elements in the Map after this XML is parsed: "firstName", "children" and "certifications". "firstName" and "children" would both of type List (ArrayList specifically) and "certifications" will be of type Map (HashMap specifically).

      In the Action to be populated, "firstName" would be expected to be a scalar value field, i.e.:

      private String firstName;

      Naturally, there should be a corresponding mutator for this field. For the "children" element, the field could be either a List or a String[] array, either is supported. For "certifications", it would need to be a Map.

      Note that in your Action, any arrays must be initialized before this Interceptor fires! You can either do this by initializing on the array declaration line, or in a constructor. They do not have to be populated, and you can in fact initialize them with a size of 0, (i.e., String[] a = new String[0]; is perfectly acceptable).

      Frank W. Zammetti, June 2006

      Attachments

        1. code.zip
          11 kB
          Frank W. Zammetti
        2. saf2_auto_ajax.zip
          8.02 MB
          Frank W. Zammetti

        Issue Links

        Activity

          This comment will be Viewable by All Users Viewable by All Users
          Cancel

          People

            Unassigned Unassigned
            fzammetti@omnytex.com Frank W. Zammetti
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Slack

                Issue deployment