Saturday 18 July 2015

PART 3: Spring MVC web-services (Validating Requests Using JSR 303)

Image: Stuart Miles

In the last post, this series identified the required steps to support persistence using Spring Data JPA in API powered by Spring MVC. It taught us that users can make a request using JSON representation of some java bean in our application. Then they expect that their requests trigger some actions (e.g. singing up a user) and that the API will return adequate responses. 
However, how can the API make sure that the request is valid in the first place?


Example of Invalid Request

A client of the signup webservice we wrote in the last post might make this request
curl -H "Content-Type: application/json; charset=utf-8" --data '{"email":"post", "password":"ww"}' http://localhost:8080/services/1/user
So what is exactly wrong with that request. Well, all three fields are invalid:
  • email format is not correct.
  • username  is null
  • password is too short
Hence, we need a code somewhere in our application to add constrains on data been provided by users.

JSR 303: BEAN VALIDATION

Throughout the application from presentation layer to persistence layer developers need to validate date. JSR-303 provided a framework to validate beans using metadata model (annotations). This save us from writing the validation logic ourselves. It, also, provide us with a chance to avoid duplicating this validation code in every layer of our application. It separates the concerns leaving the pojo classes free of cluttered validation code. This project will use Hibernate validator as implantation of JSR-303


Dependancy

In CoffeeBeansRest/pom.xml add the following dependancy 

    org.hibernate
    hibernate-validator
    5.1.3.Final


Validator Bean

In Service/com.coffeebeans.config.ServicesConfig add the following bean
@Bean
public Validator validator() {
    return new LocalValidatorFactoryBean();
}


Annotation entity classes

Now, change com.coffeebeans.domain.request.user.UserSignupRequest to be as following
package com.coffeebeans.domain.request.user;

import lombok.Data;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotNull;

/**
 * Created by muhamadto on 12/07/2015.
 */

@Data
public class UserSignupRequest {

    @NotNull @Email
    private String email;

    @NotNull
    private String username;

    @NotNull @Length(min=8, max=16)
    private String password;
}
This class showed us 3 validation annotation
  1. javax.validation.constraints.NotNull, which means that the  client must provide a value for this field.
  2. org.hibernate.validator.constraints.Length, using this annotation we can specify min and max length of a field (e.g. password).
  3. org.hibernate.validator.constraints.Email, makes sure that the supplied value for the field annotate with it is a valid email format.
NOTE: It would make sense to add length check on the username and email among other checks. However, covering the annotations in JSR303 is not the goal of this post. The scope of this lesson is rather, to explain how data validation can be enabled in Spring RESTful API.

Create validation Exception

This exception is going to be returned to the services' clients if the requests were invalid.
package com.coffeebeans.exception.validation;

import com.coffeebeans.api.error.ValidationError;

import javax.validation.ConstraintViolation;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * Created by muhamadto on 12/07/2015.
 */
public class ValidationException extends RuntimeException{
    private List<validationerror> validationErrors = new ArrayList<validationerror>();
    public ValidationException (){
        super();
    }

    public ValidationException (String message){
        super(message);
    }

    public ValidationException(String message, Throwable cause) {
        super(message, cause);
    }
    public ValidationException(Throwable cause) {
        super(cause);
    }

