Sunday, November 16, 2014

Java RESTful Web Services with Spring Boot, Jersey and Hibernate

As a follow up on my previous post, where I have showed how to create simple REST service with jersey and spring boot, in this one I will go one step further. I am going to wire everything up using Spring Data JPA, Hibernate and Postgres JDBC connector and demonstrate how CRUD resources should be designed and implemented.

While I have been looking through github repositories I have noticed that lot's of them are using XML database configuration while in this tutorial I am going to show you how to do it with Java based configuration, so let's get started.

Assuming that you have already read my previous tutorial, you should have following maven project structure: 



If not, you can just clone repository from previous tutorial on github.

1. Maven configuration

Our pom file already have some packages included like spring-boot, jersey and jackson so we will leave those but with slight update of spring-boot to 1.1.9. version. Under the <parent> node I will update version of spring-boot-starter-parent artifact. 

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.1.9.RELEASE</version>
</parent>

We only need to upgrade parent version, all other dependencies within same groupId will inherit version number.

Now we'll need packages which will allow our tiny project to talk to database and serialize data as we want to.


First I will add Spring Data JPA. It will allow us to manipulate data in database.
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Than I will add PostgreSQL connector:
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>9.3-1102-jdbc41</version>
</dependency>

In this tutorial we'll use Jersey DI, which will allow us to inject JPA repositories into our REST controllers. Also, since we are going to use validation on our entities I will include package which is responsible for validation errors sent to client.
<dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-spring3</artifactId>
    <version>${jersey-version}</version>
</dependency>

<dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-bean-validation</artifactId>
    <version>${jersey-version}</version>
</dependency>

And finally we would like to have package which will allow us to support JSON serialization and deserialization of Hibernate data types. It especially comes in handy when we want to have Lazy Loaded objects, which will be covered as part of this tutorial.
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate4</artifactId>
    <version>${jackson-version}</version>
</dependency>

You might noticed that last two package groups have package version as property, not version number. I am practicing to put package version number in separate <properties> node when I have multiple packages under same group.
<properties>
    <start-class>com.jersey.Application</start-class>
    <jersey-version>2.7</jersey-version>
    <jackson-version>2.4.3</jackson-version>
</properties>

Now after we have all packages we need to run (download full POM.xml):
     mvn clean install


2. Entity Configuration


In this tutorial I will demonstrate CRUD operations with two resources, Member and Product.  So far we have only POJO, Product. We will need to create another one, Member, and spice them up with Spring Data JPA annotations. Both of them will be annotated with @Entity  which indicates that it is a JPA entity. In case that you have table that table name doesn't correspond to entity name  you need to use table annotation with name property, @Table(name = "my_table").

Both id properties in our entities will be annotated with @Id, so that JPA knows that that is object ID and also @GeneratedValue, which will indicate that ID should be generated using database identity column.

Our member-product relation is that Member may have many Products and Product belongs to Member, thus in the Product entity we will add new property member_id which will be annotated with @JoinColumn(name ="member_id"). Value of the property 'name' is the name of corresponding foreign key.

In Member, we will add new Set of Product, with same JoinColumn annotation and also @OneToMany annotation with Lazy fetch type strategy. With that said Member resource will not load associated Products until we manually poke them.

Also for the sake of constraints, we will set @NotNull annotation on some of the properties, and once client make a request those properties are going to be mandatory.

Important notice, every JPA entity need to have empty default constructor.

Member.java
package com.jersey.representations;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.Set;

@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    @Column(name = "first_name")
    private String firstName;

    @NotNull
    @Column(name = "last_name")
    private String lastName;

    @NotNull
    private String email;

    @OneToMany(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Set products;

    public Member() {
    }

    public Member(String firstName, String lastName, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

    public Long getId() {
        return id;
    }

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

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Set getProducts() {
        return products;
    }

    public void setProducts(Set products) {
        this.products = products;
    }
}


Product.java
package com.jersey.representations;

import javax.persistence.*;
import javax.persistence.GeneratedValue;
import javax.validation.constraints.NotNull;

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @NotNull
    private String name;
    private String currency;
    @Column(name = "regular_price")
    @NotNull
    private Double regularPrice;
    @Column(name = "discount_price")
    @NotNull
    private Double discountPrice;

    @JoinColumn(name = "member_id")
    private Long member_id;

    public Product() {
    }

    public Product(Long id, String name, String currency, Double regularPrice, Double discountPrice) {
        this.id = id;
        this.name = name;
        this.currency = currency;
        this.regularPrice = regularPrice;
        this.discountPrice = discountPrice;
    }
    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 Double getRegularPrice() {
        return regularPrice;
    }

    public void setRegularPrice(Double regularPrice) {
        this.regularPrice = regularPrice;
    }

    public Double getDiscountPrice() {
        return discountPrice;
    }

    public void setDiscountPrice(Double discountPrice) {
        this.discountPrice = discountPrice;
    }

    public String getCurrency() {
        return currency;
    }

    public void setCurrency(String currency) {
        this.currency = currency;
    }

    public Long getMember_id() {
        return member_id;
    }

    public void setMember_id(Long member_id) {
        this.member_id = member_id;
    }
}


