struts2struts2-rest-plugin

struts2-rest-plugin failing: "org.apache.struts2.dispatcher.Dispatcher - Could not find action or result"


I'm writing a REST server, using struts2-rest-plugin. I'm testing the REST calls with SoapUI and Postman. I'm following the sample code here:

https://cwiki.apache.org/confluence/display/WW/REST+Plugin

My REST calls for controller.index())and controllers.create() are working OK.

But although controller.update() successfully calls the controller and updates the record:

19:46:05.361 [http-nio-8080-exec-3] DEBUG com.opensymphony.xwork2.validator.ValidationInterceptor - Validating /contacts with method update.
19:46:05.393 [http-nio-8080-exec-3] DEBUG com.opensymphony.xwork2.DefaultActionInvocation - Executing action method = update
19:46:05.398 [http-nio-8080-exec-3] DEBUG com.opensymphony.xwork2.ognl.SecurityMemberAccess - Checking access for [target: com.example.contactsapp.controllers.ContactsController@7e69160c, member: public java.lang.String com.example.contactsapp.controllers.ContactsController.update(), property: null]
19:46:07.862 [http-nio-8080-exec-3] DEBUG com.example.contactsapp.controllers.ContactsController - Updating existing contact(97)...
...

... it fails on "return", with this error:

...
19:46:11.380 [http-nio-8080-exec-3] WARN  org.apache.struts2.dispatcher.Dispatcher - Could not find action or result: /StrutsContactsApp/contacts/97
com.opensymphony.xwork2.config.ConfigurationException: No result defined for action com.example.contactsapp.controllers.ContactsController and result update
    at org.apache.struts2.rest.RestActionInvocation.findResult(RestActionInvocation.java:283) ~[struts2-rest-plugin-2.5.22.jar:2.5.22]
    at org.apache.struts2.rest.RestActionInvocation.executeResult(RestActionInvocation.java:225) ~[struts2-rest-plugin-2.5.22.jar:2.5.22]
    at org.apache.struts2.rest.RestActionInvocation.processResult(RestActionInvocation.java:189) ~[struts2-rest-plugin-2.5.22.jar:2.5.22]
    at org.apache.struts2.rest.RestActionInvocation.invoke(RestActionInvocation.java:137) ~[struts2-rest-plugin-2.5.22.jar:2.5.22]
    at com.opensymphony.xwork2.DefaultActionProxy.execute(DefaultActionProxy.java:157) ~[struts2-core-2.5.22.jar:2.5.22]
    ...

Here's the controller:

public class ContactsController implements ModelDriven<Object> {
    private static final Logger log = LogManager.getLogger(ContactsController.class);
    private String id;
    private Contact model = new Contact();
    private Collection<Contact> list;
    private ContactsRepository contactsRepository = new ContactsRepositoryImpl();

    @Override
    public Object getModel() {
        return (list != null ? list : model);
    }

    public void setId(String id) {
        if (id != null) {
            int contactId = Integer.parseInt(id);
            this.model = contactsRepository.getContact(contactId);
        }
        this.id = id;
    }

    public HttpHeaders index () {
        log.debug("Reading all contacts...");
        list = contactsRepository.getContacts();
        return new DefaultHttpHeaders("index").disableCaching();
    }

   ...
    // PUT /orders/1
    public String update() {
        log.debug("Updating existing contact(" + id + ")...", model);
        contactsRepository.updateContact(model);
        return "update";
    }
    ...

... and struts.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
    <constant name="struts.mapper.class" value="rest" />
    <constant name="struts.convention.action.suffix" value="Controller"/>
    <constant name="struts.convention.action.mapAllMatches" value="true"/>
    <constant name="struts.convention.default.parent.package" value="rest-default"/>
    <constant name="struts.convention.package.locators" value="controllers"/>

    <package name="contacts" extends="rest-default">
        <global-allowed-methods>index,show,create,update,destroy,deleteConfirm</global-allowed-methods>
    </package>
</struts>   

Nothing worked before I added <package name="contacts">. I tried several different variations of adding <action> and <result>, to no avail.

Example URLs:

Q: Any suggestions for resolving the error and getting "update()" working with struts2-rest-plugin?

PS: I'm having similar problems with all three of "update()", "show()" and "destroy()". Each expect an id (which I can see in the Eclipse debugger is being passed in correctly).

Also:

The struts-rest-plugin supports URLs like http://localhost:8080/myapp/contacts.json (for JSON) and http://localhost:8080/myapp/contacts.xml (for XML). I'm not sure if "update()" needs contacts.json/id, or just contacts/id. Neither work :(


Solution

  • OK - the entire problem was that I adopted sample code in a way that "seemed reasonable". But it turns out that the struts2-rest-plugin has many "unspoken conventions" which I wasn't aware of. Specifically:

    1. By default, struts2-rest-plugin maps pre-ordained action names index(), show(), create(), update() and destroy() to CRUD operations.

    2. I did NOT need to define a "package" or any "actions" in struts.xml. Unless I need to customize, Struts2-rest-plugin wants to manage these details itself. This is the (minimal!) struts.xml I wound up with:

    3. I did NOT want to use .jsp's (like the examples). I thought needed to "return something" (like a JSON response) from each action.

      Instead, I just needed to redirect to an action. For example:

      public HttpHeaders create() {
          log.debug("Creating new contact...", model);
          contactsRepository.addContact(model);
          return new DefaultHttpHeaders("index");
          ...
      
    4. Most important, I wasn't clear about the struts2-rest-plugin URI conventions to invoke each different CRUD operation:

      Struts-rest-plugin URL mappings (https://struts.apache.org/plugins/rest/):
      **Default**
      **Method**   **Description**                                                   **Example**
      ------    -----------                                                    -------
      index()   GET request with no id parameter.                              GET http://localhost:8080/StrutsContactsApp/contacts.json
      show()    GET request with an id parameter.                              GET http://localhost:8080/StrutsContactsApp/contacts/1.json
      create()  POST request with no id parameter and JSON/XML body.           POST http://localhost:8080/StrutsContactsApp/contacts.json
      update()  PUT request with an id parameter and JSON/XML body.            PUT http://localhost:8080/StrutsContactsApp/contacts/65.json
      destroy() DELETE request with an id parameter.                           DELETE http://localhost:8080/StrutsContactsApp/contacts/33.json
      edit()    GET  request with an id parameter and the edit view specified. 
      editNew() GET  request with no id parameter and the new view specified.
      
      Struts-rest-plugin runtime automatically manages:
       - Parsing object ID from REST URI
       - Serializing/deserializing input parameters and return objects
       - By default, controller will implement interface com.opensymphony.xwork2.ModelDriven
       - By default, shouldn't need to (i.e. *shouldn't*) declare any packages or actions in struts.xml
         <= "Follow conventions", and struts2-rest-plugin will manage all the details...