1

I'm trying my first Java RESTful web service and probably I don't have clear some mechanisms.

Here an example of my code:

@Path(Paths.USERS)
public class UserService {

    private static final String OK_MESSAGE_USERSERVICE_PUT = Messages.OK_MESSAGE_USERSERVICE_PUT;
    private Client esClient = ElasticSearch.getClient();

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public String get(@QueryParam(QParams.ID) String id) {
        // TODO Authentication
        try {
            GetResponse response = esClient
                    .prepareGet(PRIMARY_INDEX_NAME, USERS_TYPE_NAME, id)
                    .execute().actionGet();

            if (response != null) {
                return response.getSourceAsString();
            }


        }catch (ElasticsearchException e) {
            e.printStackTrace();
            return e.getMessage();
        }
        return Messages.RESOURCE_NOT_FOUND;
    }

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    public String update(@QueryParam(QParams.ID) String id,
            @PathParam("metadata") String metadata) {
        // TODO Authentication
        boolean isMyself = true;

        // precondition, the user exsists. If the check fails, you
        // should put the isMyself flag at false.
        if (isMyself){

                    esClient
                    .prepareIndex(PRIMARY_INDEX_NAME, USERS_TYPE_NAME, id)
                    .setSource(metadata).execute().actionGet();
        }

        return OK_MESSAGE_USERSERVICE_PUT;
    }

My problem is:

How should I pass the metadata to the web service? I tried with

 curl -g -X PUT 'http://localhost:8080/geocon/users?id=007&metadata={"name":{"first":"james","last":"bond"}}'

but I encounter an error like

Root Cause: java.net.URISyntaxException: Illegal character in query at index 33: /geocon/users?id=007&metadata=%7B"name":%7B"first":"james","last":"bond"%7D%7D

    java.net.URI$Parser.fail(URI.java:2848)

Googling around, I've tried this different solution:

curl -X PUT -H "application/json" -d '{"name":{"first":"james","last":"bond"}}' http://localhost:8080/geocon/users/

but with this approach, I don't know how to pass to the web service my will of updating the user with ID 007 (since, AFAIK, I'm only communicating {"name":{"first":"james","last":"bond"}}).

How would you do? Thanks!

Paul Samsotha
  • 188,774
  • 31
  • 430
  • 651
BlacK
  • 231
  • 5
  • 15

2 Answers2

1

I would completely refactor this solution.

First: Change the url scheme to make the id part of the URI path. This is a more RESTful approach

@PUT
@Path("{id}")
@Consumes(MediaType.APPLICATION_JSON)
public String update(@PathParam("id") String id) {

Second: The request entity body should be the JSON. I don't see any reason for this to be "metadata". You can get rid of that altogether. You want to update the resource representation with the data you are sending, so this should be part of the body.

Third: If you're working with JSON, you should take advantage of Pojo Mapping with a provider like Jackson. This will automatically pars the JSON to a Pojo. You can do something like

public class Agent {
    private Name name;
    // getters and setters

    public static class Name {
        private String first;
        private String last;
        // getters and setters
    }  
}

Then just have the Agent argument in the method signature, signifying that it is the body of the request.

@PUT
@Path("{id}")
@Consumes(MediaType.APPLICATION_JSON)
public String update(@PathParam("id") String id, Agent agent) {

You will need to add the Jackson provider to the project. Hopefully you are using Maven. You can add

<dependency>
    <groupId>com.fasterxml.jackson.jaxrs</groupId>
    <artifactId>jackson-jaxrs-json-provider</artifactId>
    <version>2.4.0</version>
</dependency>

Then in your application configuration, register the JacksonJsonProvider. If you need help with this, then show how you are configuration your application (whether web.xml or an Application subclass). If you want to skip the Pojo mapping and just get the raw JSON, the just set the method argument type to String as you already are doing (just without the annotation)

Fourth: Then your request would be (I'm on Windows, where we need to use double quotes and escape the interior quotes)

curl -v -X PUT
        -H "Content-Type:application/json"
        -d "{\"name\":{\"first\":\"james\", \"last\":\"bond\"}}"
        http://localhost:8080/geocon/users/007

Fifth: I would change the @GET method also, to make the id part of the URL path, instead of a query param.

Paul Samsotha
  • 188,774
  • 31
  • 430
  • 651
  • First of all, thank you for your super-useful answer. 1. I've modified it with @Path("users/{id}"), so I can view every user as a different resource. 2. YES, right. I want to update the resource with the new data. This is where I have troubles. How my web service understand that the JSON I'm posting has to be a parameter of the update method? 3. I am using RAW JSONs because the application will be schema-free (in fact, the whole software will be a middle layer between clients and Elasticsearch 4. With the changes I made, I receive a Grizzly error Request Failed 5. Great suggestion, done! – BlacK Mar 06 '15 at 18:03
  • If the method argument has no annotation, then it is automatically assumed to be the entity body of the request. What's the error? – Paul Samsotha Mar 06 '15 at 18:31
  • And why did you change it to `"users/{id}"`? I assumed the `@Path` on the class is `"users"`, no? – Paul Samsotha Mar 06 '15 at 18:33
  • Your assumption was right. My fault... Now it seems working :) – BlacK Mar 06 '15 at 19:41
  • I'm new to Jersey - everything works so far, except the automatic JSON data mapping to a POJO parameter, as demonstrated above. I added the latest version of the dependency you mentioned, and registered it in the following way: @ApplicationPath("/api") public class JerseyApplication extends ResourceConfig { public JerseyApplication() { register(JacksonFeature.class); register(JacksonJsonProvider.class); packages("com.mycom.mypackage"); } } – Omer Hassan May 15 '19 at 20:33
  • Ok, it's working now, after making a random series of changes. Going backwards and using process of elimination, it appears that removing the constructor from the POJO class makes it work. Also, using '-X' switch in CURL gives me HTTP 405 error, whereas using '--request POST' works. I'm using Tomcat EE Plus latest version, with several Glassfish Jersey dependencies from Maven. (I wasn't able to get Jersey to work on plain Tomcat, whereas had no problem at all with Tomcat EE Plus; though I realize it has many additional features which might be (maybe?) making certain things more complicated.) – Omer Hassan May 15 '19 at 21:04
0

It looks like you mix the way parameters are passed to a GET request (?foo=bar) with a PUT method, which is much more like POST.

I'd expect that you send your JSON data to an URL like /gecon/user/13, of course after having your URL mapping edited properly.

Try reading the wikipedia and this answer.

Community
  • 1
  • 1
9000
  • 37,110
  • 8
  • 58
  • 98