3. SQL Configuration


Under the config package create new java class called SqlInitialization. 

SqlInitialization.java

package com.jersey.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate4.SpringSessionContext;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@EnableJpaRepositories(basePackages = "com.jersey.persistence")
public class SqlInitialization{

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("org.postgresql.Driver");
        dataSource.setUrl("jdbc:postgresql://127.0.0.1:5432/jersey-demo");
        dataSource.setUsername("jasenko");
        dataSource.setPassword("jasenko");
        return dataSource;
    }

    @Bean
        public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setPackagesToScan("com.jersey.representations");
        entityManagerFactoryBean.setJpaProperties(buildHibernateProperties());
        entityManagerFactoryBean.setJpaProperties(new Properties() {{
            put("hibernate.current_session_context_class", SpringSessionContext.class.getName());
        }});
        entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter() {{
            setDatabase(Database.POSTGRESQL);
        }});
        return entityManagerFactoryBean;
    }

    protected Properties buildHibernateProperties()
    {
        Properties hibernateProperties = new Properties();

        hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQL9Dialect");
        hibernateProperties.setProperty("hibernate.show_sql", "false");
        hibernateProperties.setProperty("hibernate.use_sql_comments", "false");
        hibernateProperties.setProperty("hibernate.format_sql", "false");
        hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "false");

        hibernateProperties.setProperty("hibernate.generate_statistics", "false");

        hibernateProperties.setProperty("javax.persistence.validation.mode", "none");

        //Audit History flags
        hibernateProperties.setProperty("org.hibernate.envers.store_data_at_delete", "true");
        hibernateProperties.setProperty("org.hibernate.envers.global_with_modified_flag", "true");

        return hibernateProperties;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new JpaTransactionManager();
    }

    @Bean
    public TransactionTemplate transactionTemplate() {
        return new TransactionTemplate(transactionManager());
    }
}

You should update dataSource() method with your username and password.

We will use EnableJpaRepository annotation which will scan for all spring data repositories under the given package. For that purpose we will create new package called persistence and under that package create two interfaces, MemberDao and ProductDao which extends JpaRepository.

MemberDao.java
package com.jersey.persistance;

import com.jersey.representations.Member;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberDao extends JpaRepository<Member, Long> {
}

ProductDao.Java
package com.jersey.persistance;

import com.jersey.representations.Product;
import org.springframework.data.jpa.repository.JpaRepository;


public interface ProductDao extends JpaRepository<Product, Long> {
}


Under the local PG instance, create database called jersey-demo and run this SQL query which will create Member and Products tables

CREATE TABLE member
(
  id serial NOT NULL,
  first_name character varying NOT NULL,
  last_name character varying NOT NULL,
  email character varying NOT NULL,
  CONSTRAINT pk_user_id PRIMARY KEY (id)
)

