[Article] WSO2 Microservices Framework for Java the Spring Way - Part 2

  • By Sagara Gunathunga
  • 19 Sep, 2016
Archived Content
This article is provided for historical perspective only, and may not reflect current conditions. Please refer to relevant product page for more up-to-date product information and resources.

Table of contents


Introduction

In Part 1 of this series we discussed some basic concepts of the MSF4J Spring module by using a set of detailed examples. This part will discuss some real-world examples that bring these concepts together.


Sample use case

First, let’s understand the use case scenario. Let’s assume that you have been given the task of developing a RESTful service to maintain a book catalog of a small library. The service should

  • Have a facility where new books can be added and existing books can be removed
  • Return corresponding details once the book ID is provided
  • Return all the books of the catalog in the form of a list
  • Use a proper database for continuity

Before we discuss any implementation details, let's use the domain model pattern as the design principle to model this service. By applying this pattern, we can easily identify the layers shown in Figure 1.

Figure 1

As we have identified separate layers of the service and their responsibilities, now we can map the implementation level details related to each layer as shown below.

Layer Technology
REST Resource MSF4J/Spring
Repository Hibernate/Spring
Database MySQL or HSQL

If you use MSF4J annotations to implement the RESTful resource class, you will end up getting something similar to this.

Path("/catalog")
public class BookResource {

    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getBookById(@PathParam("id") long id) {

    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getAllBooks() {

    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response addBook(Book book) {
       
    }

    @DELETE
    @Path("/{id}")
    public Response removeBook(@PathParam("id") long id) {
       
    }
}

In the following table, you can find a summary of the above design where we have provided the URL context, HTTP method and input and output messages related to each action.

Action Context HTTP method Input Output
Add a book to the catalog /catalog POST JSON HTTP 201
Remove a specific book /catalog/{Id} DELETE Path parameter HTTP 200
Get details of a specific book /catalog/{Id} GET Path parameter JSON
Gell details of all books /catalog GET - JSON

According to the above design we only need four database related methods in the Repository class.

public class BookRepository {

    public void addBook(Book book) {

    }

    public void removeBook(long id) {
      
    }

    public Book findBook(long id) {
       
    }

    public List findAllBooks() {

    }
}
public class Book {

    Long id;
    private String name;
    private String author;

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}

At this point, we have the RESTful Resource class and the Repository class, thus it is possible to integrate these two layers using the Dependency Injection (DI) feature of the Spring framework. For this, you can use either the Spring component scan, Java Configuration or XML Configuration; however, to keep things simple, let’s stick to the component-scan approach.

@Component
@Path("/catalog")
public class BookResource {

    @Autowired
    private BookRepository books;

    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getBookById(@PathParam("id") long id) {
        Book book = books.findBook(id);
        return Response.status(Response.Status.ACCEPTED).entity(book).build();
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getAllBooks() {
        List allBooks = books.findAllBooks();
        return Response.status(Response.Status.ACCEPTED).entity(allBooks).build();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response addBook(Book book) {
        books.addBook(book);
        return Response.status(Response.Status.CREATED).build();
    }

    @DELETE
    @Path("/{id}")
    public Response removeBook(@PathParam("id") long id) {
        books.removeBook(id);
        return Response.status(Response.Status.ACCEPTED).build();
    }
}

As you may have already noticed, the @Component annotation on the BookResource class allows the Spring framework to detect the BookResource class as a Spring-managed component and eventually deploy it as an MSF4J service within the MSF4J runtime.

Further, the @Autowired annotation performs the actual integration between the BookResource and the BookRepository classes. In other words, this performs the integration between the RESTful service layer and the Repository layer. The only task required here is to add the @Autowired annotation. As long as you define the necessary resources within the Spring ApplicationContext the Spring framework can perform the required dependency injections very accurately.

Figure 2 illustrates the current progress of our Book-catalog service.

Figure 2

Our next task is to annotate the Book domain class using JPA annotations so that we can use this class with the Hibernate ORM. In the following listing you can find the annotated Book class.

@Entity
@Table(name = "book")
@Proxy(lazy = false)
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;

    @Column
    private String name;

    @Column
    private String author;

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}

We will not be discussing any JPA annotations because that is beyond the scope and objective of this article. You can easily find many JPA-related references on the Web.

Next, let’s implement the Repository class based on the Hibernate-Spring module. Here we have a large number of implementation level choices, but we will only discuss three popular, distinct choices.

  1. Implement Resource class based on the Hibernate Session object.
  2. Implement Resource class based on the Spring HibernateTemplate.
  3. Implement Resource class based on JPA EntityManager.

Repository implementation based on Hibernate Session object

In this approach, the Hibernate Session object is the main contact point between the Hibernate ORM framework and our service. When we want to add, remove, or load domain objects, such as instances of Book class, we simply delegate those responsibilities into the Session object. But it's worth noting that it is our responsibility to provide necessary configuration data to the Spring framework in order to successfully construct the SessionFactory bean.

The complete Repository implementation based on the Hibernate Session object is shown below.

@Transactional
@Repository
public class BookRepository {

    @Autowired
    private SessionFactory sessionFactory;

    private Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    public void addBook(Book book) {
        getSession().save(book);
    }

    public void removeBook(long id) {
        Session session = getSession();
        session.delete(session.load(Book.class, id));
    }

    public Book findBook(long id) {
        Book book = (Book) getSession().load(Book.class, id);
        return book;
    }

    public List findAllBooks() {
        return getSession().createQuery("from Book").list();
    }
}

The most important points of the above implementation are as follows:

  • As we have seen in Part 1 of this article, here too we use the Spring @Autowired annotation to inject the properly constructed SessionFactory bean into the BookRepository bean. This SessionFactory bean will be used to construct the Hibernate Session object during execution.
  • Annotating the BookRepository class using the @Repository annotation indicates that the BookRepository class belongs to the persistence layer or data access layer of our service and it makes the BookRepository class eligible for the persistence exception translation features of the Spring framework.
  • Adding the @Transactional annotation into the BookRepository class enables the Spring framework to generate necessary proxies to support the required transactional behaviors.

Figure 3 illustrates the current status of our project.

Figure 3

At this point, we have implemented all necessary classes and their integrations as well. What’s left to do is to provide the necessary configuration details, such as database access, using one of the Spring configuration options. To keep things simple, we only use the Spring Java configuration approach here. For the database, we use the in-memory HSQL database to further eliminate database creation and configuration steps.

@Configuration
@EnableTransactionManagement
public class PersistenceConfig {

    @Value("${db.driver}")
    private String DB_DRIVER;

    @Value("${db.password}")
    private String DB_PASSWORD;

    @Value("${db.driver}")
    private String dbDriver;

    @Value("${db.password}")
    private String dbPassword;

    @Value("${db.url}")
    private String dbUrl;

    @Value("${db.username}")
    private String dbUsername;

    @Value("${hibernate.dialect}")
    private String hibernateDialect;

    @Value("${hibernate.show_sql}")
    private String hibernateShowSql;

    @Value("${hibernate.hbm2ddl.auto}")
    private String hibernateHbm2DdlAuto;

    @Value("${packagesToScan}")
    private String packagesToScan;

    @Value("${hibernate.enable_lazy_load_no_trans:true}")
    private String hibernateEnableLazyLoadNoTrans;

    @Bean
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setPackagesToScan(new String[]{PACKAGES_TO_SCAN});
        sessionFactory.setHibernateProperties(hibernateProperties());
        return sessionFactory;
    }

     private Properties hibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", hibernateDialect);
        properties.put("hibernate.show_sql", hibernateShowSql);
        properties.put("hibernate.hbm2ddl.auto", hibernateHbm2DdlAuto);
        properties.put("hibernate.enable_lazy_load_no_trans", hibernateEnableLazyLoadNoTrans);
        return properties;
    }


    @Bean
    @Autowired
    public HibernateTransactionManager transactionManager(SessionFactory s) {
        HibernateTransactionManager txManager = new HibernateTransactionManager();
        txManager.setSessionFactory(s);
        return txManager;
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(DB_DRIVER);
        dataSource.setUrl(DB_URL);
        dataSource.setUsername(DB_USERNAME);
        dataSource.setPassword(DB_PASSWORD);
        return dataSource;
    }

}

As you can see here we have used the Spring environment specific variables to provide database access details. During the execution phase, you can create a file called application.properties in the classpath to provide these values into the Spring framework. The following file is an example.

#HSQL
db.driver=org.hsqldb.jdbcDriver
db.url=jdbc:hsqldb:mem://productDb
db.username=sa
db.password=
hibernate.dialect=org.hibernate.dialect.HSQLDialect
hibernate.show_sql=true
hibernate.hbm2ddl.auto=create
hibernate.enable_lazy_load_no_trans=true
packagesToScan=org.wso2.msf4j.samples.spring.hibernate.bookcatalog.model

In contrast to the Spring Java Configuration classes found in Part 1 of this series, here you can see the @EnableTransactionManagement annotation is added to the PersistenceConfig class. This annotation enables Spring's annotation-driven transaction management capabilities. In case you prefer to use traditional Spring XML configuration you can use <tx:annotation-driven/> configuration for the same purpose.

Now we have almost completed this sample, but there is one more step left for us to make this service more robust. When we try to delete a book that’s unavailable or try to read details of such a book an ObjectNotFoundException will occur. Unless we explicitly handle this exception, the runtime will pass the exception details on to the client side; this is not a good design and will also cause security vulnerabilities by exposing internal server details to clients.

To overcome this issue we can use the MSF4J ExceptionMapper concept. You can find the complete code for the ExceptionMapper implementation below.

@Component
public class ObjectNotFoundExceptionMapper implements ExceptionMapper {

