2

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:

  • GET Endpoint= http://localhost:8080, Resource= /StrutsContactsApp/contacts.json: OK
  • POST Endpoint= http://localhost:8080, Resource= /StrutsContactsApp/contacts.json + JSON body: OK
  • PUT Endpoint= http://localhost:8080, Resource= http://localhost:8080, + JSON body:

    API call succeeds, browser gets "HTTP 404"; log says "No result defined for action com.example.contactsapp.controllers.ContactsController and result update"

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 :(

FoggyDay
  • 11,962
  • 4
  • 34
  • 48
  • 1
    Nothing wrong with the configuration having not a named result code. https://stackoverflow.com/a/20782032/573032 – Roman C Jan 10 '20 at 18:43
  • I'm still not clear. What "String" should method `upate()` return (in the context of struts2-rest-plugin) to eliminate HTTP 404? `"success"`? `Action.SUCCESS`? "Something else"? Please consider giving a response (with an example), – FoggyDay Jan 10 '20 at 18:58
  • It's not clear what are you doing. And it's not clear what you have done. If you want to return a response in the body then you should use POST method and return the corresponding status code. https://stackoverflow.com/a/46625135/573032 – Roman C Jan 12 '20 at 13:05
  • Let me ask a differently: the struts2-rest-plugin sample code I used assumes that the "update" action will call some .jsp page afterwards. I suspect that's why I'm getting HTTP 404. I don't *WANT* to to call a .jsp. I'd like to return some JSON. Or return nothing at all. Q: How should I modify my code and/or my struts.xml so that action returns JSON (instead of invoking a .jsp)? – FoggyDay Jan 12 '20 at 18:41
  • one day you ask it differently, another day you forget to update your question, third day you have found that you can't resolve your problems because you need to know answers for one-more different questions. You should ask them in the different thread. I have provided you with the solution to your problem that you can't understand. – Roman C Jan 13 '20 at 14:31
  • My question remains: Given the struts2-rest-plugin code above, why is "update()" giving HTTP 404? What exactly do I need to change in my code to eliminate the 404? Same question, same code, I didn't "forget" anything. If possible, please post YOUR version of my "update()" in a response. Thank you in advance. – FoggyDay Jan 13 '20 at 21:19
  • 404 means of missing a resource. You didn't tell us which resource was expected nor you posted an example to reproduce the error. I think something wrong with the struts configuration. Try https://stackoverflow.com/a/42319946/573032 – Roman C Jan 14 '20 at 14:37
  • Sorry that my attempts to clarify the problem just seem to be causing more confusion. I *know* you know the answer. The solution is probably "obvious" (at least to you!) SUGGESTION: let me restate the problem with an [SSCCE](http://sscce.org/). HINTS: https://stackoverflow.com/a/42319946/3135317 and https://stackoverflow.com/a/20782032/3135317 are the exact *OPPOSITE* of what I need .The client should do an HTTP "PUT" to invoke the "update" action (controller). The struts2-rest-plugin controller should simply send back a JSON response. *NO* .jsp pages anywhere. – FoggyDay Jan 14 '20 at 19:42
  • But you are not sending a json response. If you want to learn how different could be the ways to return a json response to the client then read. https://stackoverflow.com/a/17096564/573032 – Roman C Jan 15 '20 at 16:40

1 Answers1

0

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...
    
FoggyDay
  • 11,962
  • 4
  • 34
  • 48