CREATE TABLE product
(
  id serial NOT NULL,
  name character varying NOT NULL,
  currency character varying NOT NULL,
  regular_price numeric NOT NULL,
  discount_price numeric NOT NULL,
  member_id bigint NOT NULL,
  CONSTRAINT pk_product_id PRIMARY KEY (id),
  CONSTRAINT fk_product__member FOREIGN KEY (member_id)
      REFERENCES member (id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE
)

4. Jackson JSON processor configuration


4.1. Object Mapper

By default Jackson will handle most primitive data types, but when it comes to handle nested objects and arrays it will crash and that's where the Jackson JSON processor comes in. Under the config package, I will create new class, ObjectMapperFactory, which will register Hibernate 4 modules.

ObjectMapperFactory.java
package com.jersey.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module;

public class ObjectMapperFactory {
    private static ObjectMapper objectMapper;
    static {
        objectMapper = new ObjectMapper()
                .registerModule(new Hibernate4Module());
    }

    public static ObjectMapper create() {
        return objectMapper;
    }
}

4.2. Bean Validation Support


Since we are building RESTfull API's we also want to have meaningful status codes in our responses, so we also need to include errors in our responses.

I will include module registration in our JerseyInitialization config class so it will be registered globally when server is started among with Jersey specific properties for Bean Validation.

JerseyInitialization.java
package com.jersey.config;

import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import org.glassfish.jersey.server.ResourceConfig;

public class JerseyInitialization extends ResourceConfig {
    /**
     * Register JAX-RS application components.
     */
    public JerseyInitialization() {
        this.register(new JacksonJsonProvider(ObjectMapperFactory.create()));
        this.property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
        this.property(ServerProperties.BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK, true);
        this.packages(true, "com.jersey.resources");
    }
}

4. REST Resources


Currently our ProductsResource have only GET methods plus those returns dummy data, where's the fun in that right? :)

I will add Create, Update and Delete methods for Product and I will refactor existing ones to use data from database, also I will create new class called MembersResource which will have method for fetching array of members, single member and method for fetching member with products. Last one will demonstrate how to poke lazy loaded objects.

Notice how I am naming all resource classes in plural. That's one of the REST conventions and if you are building API's you should stick to it. 
I will add two new annotations, actually we need them only in MembersResource for poking lazy loaded objects, @Component and @Transactional
Those two are quite complex and might require separate blog post, you may want to read more about them here and here

Long story short, when we want to get products for a member, which are lazy loaded, we would like to have transaction session opened while all products are fetched from database. Without those annotations hibernate will close connection before transaction is finished and you will end up with LazyInitializationException.

Also I will use Spring Dependency Injection to inject corresponding JPA repository into both constructors via @Injectannotation.

ProductsResource.java
package com.jersey.resources;

import com.jersey.persistance.ProductDao;
import com.jersey.representations.Product;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.inject.Inject;
import javax.validation.Valid;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;


@Path("/products")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Transactional
@Component
public class ProductsResource {
    private ProductDao productDao;
    @Inject
    public ProductsResource(ProductDao productDao){
        this.productDao = productDao;
    }

    /**
     * Get all Products
     * @return products
     */
    @GET
    public List getAll(){
        List products = this.productDao.findAll();
        return products;
    }

    /**
     * Get single Product
     * @param id
     * @return product
     */
    @GET
    @Path("{id}")
    public Product getOne(@PathParam("id")long id) {
        Product product = productDao.findOne(id);
        if(product == null){
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }else {
            return product;
        }
    }

    /**
     * Create new Product
     * @param product
     * @return new product
     */
    @POST
    public Product save(@Valid Product product) {
        return productDao.save(product);
    }

    /**
     * Update existing Product
     * @param id
     * @param product
     * @return updated product
     */
    @PUT
    @Path("{id}")
    public Product update(@PathParam("id")long id, @Valid Product product) {
        if(productDao.findOne(id) == null){
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }else {
            product.setId(id);
            return productDao.save(product);
        }
    }

    /**
     * Delete product
     * @param id
     */
    @DELETE
    @Path("{id}")
    public void delete(@PathParam("id")long id) {
        Product product = productDao.findOne(id);
        if(product == null){
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }else {
            productDao.delete(product);
        }
    }
}
MembersResource.java
package com.jersey.resources;


import com.jersey.persistance.MemberDao;
import com.jersey.persistance.ProductDao;
import com.jersey.representations.Member;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;

@Path("/members")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Component
@Transactional
public class MembersResource {
    private final MemberDao memberDao;
    private final ProductDao productDao;

    @Inject
    public MembersResource(MemberDao memberDao, ProductDao productDao) {
        this.memberDao = memberDao;
        this.productDao = productDao;
    }
    @GET
    public List getAll(){
        return this.memberDao.findAll();
    }

    @GET
    @Path("{id}/products")
    public Member getAllProductsForMember(@PathParam("id")long id) {
        Member member = memberDao.findOne(id);
        if (member == null) {
            throw new WebApplicationException((Response.Status.NOT_FOUND));
        }

        //Poke products
        member.getProducts().size();
        return member;
    }

    @GET
    @Path("{id}")
    public Member getMember(@PathParam("id")long id) {
        Member member = memberDao.findOne(id);
        if (member == null) {
            throw new WebApplicationException((Response.Status.NOT_FOUND));
        }
        return member;
    }
}

As you can see, getAllProductsForMember method, after we validate whether  member exists or not, we access to products and check for the size in this set. That way we are reaching for all products associated to this member from database.

@Valid annotation in POST and PUT methods is looking for constraints, that we set up in Entities, and if those are not satisfied Jersey Bean Validation will return us 400 (Bad Request).


5. Demonstration


Let's see what this baby can do :). Our resources are ready for consumption and I am about to show you. For demonstration purposes I will use advanced rest client for chrome.  
You might want to set new header in it: Content-Type:application/json

If you are using InteliJ as your IDE you can open built in terminal tab and run mvn spring-boot:run. If not, open command prompt or terminal and navigate to project directory and run the same command. 

I know that you have created new member in the database, so let's get them:

GET: http://localhost:8080/members

We have some members here, that's good. Now let's create new product for this member, but in my first request I will not specify name, I am curious what will happen:

POST: http://localhost:8080/products
JSON Request:
{
"regularPrice":35,
"discountPrice":25,
"currency":"euro",
"member_id":1
}


As expected, we get 400 and it's saying to us that the name is not valid value.

Now let's create that book already

{
"name":"RESTful Java with JAX-RS 2.0",
"regularPrice":35,
"discountPrice":25,
"currency":"euro",
"member_id":1
}



And SUCCESS, we have our product created. Do you feel generous? I do, and I will set discount to 20...I want people to learn :)
PUT: http://localhost:8080/products/9
JSON Request:
{
"name":"RESTful Java with JAX-RS 2.0",
"regularPrice":35,
"discountPrice":20,
"currency":"euro",
"member_id":1
}


