/java-jersey-restful-server-client-sample

RESTful Server/Client sample with Jersey in Java8

Primary LanguageJava

java-jersey-restful-server-client-sample

Build Status MIT License

RESTful sample with Jersey Server/Client in Java8. This sample is simple ToDo Application in Command line interface.

Features

Table of Contents

  1. How to run
  2. Make A Simple Server

How to run

Server:

cd projserver
mvn test clean package
java -jar target/dependency/jetty-runner.jar target/*.war

Client:

cd projclient
mvn package
java -jar ...

Make a simple CRUD server

First of all, we need to create a model class. This class is a resouce and subject to deal with API.

@XmlRootElement // important
public class Task {
    private String description;
    private Integer priority;
    private String untilDate;

    public Task(String description, Integer priority, String untilDate) {
        this.description = description;
        this.priority = priority;
        this.untilDate = untilDate;
    }

    public Task() { // important
    }

    // getter and setter methods are followed. this is also nessesary

Next, create a resouce class with GET method.

@Path("task") // is supporsed to be http://BASE_URL/task
public class TaskResource {

    @GET
    @Produces({MediaType.APPLICATION_JSON})
    public Task get() {
        return new Task("sample", 0, "2017/08/10");
    }

}

main functions is like this. Set some setting for running as server.

public class Main {
    public static final Integer PORT = 8080;

    public static void main(String[] args) throws Exception {
        final Logger logger = LogManager.getLogger();
        final Server server = new Server(Integer.valueOf(PORT));
        final WebAppContext root = new WebAppContext();

        root.setContextPath("/");
        root.setParentLoaderPriority(true);

        final String webappDirLocation = "src/main/webapp/";
        root.setDescriptor(webappDirLocation + "/WEB-INF/web.xml");
        root.setResourceBase(webappDirLocation);

        server.setHandler(root);

        logger.info("start " + server.getURI() + "...");
        server.start();
        server.join();
    }
}

Compile and run (see above)

java -jar target/dependency/jetty-runner.jar target/*.war 2>&1                                                     2017-08-14 02:04:25.580:INFO::main: Logging initialized @262ms
2017-08-14 02:04:25.589:INFO:oejr.Runner:main: Runner
2017-08-14 02:04:25.713:INFO:oejs.Server:main: jetty-9.3.3.v20150827
2017-08-14 02:04:29.446:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@e6ea0c6{/,file:///Users/iwag/Devel/src/github.com/iwag/java-jersey-restful-server-client-sample/projserver/target/projserver/,AVAILABLE}{file:///Users/iwag/Devel/src/github.com/iwag/java-jersey-restful-server-client-sample/projserver/target/projserver.war}
2017-08-14 02:04:29.485:INFO:oejs.ServerConnector:main: Started ServerConnector@7daa0fbd{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2017-08-14 02:04:29.485:INFO:oejs.Server:main: Started @4169ms

Let's get http request to it via cURL.

$ curl -i -H "Content-Type: application/json" 'localhost:8080/task'
HTTP/1.1 200 OK
Date: Mon, 14 Aug 2017 09:05:31 GMT
Content-Type: application/json
Content-Length: 62
Server: Jetty(9.3.3.v20150827)

{"description":"sample","priority":0,"untilDate":"2017/08/10"}%

Well done. Now we've done with GET(READ) among CRUD. CUD is implemented like following code.

    @DELETE
    public Response delete() {
        // write deleting code
        return Response.status(Response.Status.ACCEPTED).build();
    }

    @POST
    @Consumes({MediaType.APPLICATION_JSON})
    public Response update(Task t) {
        System.out.println(t);
        // write deleting code
        return Response.status(Response.Status.ACCEPTED).build();
    }

    @PUT
    @Consumes({MediaType.APPLICATION_JSON})
    public Response create(Task t) {
        System.out.println(t);
        // write creating code
        return Response.status(Response.Status.ACCEPTED).build();
    }

To allow server to accept a request's body as JSON, just use Consumes. When response no body, you might want to use ResponseBuilder.

Let's run.

$ curl -XPOST -i -H "Content-Type: application/json" 'localhost:8080/task' -d'{"description":"", "priority":1, "untilDate":""}'
HTTP/1.1 202 Accepted
Date: Mon, 14 Aug 2017 10:42:34 GMT
Content-Length: 0
Server: Jetty(9.3.3.v20150827)

$ curl -XPUT -i -H "Content-Type: application/json" 'localhost:8080/task' -d'{"description":"", "priority":1, "until_date":""}'
HTTP/1.1 202 Accepted
Date: Mon, 14 Aug 2017 10:43:45 GMT
Content-Length: 0
Server: Jetty(9.3.3.v20150827)

$ curl -XDELETE -i -H "Content-Type: application/json" 'localhost:8080/task'
HTTP/1.1 202 Accepted
Date: Mon, 14 Aug 2017 10:43:55 GMT
Content-Length: 0
Server: Jetty(9.3.3.v20150827)

To deal with parameter in request path, use @Path and @PathParam annotations.

    @GET
	@Path("{id}") // match a value of PathParam
    @Produces({MediaType.APPLICATION_JSON})
    public Task[] get(@PathParam("id") String id) {
        return new Task[]{new Task("sample", 0, "2017/08/10")};
    }

Now we can get id by url like "localhost:8080/task/0" and 0 is set at argument id.

JAXB tip

Sometimes field names in Java is not appropriate for JSON convention such as mixup between snake_case and camelCase. At that time, use XmlElement annotation to change field name as JSON field.

    @XmlElement(name = "until_date")
    private String untilDate;

If it returns as JSArray, just take a []

    @GET
    @Produces({MediaType.APPLICATION_JSON})
    public Task[] gets() {
        return new Task[]{new Task("sample", 0, "2017/08/10")};
    }

Test web server using by DI (Dependency Injection)

Preps in server

When we want to test only a web server instead of Database and other library depended by middleware, There's effective technique called Dependency Injection. In this project, we use hk2 (JSR-330) library. There's another popular library at DI in Java, guice in google and usually popular Web Frameworks have a feature like that nowadays.

Firstly, we seperate Database access into Service interface and implement class. Meanwhile we're using @Contract annotation in Service interface and @Service annotation in implementation class. In resource, we get data through this service class and put a @Inject annotation with service field.

@Contract
interface TaskService {
   Task get(String id);
}
@Service
interface TaskServiceImpl {
   Task get(String id) {
      return new Task();
   }
}
@Path("task")
public class TaskResource {
   @Inject
   TaskService taskService;
   ...
}

Furthermore Resource configuration class is neccesarry.

@ApplicationPath("rest")
public class TaskConfig extends ResourceConfig {

    @Inject
    public TaskConfig(ServiceLocator locator) {
        DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class);
        DynamicConfiguration config = dcs.createDynamicConfiguration();

        config.bind(BuilderHelper.link(TaskRepositoryInMem.class).to(TaskRepository.class).build());

        config.commit();

        packages(true, getClass().getPackage().getName());
    }
}

Finally, modify web.xml(src/webapp/WEB-INF) to ensure this configuration.

<init-param>
  <param-name>javax.ws.rs.Application</param-name>
  <param-value>io.github.iwag.jerseystarter.main.TaskConfig</param-value>
</init-param>

Server side test with DI

Now We can create test by using DI service and contract. We can inject Service implementation in test like below.

public class TestTask extends JerseyTest {

    @Service
    static class TaskServiceMock implements TaskService {
        static UserEntity userEntity = null;

        public TaskServiceMock() {
        }

        // implement these for test
    }

    @Override
    protected Application configure() {
        return new ResourceConfig(TaskResource.class).register(new AbstractBinder() {
            @Override
            protected void configure() {
                bind(TaskServiceMock.class).to(TaskService.class);
            }
        });
    }

    @Test
    public testGetTask0() {
    ...
    }

Simple Client for CRUD

We'd like to follow the Repository Pattern. It gives many benefits so easily to create test case and hide some environment specicic details. Make all CRUD methods in Repository interface.

interface TaskRepository {
    Task[] gets();
    Task get(String id);
    boolean create(Task t);
    boolean update(String id, Task t);
    boolean delete(String id);
}

Implement ApiTaskRepository class with the interface. Here we need to implement using jersey client and call API server.

public class ApiTaskRepository implements TaskRepository {
    @Override
    public Task[] gets() {
        // Write API requesting code
        return new Task[0];
    }

    @Override
    public Task get(String id) {
        // Write API requesting code
        return null;
    }

    @Override
    public boolean create(Task t) {
        // Write API requesting code
        return true;
    }

    @Override
    public boolean update(String id, Task t) {
        // Write API requesting code
        return true;
    }

    @Override
    public boolean delete(String id) {
        // Write API requesting code
        return true;
    }
}

Lets create test case at first. In addition, this test utilizes technique so called mocking.

public class TaskTest extends Mockito {
  static Client mockClient(Response.Status status, Task t) {
    // see  https://github.com/iwag/java-jersey-restful-server-client-sample/blob/55e07a8de20f9f37624cfa8699b5ad245e33424b/projclient/src/test/java/io/github/iwag/jerseystarter/TaskTest.java#L18
    ...
  }

  @Test
  public void testGetTask0_OK() {
      Task expected = new Task();
      Client clientMock = mockClient(Response.Status.OK, expected);
      Task t = new ApiTaskRepository(clientMock).get("0");
      Assert.assertEquals(t, expected);
  }

If you run this test, you can see a failure.

Second of all, set some constant parameters in constructor. Client is a class to request an API server which can use one more times so it should be a field of class.

public class ApiTaskRepository implements TaskRepository {
    private final String baseUrl = "http://localhost:8080";
    private final Client client;

    public ApiTaskRepository() {
        // create client instance
        client = ClientBuilder.newClient();
    }

Lets create GET api. Write following code in ApiTaskRepository.java.

@Override
public Task get(String id) {
    // make web target by url
    WebTarget webTarget = client.target(baseUrl).path("/task/" + id);

    Response res = null;
    // request by GET method without body
    res = webTarget.request(MediaType.TEXT_PLAIN).accept("application/json").get();
    // check response. For now we're checking only status code.
    if (res.getStatus() != Response.Status.OK.getStatusCode()) {
        return null;
    }
    // translate response body into Task class
    Task task = res.readEntity(Task.class);

    return task;
}

Now, you can see green light when executing a test.

For API request, there are two important things to make sure, path and response. Path is ensured in target("http://localhost:8080").path("/task/" + id) and we set Task.class in .get(Task.class) to map Task class then we receive a response as Task class.

How about POST method?

@Test
public void testCreate_OK() {
    Client clientMock = mockClient(Response.Status.ACCEPTED, null);
    boolean success = new ApiTaskRepository(clientMock).create(new Task(null, "test", 0, "2017/08/15"));
    Assert.assertTrue(success);
}
@Override
public boolean create(Task t) {
    WebTarget webTarget = client.target(baseUrl).path("/task");

    Response res = null;
    Entity<?> entity = Entity.entity(t, MediaType.APPLICATION_JSON); // convert class to accept by jersey
    res = webTarget.request(MediaType.APPLICATION_JSON).accept("application/json").post(entity);
    // check if getting correct status code (ACCEPTED)
    if (res.getStatus() != Response.Status.ACCEPTED.getStatusCode()) {
        return false;
    }
    return true;
}

Finally, introduce Main.java in client side.

public class Main {

    public static void main(String[] args) throws java.lang.Exception {
        ApiTaskRepository taskRepository = new ApiTaskRepository();
        System.out.println("GET /task/0 " + taskRepository.get("0"));
        System.out.println("PUT /task " + taskRepository.create(new Task(null, "aaa", 1, "2017/08/14")));
        return ;
    }
}

Run server program(see above instruction).

Along with that, let's run client program by launching IntelliJ Run as ... at Main.java. It will show following output.

GET /task/0 Task{description='sample', priority=0, untilDate='2017/08/10'}
PUT /task true

Process finished with exit code 0