    @Override
    public Response toResponse(ObjectNotFoundException e) {
        return Response.status(Response.Status.NOT_FOUND).type(MediaType.APPLICATION_JSON).
                entity("Specific book does not exists").build();
    }
}

In the above ObjectNotFoundExceptionMapper class receives all ObjectNotFoundExceptions and converts them into a friendly JSON error messages with an HTTP status code 404 without exposing any sensitive details.

Once you build the sample using Maven you can run the service by executing the following Java command.

java -jar target/hibernate-book-catalog-1.0.0.jar

Note - Make sure you have correct application.properties file on classpath.

Once the server startup you can use the following cURL command to try out this service.

  1. Adding a book
    curl -v -H "Content-Type: application/json" -X POST -d '{"name":"Java","author":"SUN"}' http://localhost:8080/catalog
    

    Sample output when running above command is given below. You should able to see HTTP 201 (Created) as the response for this call.

  2. Get details of a book
    curl -v  -X GET  http://localhost:8080/catalog/1 
    

    Sample output when running the above command is given below. You should able to see HTTP 200 with the JSON content.

  3. Remove a book
    curl -v  -X DELETE  http://localhost:8080/catalog/1
    

    Sample output when running above the command is given below. You should able to see HTTP 202 as the response for this call.

  4. Get details of an unavailable book
    curl -v  -X GET  http://localhost:8080/catalog/1 
    