Now, let's see what do we have there by getting all products for our member:

GET: http://localhost:8080/members/1/products

And that should be it. If you don't want that book in your store, feel free to delete it :). 

I hope that you was able to follow me along and learn something new today, if you have any questions feel free to ask in comments or send me an email I'll gladly try and help you. 

Download complete project from github.

Sunday, September 14, 2014

RESTfull web services with Spring Boot and Jersey

Please bare with me and excuse me if there are any mistakes in following tutorial, it is my firstborn :)

In this tutorial I will show how to create a new Java project from scratch and integrate Spring Boot and Jersey using maven into it.
Jersey RESTful Web Services framework is open source, production quality, framework for developing RESTful Web Services in Java that provides support for JAX-RS APIs and serves as a JAX-RS (JSR 311 & JSR 339) Reference Implementation.

Also this project will be used in further tutorials where I will discuss and explain various technologies which can be used across single application. 

Completed project can be found on github.


Technologies:

  1. Java 8
  2. InteliJ
  3. Maven 3
  4. Spring Boot 1.1.6.RELEASE
  5. Jersey 2.7

Create empty project with all necessary dependencies

Following screens will show how to create empty project with maven module. By adding maven module into empty project, well known pom.xml will be generated among with project structure.





After creating new maven project, pom.xml file should look like :
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelversion>4.0.0</modelversion>

    <groupid>com.discount</groupid>
    <artifactid>discount-rest-services</artifactid>
    <version>1.0-SNAPSHOT</version>
</project>

Now Spring and Jersey needs to be added, but also we need to integrate JSON framework into Jersey which will do JSON-to-Java binding and in this case I will use Jackson

pom.xml
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelversion>4.0.0</modelversion>

    <groupid>com.discount</groupid>
    <artifactid>discount-rest-service</artifactid>
    <version>0.1.0</version>

    <parent>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-parent</artifactid>
        <version>1.1.6.RELEASE</version>
    </parent>

    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-web</artifactid>
            <version>1.1.6.RELEASE</version>
        </dependency>

        <!-- End Spring -->

        <!-- Jackson -->
        <dependency>
            <groupid>com.fasterxml.jackson.jaxrs</groupid>
            <artifactid>jackson-jaxrs-json-provider</artifactid>
            <version>2.4.2</version>
        </dependency>
        <!-- End Jackson -->

        <!-- Jersey -->
        <dependency>
            <groupid>org.glassfish.jersey.containers</groupid>
            <artifactid>jersey-container-servlet</artifactid>
            <version>2.7</version>
        </dependency>
        <!-- End Jersey -->
    </dependencies>

    <properties>
        <start-class>com.discount.Application</start-class>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupid>org.springframework.boot</groupid>
                <artifactid>spring-boot-maven-plugin</artifactid>
            </plugin>
        </plugins>
    </build>