    protected ValidationException(String message, Throwable cause,
                                  boolean enableSuppression,
                                  boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    public ValidationException(Set> constraintViolations) {
        Iterator> iterator = constraintViolations.iterator();

        while(iterator.hasNext()){
            ConstraintViolation invalidConstraint = iterator.next();
            ValidationError validationError = new ValidationError();
            validationError.setMessage(invalidConstraint.getMessage());
            validationError.setPropertyName(String.valueOf(invalidConstraint.getPropertyPath()));
            validationError.setPropertyValue(String.valueOf(invalidConstraint.getInvalidValue()));
            validationErrors.add(validationError);
        }
    }

    public List<validationerror> getValidationErrors() {
        return validationErrors;
    }
}
This Exception class uses another class that comprises of:
  • Name of the field with invalid value;
  • The value that violated the constrains 
  • custom message
package com.coffeebeans.api.error;

import lombok.Data;

/**
 * Created by muhamadto on 12/07/2015.
 */
public @Data
class ValidationError {
    private String propertyName;
    private String propertyValue;
    private String message;
}


Use Validator Bean

com.coffeebeans.service.base.BaseService is a good place to write a generic method to validate if data is violating the constrains dictate through the metadata.
@Autowired
Validator validator;

protected void validate(Object request) throws ValidationException{
    Set<? extends ConstraintViolation<?>> constraintViolations = validator.validate(request);
    if (!CollectionUtils.isEmpty(constraintViolations)) {
        throw new ValidationException(constraintViolations);
    }
}
The app autowired the validator bean and used it to check the data.


Perform the validation on requests

As an example, let's validate the UserSignupRequest from last post. Make a call to com.coffeebeans.service.base.BaseService#validate() in the first line of com.coffeebeans.service.user.UserServiceImpl#createUser(). The logic should be like this.
@Override
@Transactional
public UserResponse createUser(UserSignupRequest userSignupRequest, Role role) {
    validate(userSignupRequest);

    UserResponse newUser;

    final String email = userSignupRequest.getEmail().toLowerCase();
    if(this.userRepository.findByEmail(email) == null){
        LOGGER.info("User does not  exist - creating a new user [{}].", userSignupRequest.getUsername());

        User user = new User(userSignupRequest);
        user.setRole(role);
        Date now = new Date();

        user.setInsertDate(now);
        user.setLastModified(now);

        user = this.userRepository.save(user);

        newUser = UserResponse.convert(user);

        LOGGER.debug("Created new user [{}].", user);
    } else{
        LOGGER.error("Duplicate user located [{}], exception raised with appropriate HTTP response code.", email);
        throw new DuplicateEntryException("User already exists.");
    }
    return newUser;
}


Exception Handling

In com.coffeebeans.controller.base.BaseController add the following snippet.
@ResponseBody
@ExceptionHandler(ValidationException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
ErrorResponse handleException(ValidationException e){
    return new ErrorResponse(HttpStatus.BAD_REQUEST.toString(), e.getMessage(), "Bad Request", e.getValidationErrors());
}
The com.coffeebeans.api.error.ErrorResponse now has a new constructor which takes additional list of the validation errors.
package com.coffeebeans.api.error;

import lombok.Data;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by muhamadto on 12/07/2015.
 */
@Data
public class ErrorResponse {

    private String errorCode;
    private String consumerMessage;
    private String applicationMessage;
    private List<validationerror> validationErrors = new ArrayList<>();

    public ErrorResponse(){}

    public ErrorResponse(String errorCode, String consumerMessage,  String applicationMessage){
        this(errorCode, consumerMessage, applicationMessage, null);
    }

    public ErrorResponse(String errorCode, String consumerMessage,  String applicationMessage, List<validationerror> validationErrors){
        this.errorCode = errorCode;
        this.consumerMessage = consumerMessage;
        this.applicationMessage = applicationMessage;
        this.validationErrors = validationErrors;
    }
}


Using the invalid request again

Try to send this request at the begging of this post again

  • Request
  • curl -H "Content-Type: application/json; charset=utf-8" --data '{"email":"post", "password":"ww"}' http://localhost:8080/services/1/user
  • Response
  • {
        "errorCode": "400"
        "consumerMessage"": null
        "applicationMessage": "Bad Request"
        "validationErrors":[
            {
                "propertyName": "email"
                "propertyValue": "post"
                "message": "not a well-formed email address"
            },{
                "propertyName": "password"
                "propertyValue": "ww"
                "message": "length must be between 8 and 16"
            },{
                "propertyName": "username"
                "propertyValue: "null"
                "message": "may not be null"
            }
        ]
    }


What is next

In this post we learned how to validate data which was provided by client. In the next post, this series will navigate how to cache API output.

Monday 13 July 2015

PART 2: Spring MVC web-service (Supporting Spring Data JPA)

The last post outlined how to use Spring MVC to kick-start a RESTful web-services application. This post will build upon what we learned from last lesson by building persistence mechanism using Spring Data JPA. Download the code of this project from Github.


WHAT TECHNOLOGIES WILL BE USED?

  • MySQL
  • Spring Data JPA
  • BoneCP (JDBC Connection Pool)
  • Hibernate (as JPA Vendor)
  • Ehcache

DEPendancies

In Persistence/pom.xml we will need to add the dependancies required for Spring Data JPA.
    
        4.3.5.Final
    

    
        
            org.springframework.boot
            spring-boot-starter-data-jpa
            1.8.1.RELEASE
        

        
            org.hibernate
            hibernate-entitymanager
            ${hibernate}
        

        
            org.hibernate
            hibernate-ehcache
            ${hibernate}
            
                
                    net.sf.ehcache
                    ehcache-core
                
            
        

        
            net.sf.ehcache
            ehcache
            2.7.0
        

        
            mysql
            mysql-connector-java
            5.1.22
            runtime
        

        
            com.jolbox
            bonecp
            0.7.1.RELEASE
        
    
Now, what did all that mean? These dependancies instruct the application to use:
  1. Spring Data JPA module.
  2. Hibernate as JPA vendor. The usage of HibernateJpaVendorAdapter ensure that our application will not require persistence.xml among many other benefits like support for supports the detection of annotated packages
  3. Hibernate Ehcache module but excludes the core module as hibernate uses version and no Ehcache has newer versions 
  4. BoneCP as connection pool, I personally find it fast. However, it seems that the project is discontinued. So, it may be time to think about an alternative Maybe Tomcat JDBC.
  5. mysql database (You may delete this dependancy and place the connector in <%CATALINA_HOME%>/lib)


Configuration

To configure Spring in persistence we will need to define a new java config file, and register this config class to root context in com.coffeebeans.initializer.WebAppInitializer  

Config Class

package com.coffeebeans.config;

import com.coffeebeans.utils.Constants;
import com.jolbox.bonecp.BoneCPDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;

/**
 * Created by muhamadto on 12/07/2015.
 */

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.coffeebeans.repository")
@PropertySource("classpath:default.properties")
public class PersistenceConfig {
    @Autowired
    Environment env;

    @Bean(destroyMethod = "close")
    public DataSource dataSource() {
        BoneCPDataSource dataSource = new BoneCPDataSource();
        dataSource.setJdbcUrl(System.getProperty(Constants.DB_URL, Constants.LOCALHOST));
        dataSource.setUsername(System.getProperty(Constants.DB_USERNAME));
        dataSource.setPassword(System.getProperty(Constants.DB_PASSWORD));
        dataSource.setIdleConnectionTestPeriodInMinutes(Integer.parseInt(env.getProperty(Constants.BONECP_IDLE_CONNECTION_TEST_PERIOD_IN_MINUTES)));
        dataSource.setIdleMaxAgeInMinutes(Integer.parseInt(env.getProperty(Constants.BONECP_IDLE_MAX_AGE_IN_MINUTES)));
        dataSource.setMaxConnectionsPerPartition(Integer.parseInt(env.getProperty(Constants.BONECP_MAX_CONNECTIONS_PER_PARTITION)));
        dataSource.setMinConnectionsPerPartition(Integer.parseInt(env.getProperty(Constants.BONECP_MIN_CONNECTIONS_PER_PARTITION)));
        dataSource.setPartitionCount(Integer.parseInt(env.getProperty(Constants.BONECP_PARTITION_COUNT)));
        dataSource.setAcquireIncrement(Integer.parseInt(env.getProperty(Constants.BONECP_ACQUIRE_INCREMENT))) ;
        dataSource.setStatementsCacheSize( Integer.parseInt(env.getProperty(Constants.BONECP_STATEMENTS_CACHE_SIZE)));
        return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource());
        em.setPackagesToScan(env.getProperty(Constants.COFFEEBEANS_MODEL_PACKAGE));
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        em.setJpaProperties(additionalProperties());
        return em;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);

        return transactionManager;
    }