    Sample output when running the above command is given below. You should be able to get a user-friendly error message along with HTTP 404 status code as the response for this call.


Repository implementation based on Spring HibernateTemplate

Figure 4

Due to proper design principles we’ve used to model this service, it’s a straightforward process to change Repository class to use the Spring HibernateTemplate by replacing the Hibernate Session object. In fact, we have to change the BookRepository and the PersistenceConfig classes only.

Here’s the BookRepository implementation based on the HibernateTemplate.

@Transactional
@Repository
public class BookRepository {

    @Autowired
    private HibernateTemplate hibernateTemplate;

    public void addBook(Book book) {
        hibernateTemplate.save(book);
    }

    public void removeBook(long id) {
        hibernateTemplate.delete(hibernateTemplate.get(Book.class, id));
    }

    public Book findBook(long id) {
        return hibernateTemplate.get(Book.class, id);
    }

    public List findAllBooks() {
        return hibernateTemplate.loadAll(Book.class);
    }
}

You can find the changed PersistenceConfig class below.

@Configuration
@EnableTransactionManagement
public class PersistenceConfig {

    @Value("${db.driver}")
    private String dbDriver;

    @Value("${db.password}")
    private String dbPassword;

    @Value("${db.url}")
    private String dbUrl;

    @Value("${db.username}")
    private String dbUsername;