</project>
After that run the following command under the project location path: 

mvn clean package

If you look in the target directory you should see discount-rest-services-1.0-SNAPSHOT.jar.  spring-boot-maven-plugin plugin is responsible for creating this executable jar file, which allows us to run the application in production.  Executable jars (sometimes called “fat jars”) are archives containing your compiled classes along with all of the jar dependencies that your code needs to run. 

Create Resources and Representation layers

Since this demo application is about discounted products, we will need Product representation and properties like id, name, regularPrice, discountPrice and currency. So under java folder create following packages, com.discount.representation and com.discount.resources. 

Folder structure should look like:




Inside representations package add new java class called Product.java with above mentioned properties, getters, setters and constructor.

Product.java
package com.discount.representations;

public class Product {
    private Long id;
    private String name;
    private String currency;
    private Double regularPrice;
    private Double discountPrice;

    public Product(Long id, String name, String currency, Double regularPrice, Double discountPrice) {
        this.id = id;
        this.name = name;
        this.currency = currency;
        this.regularPrice = regularPrice;
        this.discountPrice = discountPrice;
    }

    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 Double getRegularPrice() {
        return regularPrice;
    }

    public void setRegularPrice(Double regularPrice) {
        this.regularPrice = regularPrice;
    }

    public Double getDiscountPrice() {
        return discountPrice;
    }

    public void setDiscountPrice(Double discountPrice) {
        this.discountPrice = discountPrice;
    }

    public String getCurrency() {
        return currency;
    }

    public void setCurrency(String currency) {
        this.currency = currency;
    }
}

Under resources package add new java class called ProductsResource.java

ProductsResource.java
package com.discount.resources;

import com.discount.representations.Product;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.List;


@Path("/products")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ProductsResource {

    @GET
    public List getAll(){
        List products = new ArrayList();
        products.add(new Product(1L, "iPhone 6", "USD", 850D, 750D));
        return products;
    }

    @GET
    @Path("{id}")
    public Product getOne(@PathParam("id")Long id){
        if(id == 888){
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }else {
            return new Product(id, "iPhone 6", "USD", 850D, 750D);
        }
    }
}

All resource classes are annotated with @Path or at least one method inside class with  a request method generator such as @GET, @POST, @PUT, @DELETE and with @Path.

Those annotations comes from JAX-RS which is Java API designed to help creating REST services. More about JAX-RS annotations can be found on official Java documentation.


Set up Spring and run the application

Now we need to tell Spring to scan and register our resource class so we can access it. But first we will create a class which will inherit resource configuration class which is main class for configuring a web application and add all resources under resources package. Under com.discount package we will create new package called config and add new class JerseyInitialization.java

JerseyInitialization.java
package com.discount.config;

import org.glassfish.jersey.server.ResourceConfig;

public class JerseyInitialization extends ResourceConfig {
    /**
     * Register JAX-RS application components.
     */
    public JerseyInitialization(){
        this.packages("com.discount.resources");
    }
}


Under com.discount package add new class, Application.java

Application.java
package com.discount;
import com.discount.config.JerseyInitialization;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.servlet.ServletProperties;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;

@EnableAutoConfiguration
public class Application {
    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).run(args);
    }

    @Bean
    public ServletRegistrationBean jerseyServlet() {
        ServletRegistrationBean registration = new ServletRegistrationBean(new ServletContainer(), "/*");
        registration.addInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS, JerseyInitialization.class.getName());
        return registration;
    }
}
@EnableAutoConfiguration annotation tells Spring Boot to “guess” how you will want to configure Spring, based on the jar dependencies that you have added. Since spring-boot-starter-web added Tomcat and Spring MVC, the auto-configuration will assume that you are developing a web application and setup Spring accordingly. Finally we will inject all our jersey resources into servlet and we should be good to go. 

In command prompt type:


mvn clean package && java -jar target/discount-rest-services-1.0-SNAPSHOT.jar

After successful build and spring boot have started the application you can try visiting http://localhost:8080/products from your browser.