    private Properties additionalProperties() {
        Properties prop = new Properties();
        prop.setProperty(Constants.HIBERNATE_CACHE_USE_SECOND_LEVEL_CACHE, env.getProperty(Constants.HIBERNATE_CACHE_USE_SECOND_LEVEL_CACHE));
        prop.setProperty(Constants.HIBERNATE_CACHE_REGION_FACTORY_CLASS, env.getProperty(Constants.HIBERNATE_CACHE_REGION_FACTORY_CLASS));
        prop.setProperty(Constants.HIBERNATE_CACHE_USE_QUERY_CACHE, env.getProperty(Constants.HIBERNATE_CACHE_USE_QUERY_CACHE));
        prop.setProperty(Constants.HIBERNATE_GENERATE_STATISTICS, env.getProperty(Constants.HIBERNATE_DIALECT));
        prop.setProperty(Constants.HIBERNATE_DIALECT, env.getProperty(Constants.HIBERNATE_DIALECT));

        return prop;
    }
}
Notice that DB_URL, DB_USERNAME, and DB_PASSWORD are jvm variables

Register The Config Class With The Root Context

In com.coffeebeans.initializer.WebAppInitializer change onStartUp
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
    rootContext.register(ServicesConfig.class,PersistenceConfig.class);
    servletContext.addListener(new ContextLoaderListener(rootContext));

    AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();
    dispatcherServlet.register(MvcConfig.class);

    ServletRegistration.Dynamic dispatcher = servletContext.addServlet("mvc-dispatcher", new DispatcherServlet(dispatcherServlet));
    dispatcher.setLoadOnStartup(1);
    dispatcher.addMapping("/1/*");

    FilterRegistration charEncodingFilterReg = servletContext.addFilter("CharacterEncodingFilter", CharacterEncodingFilter.class);
    charEncodingFilterReg.setInitParameter("encoding", "UTF-8");
    charEncodingFilterReg.setInitParameter("forceEncoding", "true");
    charEncodingFilterReg.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, "/*");
}

PersistenceConfig is annotated with:
  1. org.springframework.context.annotation.Configuration, which signals that this class is a Spring config class
  2. org.springframework.transaction.annotation.EnableTransactionManagement, to enable Spring's annotation-driven transaction management capability, similar to the support found in Spring's <tx:*> XML namespace
  3. org.springframework.data.jpa.repository.config.EnableJpaRepositories, to enable and activate Spring Data JPA repositories as the name suggests  and to provide information on which base package will contain the repositories, in our example basePackages = "com.coffeebeans.repository".If no base package is configured it will use the one the configuration class resides in. In other work, Spring will scan for JPA repositories in the provided package to sire them later in a client that might depend on them.
  4. org.springframework.context.annotation.PropertySource, declarative way for adding properties to Spring environment. In our case these properties define in default.properties file.
It defines the following beans:
  1. DataSource bean of type BoneCPDataSource. It uses a connection pool to the data base. In this example we use DB url, username and password. In a production environment it would be better to create a data source in the container (e.g. tomcat -> contaxt.xml) and look it up using JNDI. However, this will need web.xml to be define because Servlet 3 Java API does not have replacement for <resource-ref/>.
  2. EntityManagerFactory bean of type  LocalContainerEntityManagerFactoryBean. It creates JPA EntityManagerFactory and provide ability to pass it to JPA-based DAOs using dependancy injectionThis FactoryBean is more flexible in that you can override the location of the persistence.xml file. However, in Spring 4 we can instruct the code to use Spring-based scanning for entity classes in the classpath instead of JPA's approach which uses persistence.xml. This can be done by calling setPackagesToScan() on the LocalContainerEntityManagerFactoryBean.
  3. TransactionManager bean of type PlatformTransactionManager, for controlling transactions. 
Remember default.properties?!  Here's it. In Persistence module add default.properties in src/main/resources
################### DataSource Configuration ##########################

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.dataSource=java:comp/env/jdbc/Rest

init-db=false

################### Hibernate Configuration ##########################

hibernate.show_sql=false
hibernate.hbm2ddl.auto=update
hibernate.generateDdl=false
hibernate.cache.use_second_level_cache=true
hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
hibernate.cache.use_query_cache=true
hibernate.generate_statistics=true
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

############################### BoneCP ##############################

bonecp.driverClass=com.mysql.jdbc.Driver
bonecp.dataSource=java:comp/env/jdbc/Rest
bonecp.idleMaxAgeInMinutes=240
bonecp.idleConnectionTestPeriodInMinutes=60
bonecp.maxConnectionsPerPartition=10
bonecp.minConnectionsPerPartition=1
bonecp.partitionCount=2
bonecp.acquireIncrement=5
bonecp.statementsCacheSize=100

############################ Persistence ###########################

repository.package=com.coffeebeans.repository
model.package=com.coffeebeans.domain.model

and add com.coffeebeans.utils.Constants, to define the identifiers of the properties as java constants.
package com.coffeebeans.utils;

/**
 * Created by muhamadto on 12/07/2015.
 */
public class Constants {
    public static final String COFFEEBEANS_REPOSITORY_PACKAGE = "repository.package";
    public static final String COFFEEBEANS_MODEL_PACKAGE = "model.package";
    public static final String LOCALHOST = "localhost";

    public static final String JDBC_DRIVER_CLASS = "jdbc.driverClass";
    public static final String DB_URL = "DB_URL";
    public static final String DB_USERNAME = "DB_USERNAME";
    public static final String DB_PASSWORD = "DB_PASSWORD";

    public static final String BONECP_IDLE_CONNECTION_TEST_PERIOD_IN_MINUTES = "bonecp.idleConnectionTestPeriodInMinutes";
    public static final String BONECP_IDLE_MAX_AGE_IN_MINUTES = "bonecp.idleMaxAgeInMinutes";
    public static final String BONECP_MAX_CONNECTIONS_PER_PARTITION = "bonecp.maxConnectionsPerPartition";
    public static final String BONECP_MIN_CONNECTIONS_PER_PARTITION = "bonecp.minConnectionsPerPartition";
    public static final String BONECP_PARTITION_COUNT = "bonecp.partitionCount";
    public static final String BONECP_ACQUIRE_INCREMENT = "bonecp.acquireIncrement";
    public static final String BONECP_STATEMENTS_CACHE_SIZE = "bonecp.statementsCacheSize";