    @Value("${hibernate.dialect}")
    private String hibernateDialect;

    @Value("${hibernate.show_sql}")
    private String hibernateShowSql;

    @Value("${hibernate.hbm2ddl.auto}")
    private String hibernateHbm2DdlAuto;

    @Value("${packagesToScan}")
    private String packagesToScan;

    @Value("${hibernate.enable_lazy_load_no_trans:true}")
    private String hibernateEnableLazyLoadNoTrans;

    @Bean
    @Autowired
    public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
        HibernateTransactionManager htm = new HibernateTransactionManager();
        htm.setSessionFactory(sessionFactory);
        return htm;
    }

    @Bean
    @Autowired
    public HibernateTemplate getHibernateTemplate(SessionFactory sessionFactory) {
        HibernateTemplate hibernateTemplate = new HibernateTemplate(sessionFactory);
        return hibernateTemplate;
    }

    @Bean
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setPackagesToScan(new String[]{packagesToScan});
        sessionFactory.setHibernateProperties(hibernateProperties());
        return sessionFactory;
    }

    private Properties hibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", hibernateDialect);
        properties.put("hibernate.show_sql", hibernateShowSql);
        properties.put("hibernate.hbm2ddl.auto", hibernateHbm2DdlAuto);
        properties.put("hibernate.enable_lazy_load_no_trans", hibernateEnableLazyLoadNoTrans);
        return properties;
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(dbDriver);
        dataSource.setUrl(dbUrl);
        dataSource.setUsername(dbUsername);
        dataSource.setPassword(dbPassword);
        return dataSource;
    }
}

Repository implementation based on JPA EntityManager

Figure 5

In this section, we will look at how to change the BookRepository class to use JPA EntityManager. Here too we have to change the BookRepository and the PersistenceConfig classes only. Unlike the previous sections, here EntityManager is a standard interface to communicate with the JPA persistence context.

Here’s the JPA EntityManager based BookRepository class.

@Transactional
@Repository
public class BookRepository {

    @Autowired
    private EntityManagerFactory emf;

    private EntityManager getEntityManager() {
        return emf.createEntityManager();
    }

    public void addBook(Book book) {
        EntityManager manager = getEntityManager();
        manager.getTransaction().begin();
        manager.persist(book);
        manager.getTransaction().commit();
        manager.close();
    }

    public void removeBook(long id) {
        EntityManager manager = getEntityManager();
        manager.getTransaction().begin();
        manager.remove(manager.find(Book.class, id));
        manager.getTransaction().commit();
        manager.close();
    }

    public Book findBook(long id) {
        return getEntityManager().find(Book.class, id);
    }

    public List findAllBooks() {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery cq = cb.createQuery(Book.class);
        Root rootEntry = cq.from(Book.class);
        CriteriaQuery all = cq.select(rootEntry);
        TypedQuery allQuery = getEntityManager().createQuery(all);
        return allQuery.getResultList();
    }
}

Here’s the PersistenceConfig class to be used with JPA EntityManager based BookRepository.

@Configuration
@EnableTransactionManagement
public class PersistenceConfig {

    @Value("${db.driver}")
    private String dbDriver;

    @Value("${db.password}")
    private String dbPassword;

    @Value("${db.url}")
    private String dbUrl;

    @Value("${db.username}")
    private String dbUsername;

    @Value("${hibernate.show_sql}")
    private String hibernateShowSql;

    @Value("${hibernate.hbm2ddl.auto}")
    private String hibernateHbm2DdlAuto;

    @Value("${packagesToScan}")
    private String packagesToScan;


    @Bean
    public PlatformTransactionManager transactionManager() {
        EntityManagerFactory factory = entityManagerFactory().getObject();
        return new JpaTransactionManager(factory);
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(Boolean.TRUE);
        vendorAdapter.setShowSql(Boolean.valueOf(hibernateShowSql));
        factory.setDataSource(dataSource());
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan(packagesToScan);
        Properties jpaProperties = new Properties();
        jpaProperties.put("hibernate.hbm2ddl.auto", hibernateHbm2DdlAuto);
        factory.setJpaProperties(jpaProperties);
        factory.afterPropertiesSet();
        factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
        return factory;
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(dbDriver);
        dataSource.setUrl(dbUrl);
        dataSource.setUsername(dbUsername);
        dataSource.setPassword(dbPassword);
        return dataSource;
    }
}

