Spring 5 REST / Graph DB : Part 2 – Simple RestController

Pr-requisite : http://www.sandeeprao.net/index.php/2018/04/22/spring-5-rest-arango-graphdb-basic-poc-from-scratch/

It is possible to have the web app deployed in a container without xml configurations (web.xml) and the traditional dispatcher servlet configurations.  Extending AbstractAnnotationConfigDispatcherServletInitializer and providing the implementations for the following should suffice.

getRootConfigClasses() — for “root” application context (non-web infrastructure) configuration.

getServletConfigClasses() — for DispatcherServlet application context (Spring MVC infrastructure) configuration

Had decided to have a package separately for configs with the implementations.

ApplicationConfig.java – Instruct the servlet container to load REST resources from a specific package.

package in.dibs.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "in.dibs")
public class ApplicationConfig {
}

ApplicationInitializer.java – initializer class which is loaded at the startup of the application, it defines the configuration class of the application along with the context url.

package in.dibs.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Clasas[] { ApplicationConfig.class };
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    // No views/JSP for now
    return null;
  }

  @Override
  protected String[] getServletMappings() {
    return new String[] { "/api/*" };
  }
}

Let us have all the message constants at one place so that it becomes easier to handle internationalization – For now just a simple constants package.

APIConstants.java – A simple place holder for constants

package in.dibs.constants;

import java.util.HashMap;
import java.util.Map;

public class APIConstants {
  private static Map<Integer, String> messages = new HashMap<Integer, String>();
  // 0 to 999 Common
  public static final int FAILED = 0;
  public static final int SUCCESS = 1;
  public static final int DATA_NOT_FOUND = 2;
  public static final int DATA_READ_FAILED = 3;
  public static final int INVALID_INPUT = 4;

  // 1000 to 1999 Tenant
  public static final int TENANT_CREATED = 1000;

  // TODO : Move to external resource file
  public static final String MSG_NOT_FOUND = "Matching data for the provided ID not found.";
  public static final String MSG_READ_FAILED = "Error while reading from data store.";
  public static final String MSG_INVALID_INPUT = "One or more information required as input not provided";

  public static final String MSG_TENANT_CREATED = "Tenant : %s, persisted in data store";
  
  static {
    messages.put(DATA_NOT_FOUND, MSG_NOT_FOUND);
    messages.put(DATA_READ_FAILED, MSG_READ_FAILED);
    messages.put(INVALID_INPUT, MSG_INVALID_INPUT);
  }

  public static String getMessage(int code) {
    if (messages.containsKey(code)) {
      return messages.get(code);
    } else {
      return "Unknown status";
    }
  }
}

Placing all entities in the project in appropriate package

Tenant.java – A simple entity to start with

package in.dibs.entity;

public class Tenant {
  private String name;
  private String id;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getId() {
    return id;
  }

  public void setId(String id) {
    this.id = id;
  }

  @Override
  public String toString() {
    return "Tenant [name=" + name + ", id=" + id + "]";
  }
}

Placing all the REST API implementations in separate package


APIResponse.java – It would be better to have all API return response in standard format.  Just defining a basic standard

 

package in.dibs.api;

import in.dibs.constants.APIConstants;

public class APIResponse<T> {
  private int status;
  private String message;
  private String systemMessage;
  private T responseObject;

 /**
  * Success scenario with no response object expected.
  * 
  * @param code
  * API operation result code.
  */
  public APIResponse(int code) {
    this.status = code;
    this.message = APIConstants.getMessage(code);
    this.systemMessage = "NA";
    this.responseObject = null;
  }

 /**
  * Success scenario with response object expected. Message to be set by caller.
  * 
  * @param code
  * API Operation result code.
  * @param responseObject
  */
  public APIResponse(int code, T responseObject) {
   this.status = code;
   this.systemMessage = "NA";
   this.responseObject = responseObject;
  }

 /**
  * RT exception occurred.
  * 
  * @param systemMessage
  * Exception from underlying system / framework / library
  */
  public APIResponse(String systemMessage) {
    this.status = APIConstants.FAILED;
    this.message = APIConstants.getMessage(this.status);
    this.systemMessage = systemMessage;
    this.responseObject = null;
  }

 /**
  * Application exception occurred.
  * 
  * @param code
  * API Operation result code.
  * @param systemMessage
  * Associated RT Exception message if any.
  * @param responseObject
  * Response object to be passed on.
  */
  public APIResponse(int code, String systemMessage, T responseObject) {
    this.status = code;
    this.message = APIConstants.getMessage(code);
    this.systemMessage = systemMessage;
    this.responseObject = responseObject;
  }

  public int getStatus() {
    return status;
  }

  public void setStatus(int status) {
    this.status = status;
  }

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }

  public Object getResponseObject() {
    return responseObject;
  }

  public void setResponseObject(T responseObject) {
    this.responseObject = responseObject;
  }

  public String getSystemMessage() {
    return systemMessage;
  }

  public void setSystemMessage(String systemMessage) {
    this.systemMessage = systemMessage;
  }

  @Override
  public String toString() {
    return "APIResponse [status=" + status + ", message=" + message + ", systemMessage=" + systemMessage
    + ", responseObject=" + responseObject.toString() + "]";
  }
}

TenantAPI.java – A Simple REST API

package in.dibs.api;

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import in.dibs.constants.APIConstants;
import in.dibs.entity.Tenant;

@RestController
@RequestMapping("/tenant")
public class TenantAPI {
  @RequestMapping(method = RequestMethod.POST)
  public APIResponse<Tenant> createTenant(@RequestBody Tenant tenant) {
    // For now just a success response
    APIResponse<Tenant> response = new APIResponse(APIConstants.TENANT_CREATED, tenant);
    response.setMessage(String.format(APIConstants.MSG_TENANT_CREATED, tenant.getName()));
    return response;
  }
}

Now build the war file

Now the war file is built.  Let us deploy it as Tomcat webapp and start Tomcat.

Testing the API functionality from Postman

At this point we have the base code to start developing REST APIs.  However our objective is the have REST APIs exposed for CRUD operations to persist/fetch from GraphDB.  So let us proceed with having GraphDB persistence layer added in the next part.