    public static final String HIBERNATE_CACHE_USE_SECOND_LEVEL_CACHE = "hibernate.cache.use_second_level_cache";
    public static final String HIBERNATE_CACHE_REGION_FACTORY_CLASS = "hibernate.cache.region.factory_class";
    public static final String HIBERNATE_CACHE_USE_QUERY_CACHE = "hibernate.cache.use_query_cache";
    public static final String HIBERNATE_GENERATE_STATISTICS = "hibernate.generate_statistics";
    public static final String HIBERNATE_DIALECT = "hibernate.dialect";
}

By registering PersistenceConfig with rootContext in com.coffeebeans.initializer.WebAppInitializer#onStartup(), Spring Data JPA is component is fully configured. Now, lets add entities and repositories.


Entity Classes

Entity class are in com.coffeebeans.domain.model package as we previously specified using LocalContainerEntityManagerFactoryBean#setPackagesToScan() in com.coffeebeans.config.PersistenceConfig.

Define a super class for entity classes

import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.AbstractPersistable;
import org.springframework.util.Assert;

import javax.persistence.*;
import java.util.Date;
import java.util.UUID;

/**
 * Created by muhamadto on 12/07/2015.
 */

@MappedSuperclass
@Data
public class DomainObject extends AbstractPersistable <long > {

    @Version
    protected int version;

    @Column(length = 36, unique = true, nullable = false)
    protected String uuid;

    @CreatedDate
    protected Date insertDate;

    @LastModifiedDate
    protected Date lastModified;

    public DomainObject() {
        this(UUID.randomUUID());
    }

    public DomainObject(UUID uuid) {
        Assert.notNull(uuid, "UUID is required");
        this.uuid = uuid.toString();
        this.insertDate = new Date();
    }
}
Let's inspect this class closely:
  • DomainObject class inherts from  org.springframework.data.jpa.domain.AbstractPersistable which ultimitly inherets Serializable and add mapping for id property. 
  • DomainObject class is annotate with javax.persistence.MappedSuperclass, which means that the mappings for its fields apply only to its subclasses.
  • Another annotation also is lombok.Data, This saves us from writing boilerplate code for setters, getters, default constructor, ... etc.
  • It contains the general fields that all entity classes must have, like inserDate, id (which it inherits from AbstractPersistable) and version.

Define User entity class

package com.coffeebeans.domain.model.user;

import com.coffeebeans.domain.model.base.DomainObject;
import com.coffeebeans.domain.request.user.UserSignupRequest;
import lombok.Data;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.Cache;

import javax.persistence.*;
import java.util.UUID;

/**
 * Created by mohamedhamtou on 28/12/14.
 */
@Entity
@Table(name="USER")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "user")
@Data
public class User extends DomainObject {
    @Column(length = 36, unique = true, nullable = false)
    protected String username;

    @Column(length = 36, unique = true, nullable = false)
    protected String email;

    @Column(length = 16, nullable = false)
    protected String password;

    @Type(type = "numeric_boolean")
    protected boolean enabled;
    
    @Type(type = "numeric_boolean")
    protected boolean verified = false;

    @Enumerated(EnumType.STRING)
    protected Role role;
    
    public User() {
        this(UUID.randomUUID());
    }

    public User(UUID uuid) {
        super(uuid);
    }

    public User(String username, String email, String encryptedPassword) {
        super(UUID.randomUUID());
        this.username = username.toLowerCase();
        this.email = email.toLowerCase();
        this.password = encryptedPassword;
    }

    public User(UserSignupRequest userSignupRequest) {
        super(UUID.randomUUID());
        this.username = userSignupRequest.getUsername().toLowerCase();
        this.email = userSignupRequest.getEmail().toLowerCase();
        this.password = userSignupRequest.getPassword();
    }
}
Important things to notice:
  • It's annotated with javax.persistence.Entity
  • Its corresponding table called USER, we know that through the annotation javax.persistence.Table
  • org.hibernate.annotations.Cache, to add caching strategy, region attribute refers to cache in ehcache.xml
The user role is define in com.coffeebeans.domain.model.user.Role
package com.coffeebeans.domain.model.user;

/**
 * Created by muhamadto on 5/07/2015.
 */
public enum Role {

    ROLE_ANONYMOUS("ROLE_ANONYMOUS"),
    ROLE_USER("ROLE_USER"),
    ROLE_ADMIN("ROLE_ADMIN");

    private String userAuthorityType;
    private Role(String userAuthorityType){
        this.userAuthorityType= userAuthorityType;
    }

    public String toString() {
        return userAuthorityType;
    }
}

DataBase

CREATE DATABASE `Rest`;

USE `Rest`;