Security requirements for the service

We’ve addressed many functional aspects so far, but what about the security aspect of this service? To make this service applicable in a real-world scenario, let’s introduce some security measures. To keep things simple, in this example, we will use the following 2 security approaches for our application:

  1. Secure messages using HTTP BasicAuth.
  2. Disable HTTP transport and allow only HTTPS transport.

The MSF4J security module supports two security schemes out of the box: the HTTP BasicAuth and the OAuth2-JWT token based security. To keep things simple, we will use the HTTP BasicAuth for this sample which will require the user to provide a valid username and password with service calls. For OAuth2-JWT based security, we need separate OAuth servers, such as WSO2 Identity Server (WSO2 IS).

The MSF4J framework already provides an abstract class called AbstractBasicAuthSecurityInterceptor to simplify the HTTP BasicAuth processing. Once you extend from this class you only have to write the authentication logic to verify the provided username and password while the AbstractBasicAuthSecurityInterceptor is responsible for HTTP header processing and extracting username and password from the HTTP authorization header.

For this sample, we will only check whether the provided username and password match. You can find the complete code below, but in an actual scenario you can plug more complex authentication logic or you can call an external identity provider (external IDP) with user provided credentials.

@Component
public class SameUsernamePasswordInterceptor extends AbstractBasicAuthSecurityInterceptor {

    @Override
    protected boolean authenticate(String username, String password) {
        if (username.equals(password)) {
            return true;
        }
        return false;
    }
}

In the above code adding the @Component annotation into the SameUsernamePasswordInterceptor class allows the Spring framework to add the SameUsernamePasswordInterceptor as an interceptor to the MSF4J runtime.

If the authentication fails, which means authenticate() method returns ‘false’ as a result, the MSF4J runtime returns the HTTP-401 Unauthorized header on the client side.

As we achieved message level security, now our next step is to disable HTTP transport and enable HTTPS transport with required SSL properties. As you’re probably aware already you basically need to provide the following 3 properties:

  1. Valid keystore file
  2. Keystore password
  3. Certificate password

You can create a valid certificate and a keystore through any convenient approach. In this sample, we use the Maven keytool plugin to generate a keystore with a valid certificate at build time.

Once we have a valid keystore we can use the application.properties file to provide the required configuration values to the MSF4J runtime. Here’s a sample application.properties that’s related to the current sample.

#Transport
http.enabled=false
https.enabled=true
https.port=8443
https.keyStoreFile=ssl.keystore
https.keyStorePass=testkey
https.certPass=testkey 

In the above property file “http.enabled=false” the key-value pair disables the HTTP connector while the “https.enabled=true” key-value pair enables the HTTPS transport.

At this point, we have completed all functional requirements and the required security measures for our sample project. Now you can use following slightly modified cURL commands to try out this sample again.

  1. Adding a book
    curl -v -k -u user:user -H "Content-Type: application/json" -X POST -d '{"name":"Java","author":"SUN"}' https://localhost:8443/catalog
    
  2. Get details of a book
    curl -v  -k -u user:user -X GET  https://localhost:8443/catalog/1
    
  3. Remove a book
    curl -v  -k -u user:user -X DELETE  https://localhost:8443/catalog/1 
    
  4. Get details of an unavailable book
    curl -v  -k -u user:user -X GET  https://localhost:8443/catalog/1
    

You now have a working book catalog service with proper security measures. Additionally, you have a better understanding about how to easily change persistence logic through minimal code changes.


Conclusion

During the first part of this article series we introduced the basic concepts related to the MSF4J Spring module and discussed each concept separately using a detailed example. In this part, we presented a complete and practical use case and demonstrated how you can model the use case using popular design patterns known to Spring developers. We also discussed possible database and security options available with the MSF4J Spring module. Most of the concepts discussed during in this series are highly applicable in a real-world service development scenario.

You can find all the source code related to this article here with detailed instructions.