CREATE TABLE `USER` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `insertDate` datetime NOT NULL,
  `lastModified` datetime NOT NULL,
  `uuid` varchar(36) NOT NULL,
  `version` int(11) NOT NULL,
  `email` varchar(36) NOT NULL,
  `enabled` bit(1) NOT NULL,
  `verified` bit(1) NOT NULL,
  `password` varchar(16) NOT NULL,
  `role` varchar(25) NOT NULL,
  `username` varchar(36) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uuid` (`uuid`),
  UNIQUE KEY `username_UNIQUE` (`username`),
  UNIQUE KEY `email_UNIQUE` (`email`),
  UNIQUE KEY `id_UNIQUE` (`id`)
);

CACHING

For now we will focus on 2nd level caching of hibernate. This can be done by:
  • Add src/main/resources/ehcache.xml in Persistence module.


    

    
        
    

    
        
    
  • Annotating entity classes (e.g. User) with org.hibernate.annotations.Cache
  • Adding the following properties to the EntityManagerFactory (please revisit PersistenceConfig#additionalProperties())
    • hibernate.cache.use_second_level_cache=true
    • hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
    • hibernate.cache.use_query_cache=true


Defining Repositories

  • Repositories live in com.coffeebeans.repository package as we previously specified using @EnableJpaRepositories(basePackages = "com.coffeebeans.repository") in com.coffeebeans.config.PersistenceConfig.
  • In Spring Data JPA, Repository interface is the central interface. It takes the the domain class (e.g. User) to manage as well as the id type of the domain class as type arguments (e.g. Long as we passed Long to AbstractPersistable while defining DomainObject super class). This Repository interface acts as a marker interface to capture the types to work with. 
  • The CrudRepository that inserts Repository adds sophisticated CRUD functionality for the entity class that is being managed
  • PagingAndSortingRepository adds methods to facilitate paginated access to entities.
  • Then persistence technology specific sub-interfaces to include additional technology specific methods like JpaRepository in our example.

Define User repository interface

package com.coffeebeans.repository.user;

import com.coffeebeans.domain.model.user.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * Created by muhamadto on 12/07/2015.
 */

@Repository("userRepository")
public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
    User findByEmail(String email);
    User findByUuid(String uuid);
    User findById(Long id);
}

Before we go and build a service to use our repository we will add two additional types of classes.
  • Request class: which a client of a service will provide in the request
  • Response class: which will be returned to the service caller.

Request Class

package com.coffeebeans.domain.request.user;

import lombok.Data;

/**
 * Created by muhamadto on 12/07/2015.
 */

@Data
public class UserSignupRequest {

    private String email;

    private String username;

    private String password;
}

Response Class

package com.coffeebeans.domain.response;
import lombok.Data;

import java.net.URI;

/**
 * Created by mohamedhamtou on 20/02/15.
 */
@Data 
public class BaseResponse {
    protected URI location;
}

package com.coffeebeans.domain.response;

import com.coffeebeans.domain.model.user.User;
import com.coffeebeans.domain.response.base.BaseResponse;
import lombok.Data;

/**
 * Created by mohamedhamtou on 20/02/15.
 */
@Data
public class UserResponse extends BaseResponse{
    protected boolean enabled;
    protected boolean verified;

    protected String username;
    protected String email;

    public static UserResponse convert(User user){
        UserResponse userResponse = new UserResponse();
        
        userResponse.setUsername(user.getUsername());
        userResponse.setEmail(user.getEmail());
        userResponse.setVerified(user.isVerified());
        userResponse.setEnabled(user.isEnabled());

        return userResponse;
    }
}
Now persistence module is ready to be used. So, let's build a service to use it.

BUILDING THE CLIENT

In Services module

Define A User Controller

import com.coffeebeans.controller.base.BaseController;
import com.coffeebeans.domain.model.user.Role;
import com.coffeebeans.domain.request.user.UserSignupRequest;
import com.coffeebeans.domain.response.UserResponse;
import com.coffeebeans.service.user.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import java.net.URI;

/**
 * Created by muhamadto on 12/07/2015.
 */

@RestController
@RequestMapping(value = "user", produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController extends BaseController {
    @Autowired
    private UserService userService;

    @ResponseBody
    @RequestMapping(method = RequestMethod.POST)
    public UserResponse signupUser(@RequestBody final UserSignupRequest userSignupRequest){
        UserResponse userResponse = this.userService.createUser(userSignupRequest, Role.ROLE_ANONYMOUS);
        URI location = this.buildLocationFromCurrentRequestUri(userResponse.getUsername());
        userResponse.setLocation(location);
        return userResponse;
    }

    @ResponseBody
    @RequestMapping(method = RequestMethod.GET, value = "/{username}")
    public UserResponse getMyUserDetails(@PathVariable String username) {
        UserResponse userResponse = this.userService.getUserByUsername(username);
        URI location = this.buildLocationFromCurrentServletMapping("user", userResponse.getUsername());
        userResponse.setLocation(location);
        return userResponse;
    }
}
This controllers defines to RESTful calls. 
  1. One to create a user
  2. Another to get that user by name
Both methods in this controller return UserResponse to their clients. signupUser()requires that the client of  to JSON representation of UserSignupRequest class (Curtesy of RequestBody annotation).

Define A User Service

import com.coffeebeans.domain.model.user.Role;
import com.coffeebeans.domain.request.user.UserSignupRequest;
import com.coffeebeans.domain.response.UserResponse;

/**
 * Created by muhamadto on 12/07/2015.
 */
public interface UserService {
    UserResponse createUser(final UserSignupRequest userSignupRequest, Role role);
    UserResponse getUserByUsername(String username);
}

and a class to implement this interface
package com.coffeebeans.service.user;

import com.coffeebeans.exception.general.NotFoundException;
import com.coffeebeans.repository.user.UserRepository;
import com.coffeebeans.domain.model.user.Role;
import com.coffeebeans.domain.model.user.User;
import com.coffeebeans.domain.request.user.UserSignupRequest;
import com.coffeebeans.domain.response.UserResponse;
import com.coffeebeans.exception.general.DuplicateEntryException;
import com.coffeebeans.service.base.BaseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

/**
 * Created by muhamadto on 12/07/2015.
 */

@Service("userService")
public class UserServiceImpl extends BaseService implements UserService{
    public static Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);

    @Autowired
    private UserRepository userRepository;

    @Override
    @Transactional
    public UserResponse createUser(UserSignupRequest userSignupRequest, Role role) {
        UserResponse newUser;

        final String email = userSignupRequest.getEmail().toLowerCase();
        if(this.userRepository.findByEmail(email) == null){
            LOGGER.info("User does not  exist - creating a new user [{}].", userSignupRequest.getUsername());

            User user = new User(userSignupRequest);
            user.setRole(role);
            Date now = new Date();

            user.setInsertDate(now);
            user.setLastModified(now);

            user = this.userRepository.save(user);

            newUser = UserResponse.convert(user);

            LOGGER.debug("Created new user [{}].", user);
        } else{
            LOGGER.error("Duplicate user located [{}], exception raised with appropriate HTTP response code.", email);
            throw new DuplicateEntryException("User already exists.");
        }
        return newUser;

    }

    @Override
    public UserResponse getUserByUsername(String username) {
        UserResponse userResponse;
        User user = this.userRepository.findByUsername(username);
        if(user != null){
            LOGGER.debug("User has been found [{}].", user);
            userResponse = UserResponse.convert(user);
        } else{
            LOGGER.error("Could not find a user for search criteria [{}]", username);
            throw new NotFoundException(String.format("Could not find a user for search criteria [username = %s]", username));
        }
        return userResponse;
    }
}
  • UserServiceImpl uses gets instance of UserRepository using dependancy injection. It uses this instance to save, update, and query UserUserRepository can be autowired no because of EnableJpaRepositories in PersistenceConfig class.

Exception Handling

As we saw in the UserServiceImpl, their are two exception:
  • createUser()  throws  com.coffeebeans.exception.general.DuplicateEntryException if the added user already exist 
  • getUserByUsername()  throws  com.coffeebeans.exception.general.NotFoundException if the username search did not find that user.
  • Both exceptions are subclasses from RuntimeException
package com.coffeebeans.exception.general;

/**
 * Created by mohamedhamtou on 26/01/15.
 */

public class NotFoundException extends RuntimeException {
    public NotFoundException(){
        super();
    }

    public NotFoundException(String message){
        super(message);
    }

    public NotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
    public NotFoundException(Throwable cause) {
        super(cause);
    }

    protected NotFoundException(String message, Throwable cause,
                                boolean enableSuppression,
                                boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

package com.coffeebeans.exception.general;

/**
 * Created by muhamadto on 12/07/2015.
 */
public class DuplicateEntryException extends RuntimeException {
    public DuplicateEntryException() {
        super();
    }

    public DuplicateEntryException(String message) {
        super(message);
    }

    public DuplicateEntryException(String message, Throwable cause) {
        super(message, cause);
    }

    public DuplicateEntryException(Throwable cause) {
        super(cause);
    }

    protected DuplicateEntryException(String message, Throwable cause,
                                      boolean enableSuppression,
                                      boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
So how can we handle these, add the following methods to com.coffeebeans.controller.base.BaseController
  @ResponseBody
    @ExceptionHandler(DuplicateEntryException.class)
    @ResponseStatus(value = HttpStatus.CONFLICT)
    ErrorResponse handleException(DuplicateEntryException e){
        return new ErrorResponse(HttpStatus.CONFLICT.toString(), e.getMessage(), "Duplicated User");
    }

    @ResponseBody
    @ExceptionHandler(NotFoundException.class)
    @ResponseStatus(value = HttpStatus.NOT_FOUND)
    ErrorResponse handleException(NotFoundException e){
        return new ErrorResponse(HttpStatus.NOT_FOUND.toString(), e.getMessage(), "NOT FOUND (aka Huh?)");
    }
  • com.coffeebeans.api.error.ErrorResponse is a custom error class. The usefulness of this class will be clear once we start the JSR 303: Bean Validation lesson.
    • specific message such as throw new NotFoundException(String.format("Could not find a user for search criteria [username = %s]", username));
    • General message such as NOT FOUND (aka Huh?)
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by muhamadto on 12/07/2015.
 */
@Data
public class ErrorResponse {

    private String errorCode;
    private String consumerMessage;
    private String applicationMessage;

    public ErrorResponse(){}

    public ErrorResponse(String errorCode, String consumerMessage,  String applicationMessage){
        this.errorCode = errorCode;
        this.consumerMessage = consumerMessage;
        this.applicationMessage = applicationMessage;
    }
}
Now run mvn clean install on the persistence module, then mvn clean package on Services module then depoly the war file in tomcat.


Using CURL To TEST

  • Signup user
    • Request:
      curl -H "Content-Type: application/json; charset=utf-8" --data '{"email":"post@domain.com","username":"example", "password":"password"}' http://localhost:8080/services/1/user
    • Response:
      {
          "location": "http://localhost:8080/services/1/user/example1"
          "enabled": false
          "verified": false
          "username": "example1"
          "email": "example1@domain.com"
      }
      
  • Retrieve User by Name
    • Request:
      curl http://localhost:8080/services/1/user/example1
    • Response:
      {
          "location": "http://localhost:8080/services/1/user/example1"
          "enabled": false
          "verified": false
          "username": "example1"
          "email": "example1@domain.com"
      }
The location property in the response is coming from the com.coffeebeans.domain.response.BaseResponse location property and been built through one of two methods in com.coffeebeans.controller.base.BaseController. These methods are:
    protected URI buildLocationFromCurrentRequestUri(String username){
        URI location = ServletUriComponentsBuilder.fromCurrentRequestUri().path("/").path(username).build().toUri();
        return location;
    }

    protected URI buildLocationFromCurrentServletMapping(String mapping, String username){
        URI location = ServletUriComponentsBuilder.fromCurrentServletMapping().path("/").path(mapping).path("/").path(username).build().toUri();
        return location;
    }
Now, we have a working RESTful web-services application that run Spring MVC and supports persistence using Spring Data JPA.

What is Next

Next post we will talk about bean validation.

Saturday 4 July 2015

PART 1: Spring MVC web-services (Designing RESTful API)

This is the first article in a series that will detail how to use Spring modules to build a RESTful web-service.

What technologies will be used?

  • Spring MVC
  • Spring Data JPA
  • Hibernate Validation
  • Spring Cache abstraction

What is the project layout?

This project is divided into multiple modules, it will grow as we progress in our series. However, for this post it only has two modules. These are Persistence and Services
CoffeeBeansRest
    |
    |----- Persistence
    |           |----- src
    |                    |----- main
    |                             |----- java
    |                             |----- resources
    |                    |----- test
    |                             |----- java
    |                             |----- resources
    |----- Services
    |           |----- src
    |                    |----- main
    |                             |----- java
    |                             |----- resources
    |                    |----- webapps
    |                             |----- java
    |                             |----- resources
    |                    |----- test
    |                             |----- java
    |                             |----- resources
    |
    |----- Other modules

Figure.1 project layout


Creating the project:

1. Create a parent project

In CoffeeBeansRest directory add pom.xml. In this pom file we can add all the dependancies that will be common across all modules, for instance logging or testing

    4.0.0

    com.coffeebeans
    coffeebeans
    pom
    1.0-SNAPSHOT
    
        Persistence
        Services
    

    
        <java.version>1.8</java.version>
        <spring.version>4.1.4.RELEASE</spring.version>
    

    
        
            
                maven-compiler-plugin
                
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                
            
        
    

    
        
            junit
            junit
            4.11
            test
        

        
            org.projectlombok
            lombok
            1.14.8
            provided
        

        
            ch.qos.logback
            logback-classic
            1.1.2
        

        
            ch.qos.logback
            logback-core
            1.1.2
        

Notice :
  • The value in packaging element is pom, which allows this to be parent for other projects.
  • The module element has two sub-elements telling us about the modules we intend to include in this project.


2. Create the Persistence module

  • Create a directory inside CoffeeBeansRest and call it Persistence
  • Create the usual maven layout for simple project inside it (refer Figure.1) (if you using intellij, add module choose maven then input the artifactid=persistence and then the module name=Persistence). 
  • The pom.xml in this directory contains dependancies that are related directly to the service module and are not being used anywhere else. should look like this,

    
        coffeebeans
        com.coffeebeans
        1.0-SNAPSHOT
    
    4.0.0

    persistence

The parent element that tells us that this module is part of the com.coffeebeans group and it's going to be deployed as a jar file. 


3. Create the Services module

  • Like what we did for Persistence module, create a directory inside CoffeeBeansRest and call it Services
  • Create the usual maven layout for webapp inside it (refer Figure.1) (if you using intellij, add module choose maven then input the artifactid=service and then the module name=Service). 
  • The pom.xml in this directory contains dependancies that are related directly to the service module and are not being used anywhere else. should look like this,

    
        com.coffeebeans
        coffeebeans
        1.0-SNAPSHOT
    
    4.0.0
    services
    war
    services
    1.0-SNAPSHOT

    
        <build.timestamp>${maven.build.timestamp}</build.timestamp>
    

    
        Services
        
            
                src/main/resources
                true
            
        
    
    
        
            com.coffeebeans
            persistence
            1.0-SNAPSHOT
        

        
            org.springframework
            spring-webmvc
            ${spring.version}
        

        
            javax.servlet
            javax.servlet-api
            3.0.1
            provided
        

        
            com.fasterxml.jackson.core
            jackson-core
            2.4.1
        

        
            com.fasterxml.jackson.core
            jackson-databind
            2.4.1.1
        
    


The parent element that tells us that this module is part of the com.coffeebeans group and it's going to be deployed as a war file. Also notice that the services module will depend on the persistence.

Now the lay out of the project is ready and it should look like what Figure.1 project layout.


4. Create a message file

In services/src/main/resources create messages.properties file. This file will contain the different messages that might be displayed to a user.
build.version=${project.version}
api.version=1
build.time=${build.timestamp}
It will retrieve the project.version and build.timestamp from service/pom.xml.


5. Configure logback

In services/src/main/resources create logback.xml file.

    
        
            
                [SRV] %d{HH:mm:ss.SSS} %-5level %logger - %msg%n
            
        
    
    

    
        
    
    


Initialise the webapp

Traditionally web.xml would be used to configure the ServletContext using something like this,

    mvc-dispatcher
    org.springframework.web.servlet.DispatcherServlet
    
        contextConfigLocation
        /WEB-INF/services-servlet.xml
    
    1



    mvc-dispatcher
    /1/*

However, since we are Servlet 3.0+ServletContext can be configured programmatically. To achieve that add  com.coffeebeans.initializer.WebAppInitializer class in Service module.
package com.coffeebeans.initializer;

import com.coffeebeans.config.MvcConfig;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

/**
 * Created by muhamadto on 5/07/2015.
 */
public class WebAppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();

        servletContext.addListener(new ContextLoaderListener(rootContext));

        AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();
        dispatcherServlet.register(MvcConfig.class);

        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherServlet));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/1/*");

    }
}

Here you might realised that the code created two Application contexts. Namely, rootContext and dispatcherServlet. So, what are the difference between them.
In Spring you can build multilevel application contexts, for example in a webapp there are usually root context and servlet context levels.

  • Root context contains the beans that can be used for the entire application (e.g. Spring Security). 
  • The other level which is the servlet contexts, this can be of multiple contexts for example a servlet content to serve web pages and another one to serve web-services 

This will allow the different contexts to avoid name clashes between beans. If a bean is not existing in the current servlet context. Spring will look it up in the higher level which is root context. 


Configure Spring MVC

You might realise that the WebAppInitializer#onStartup() referenced MvcConfig.classWebAppInitializer told our application that rootContext and dispatcherServlet are two contexts that will be use and it registers some config class (e.g. MvcConfig.class) to each context. In MvcConfig.class  we would tell our application to enable Spring MVC and where to find its controllers. Later we will add configuration class Spring Data JPA and other modules from Spring framework.
package com.coffeebeans.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/**
 * Created by muhamadto on 5/07/2015.
 */

@EnableWebMvc
@Configuration
@ComponentScan(basePackages = {"com.coffeebeans.controller"})
public class MvcConfig extends WebMvcConfigurerAdapter{

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource resource = new ResourceBundleMessageSource();
        resource.setBasename("messages");
        resource.setDefaultEncoding("UTF-8");
        return resource;
    }
}
@EnableWebMvc  enable Spring MVC in our app.
@ComponentScan(basePackages = {"com.coffeebeans.controller"}  instructs the app to look for controllers in com.coffeebeans.controller  package.

Building controllers

Now, let's build a controller for our API. We can build a simple thing like "Hello, world". However, it's more fun to build something useful like a controller to return the api version from the messages.properties file we created earlier. 


1. Create a class to model the version

In Persistence module create com.coffeebeans.processing.utils.Version
package com.coffeebeans.processing.utils;

import lombok.Data;

/**
 * Created by muhamadto on 5/07/2015.
 */
public @Data
class Version {
    private String buildVersion;
    private String timestamp;
    private String apiVersion;
} 

I used lombok to create setters, getters and default constructor of the class to reduce the boilerplate code.



2. Define VersionService

package com.coffeebeans.service.version;

import com.coffeebeans.service.base.BaseService;
import com.coffeebeans.api.version.ApiVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Service;

import java.util.Locale;

/**
 * Created by muhamadto on 12/07/2015.
 */

public interface VersionService{
    public ApiVersion getVersion();
}
and a class to implement this interface
package com.coffeebeans.service.version;

import com.coffeebeans.api.version.ApiVersion;
import com.coffeebeans.service.base.BaseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Service;

import java.util.Locale;

/**
 * Created by muhamadto on 19/07/2015.
 */
@Service("versionService")
public class VersionServiceImpl extends BaseService implements VersionService{
    public static Logger LOGGER = LoggerFactory.getLogger(VersionService.class);

    @Autowired
    MessageSource messageSource;

    @Override
    public ApiVersion getVersion() {
        LOGGER.debug("Getting ApiVersion of the API STARTED");
        ApiVersion version = new ApiVersion();

        String buildVersion = messageSource.getMessage("build.version", null,  Locale.getDefault());
        version.setBuildVersion(buildVersion);

        String buildTime = messageSource.getMessage("build.time", null,  Locale.getDefault());
        version.setTimestamp(buildTime);

        String apiVersion = messageSource.getMessage("api.version", null,  Locale.getDefault());
        version.setApiVersion(apiVersion);

        LOGGER.debug("API version is {}", version);
        return version;
    }
}

3. Configure the Services

Add com.coffeebeans.config.ServicesConfig
package com.coffeebeans.config;

import com.coffeebeans.service.version.VersionService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

import javax.validation.Validator;
import java.nio.charset.StandardCharsets;

/**
 * Created by muhamadto on 12/07/2015.
 */

@Configuration
@ComponentScan(basePackages = {"com.coffeebeans.service"})
public class ServicesConfig {
    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource resource = new ResourceBundleMessageSource();
        resource.setBasename("messages");
        resource.setDefaultEncoding(StandardCharsets.UTF_8.toString());
        return resource;
    }

    @Bean
    public Validator validator() {
        return new LocalValidatorFactoryBean();
    }
}
then register this config with the rootContext in com.coffeebeans.initializer.WebAppInitializer#onStartup()
rootContext.register(ServicesConfig.class);
servletContext.addListener(new ContextLoaderListener(rootContext));

4. Create BaseController interface

Before creating our controller, we need to create an abstract class to have the common functionality in it
package com.coffeebeans.controller.base;

import com.coffeebeans.api.error.ErrorResponse;
import com.coffeebeans.exception.general.DuplicateEntryException;
import com.coffeebeans.exception.general.NotFoundException;
import com.coffeebeans.exception.validation.ValidationException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;

/**
 * Created by muhamadto on 4/07/2015.
 */
public abstract class BaseController {

}

This class will be used later to add functionality like generic Exception handling.


5. Create our controller

Spring will inject the VersionService into this controller thanks to org.springframework.beans.factory.annotation.Autowired annotation.
package com.coffeebeans.controller.rest.version;

import com.coffeebeans.api.version.ApiVersion;
import com.coffeebeans.controller.base.BaseController;
import com.coffeebeans.service.version.VersionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by muhamadto on 4/07/2015.
 */

@RestController
public class ApiVersionController extends BaseController{

    @Autowired
    private VersionService versionService;

    @RequestMapping(value="version", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ApiVersion getAPIVersion() throws Exception {
        return versionService.getVersion();
    }
}

This controller will use getAPIVersion() of  VersionService to return the version of the api and it will do so as a response to restful GET call. Moreover, the returning content-type will be JSON. But, how did the application transform the version object to json format, for this automatic transformation to work. you need to add:
    1. org.springframework.web.bind.annotation.ResponseBody before the method. 
    2. Add jackson dependancies to the pom file which we did before in the services/pom.xml
In the browser go to http://localhost:8080/services/1/version, you should see something like this 
{"buildVersion":"1.0-SNAPSHOT","timestamp":"20150705-0611","apiVersion":"1"}

6. Serving JSP pages from controller

What if we need to serve JSP pages as well, to do so 
  • Add the following method to MvcConfig
 @Bean
    public InternalResourceViewResolver jspViewResolver() {
        InternalResourceViewResolver bean = new InternalResourceViewResolver();
        bean.setPrefix("/WEB-INF/jsp/");
        bean.setSuffix(".jsp");
        return bean;
    }
  • Add the following IndexController:
import com.coffeebeans.controller.base.BaseController;
import com.coffeebeans.service.version.VersionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

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

/**
 * Created by muhamadto on 4/07/2015.
 */

@Controller
public class IndexController extends BaseController{

    @Autowired
    private VersionService versionService;

    @RequestMapping(value={"","index**", "welcome**"}, method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView displayAPIVersion() throws Exception {
        Map model = new HashMap<>();
        model.put("apiversion",  versionService.getVersion().getApiVersion());
        ModelAndView modelAndView = new ModelAndView("version", model);

        return modelAndView;
    }
}
displayAPIVersion() will respond to request http://localhost:8080/services/1/index, http://localhost:8080/services/1/welcome or simply http://localhost:8080/services/1/ serving jsp called version.jsp as this line instructs the app
ModelAndView modelAndView = new ModelAndView("version", model);
  • Also, a page in WEB-INF/jsp/version.jsp has to be created
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags">


API Version ${apiversion}
Build Version:  
Build Time: 


In the browser go to http://localhost:8080/services/1/index, you should see a formatted html page:
API Version 1
Build Version: 1.0-SNAPSHOT
Build Time: 20150705-0611 


What is next

The next post will detail how to add support for Spring Data JPA