Skip to content

Latest commit

 

History

History
1317 lines (943 loc) · 54.2 KB

spring-stack.rst

File metadata and controls

1317 lines (943 loc) · 54.2 KB

Spring Stack

RESThub 2 Spring stack provides a server side full stack and guidelines for building Java/Spring application (usually web application, but not only).

It provides a coherent stack based on:
It provides the following modules:
  • resthub-archetypes: project templates (WAR or multi-module layout) to start quickly a new project
  • resthub-jpa: support for JPA based persistence based on Spring Data, including embedded H2 database for testing
  • resthub-mongodb: support for MongoDB based on Spring Data
  • resthub-test: testing stack based on TestNG, Mockito and Fest Assert 2
  • resthub-web-server: generic REST webservices support based on Spring MVC 3.2 including exception mapping to HTTP status codes
  • resthub-web-client: simple to use HTTP client based on AyncHttpClient

Released artifacts are available from Maven Central and will be automatically found without adding any additional repository.

Snapshot artifacts are available from Sonatype OSS Snapshot repository. In order to use it for your projects, you should add the following element to your pom.xml :

<repositories>
    <repository>
        <id>snapshot</id>
         <url>https://oss.sonatype.org/content/repositories/snapshots</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>

The whole RESThub 2.1 Spring stack Javadoc is available.

Java and Maven 3 should be installed on your computer. RESThub based applications are usually developed thanks to a Java IDE like Eclipse, Netbeans or IntelliJ IDEA. If you don't know which IDE to choose, Netbeans is recommended since it is free and has great Maven support and Java/Javascript capabilities.

The easiest way to start is to use RESThub archetypes to create your first web application.

You will have to choose between the following RESThub archetypes:
  • resthub-jpa-backbonejs-archetype: simple HTML5 web application with JPA persistence
  • resthub-mongodb-backbonejs-archetype: simple HTML5 web application with MongoDB persistence
  • resthub-jpa-backbonejs-multi-archetype: Multimodules HTML5 web application with JPA persistence
  • resthub-mongodb-backbonejs-multi-archetype: Multimodules HTML5 web application with MongoDB persistence

To create your project based or RESThub archetypes, just open a command line terminal, and copy/paste the line related to the archetype you chosed:

mvn archetype:generate -DarchetypeArtifactId=resthub-jpa-backbonejs-archetype -DarchetypeGroupId=org.resthub -DarchetypeVersion=2.1.4
mvn archetype:generate -DarchetypeArtifactId=resthub-mongodb-backbonejs-archetype -DarchetypeGroupId=org.resthub -DarchetypeVersion=2.1.4
mvn archetype:generate -DarchetypeArtifactId=resthub-jpa-backbonejs-multi-archetype -DarchetypeGroupId=org.resthub -DarchetypeVersion=2.1.4
mvn archetype:generate -DarchetypeArtifactId=resthub-mongodb-backbonejs-multi-archetype -DarchetypeGroupId=org.resthub -DarchetypeVersion=2.1.4

After choosing the right archetype and answering a few questions, your project is generated and ready to use. You can run it thanks to built-in Jetty support:

mvn jetty:run

You should follow RESThub Spring Stack tutorial in order to learn step by step how to use it.

Let's take a look at a typical RESThub based application...

RESThub stack based projects follow the "Maven standard" project layout:
  • /pom.xml: the Maven configuration file which defines dependencies, plugins, etc.
  • /src/main/java: your java classes go there
  • /src/main/java/**/WebAppInitializer.java: Java based WebApp configuration (replaces your old web.xml file)
  • /src/main/resources: your xml and properties files go there
  • /src/main/resources/applicationContext.xml: this is your Spring application configuration file. Since we mainly use annotation based configuration,
  • /src/main/webapp: your HTML, CSS and javascript files go there
RESThub based applications usually use one of these 2 layouts:
  • A single WAR project
  • A multi-module project with the following sub-modules:
    • myproject-webapp (WAR): it is your web application, it contains static resources, environment specific configuration and it declares dependencies to other modules in the pom.xml
    • myproject-contract (JAR): contains your POJOs (Entities, DTO ...) and service interface. This module should be used by web client or RPC mechanism to know the public classes and interfaces of your application without retreiving all the implementation dependencies. As a consequence, if you need to add some implementation dependencies (usually needed for annotations), add them as optional Maven dependencies.
    • myproject-core (JAR): your project implementation (controllers, service implementations, repositories)

Check the RESThub 2 Todo example application source code to learn how to design your RESThub based web application.

How to run the todo application:
  • Download the zip file and extract it
  • Install MongoDB, create the data folder (C:\data\db or /data/db by default) and run mondgod
  • Run mvn jetty:run in the todo-backbone-example directory
  • Open your browser and browse http://localhost:8080/index.html

You will find below the typical configuration file for your application.

Your project pom.xml defines your project name, version, dependencies and plugins used. Please notice that it is easier to let RESThub archetypes create the pom.xml automatically for you.

pom.xml example:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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.mycompany</groupId>
    <artifactId>myproject</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>My project</name>

    <properties>
        <resthub.spring.stack.version>2.1.4</resthub.spring.stack.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.resthub</groupId>
            <artifactId>resthub-mongodb</artifactId>
            <version>${resthub.spring.stack.version}</version>
        </dependency>
        <dependency>
            <groupId>org.resthub</groupId>
            <artifactId>resthub-web-server</artifactId>
            <version>${resthub.spring.stack.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>todo</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>8.1.13.v20130916</version>
                <configuration>
                    <!-- We use non NIO connector in order to avoid read only static files under windows -->
                    <connectors>
                        <connector implementation="org.eclipse.jetty.server.bio.SocketConnector">
                            <port>8080</port>
                            <maxIdleTime>60000</maxIdleTime>
                        </connector>
                    </connectors>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

RESThub dependencies are available on Maven Central:

<dependency>
    <groupId>org.resthub</groupId>
    <artifactId>resthub-jpa</artifactId>
    <version>2.1.4</version>
</dependency>

<dependency>
    <groupId>org.resthub</groupId>
    <artifactId>resthub-mongodb</artifactId>
    <version>2.1.4</version>
</dependency>

<dependency>
    <groupId>org.resthub</groupId>
    <artifactId>resthub-web-server</artifactId>
    <version>2.1.4</version>
</dependency>

<dependency>
    <groupId>org.resthub</groupId>
    <artifactId>resthub-web-client</artifactId>
    <version>2.1.4</version>
</dependency>

<dependency>
    <groupId>org.resthub</groupId>
    <artifactId>resthub-test</artifactId>
    <version>2.1.4</version>
    <scope>test</scope>
</dependency>

Web application initializer replaces the old web.xml file used with Servlet 2.5 or older webapps. It has the same goal, but since it is Java based, it is safer (compilation check, autocomplete).

WebAppInitializer.java example:

public class WebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.getEnvironment().setActiveProfiles("resthub-jpa", "resthub-web-server");
        String[] locations = { "classpath*:resthubContext.xml", "classpath*:applicationContext.xml" };
        appContext.setConfigLocations(locations);

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

        servletContext.addListener(new ContextLoaderListener(appContext));
    }
}

RESThub 2 uses Spring 3.2 profiles to let you activate or not each module. It allows you to add Maven dependencies for example on resthub-jpa and resthub-web-server and let you control when you activate these modules. It is especially useful when running unit tests: when testing your service layer, you may not need to activate the resthub-web-server module.

You can also use Spring profile for your own application Spring configuration.

Profile activation on your webapp is done very early in the application lifecycle, and is done in your Web application initializer (Java equivalent of the web.xml) described just before. Just provide the list of profiles to activate in the onStartup() method:

XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.getEnvironment().setActiveProfiles("resthub-mongodb", "resthub-web-server");

In your tests, you should use the @ActiveProfiles annotation to activate the profiles you need:

@ActiveProfiles("resthub-jpa") // or @ActiveProfiles({"resthub-jpa","resthub-web-server"})
public class SampleTest extends AbstractTransactionalTest {

}

RESThub web tests comes with a helper to activate profiles too:

public class SampleControllerTest extends AbstractWebTest {

    public SampleControllerTest() {
        // Call AbstractWebTest(String profiles) constructor
        super("resthub-web-server,resthub-jpa");
    }
}
RESThub built-in Spring profiles have the same name than their matching module:
  • resthub-jpa: enable JPA database support (resthub-jpa dependency needed)
  • resthub-mongodb: enable MongoDB support (resthub-mongodb dependency needed)
  • resthub-web-server: enable default web server configuration (resthub-web-server dependency needed)
  • resthub-client-logging: enable a webservice use to send logs from client to server (resthub-web-server dependency needed)

By default RESThub webservices and unit tests scan and automatically include all resthubContext.xml (RESThub context files) and applicationContext.xml files (your application context files) available in your application classpath, including its dependencies.

Here is an example of a typical RESThub based src/main/resources/applicationContext.xml (this one uses JPA, you may adapt it if you use MongoDB):

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/data/jpa
                           http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <context:component-scan base-package="org.mycompany.myproject" />
    <jpa:repositories base-package="org.mycompany.myproject.repository" />

</beans>

You'll usually have a src/main/resources/logback.xml file in order to configure logging:

<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss} [%thread] %-5level %logger{26} - %msg%n%rEx</pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

You should use JEE6 annotations to declare and inject your beans.

To declare a bean:

@Named("beanName")
public class SampleClass {

}

To inject a bean by type (default):

@Inject
public void setSampleProperty(...) {

}

Or to inject a bean by name (Allow more than one bean implementing the same interface):

@Inject @Named("beanName")
public void setSampleProperty(...) {

}

RESThub is designed to give you the choice between a 2 layers (Controller -> Repository) or a 3 layers (Controller -> Service -> Repository) software architecture. If you choose the 3 layers one, you can use the RESThub CRUD service when it is convenient:

@Named("sampleService")
public class SampleServiceImpl extends CrudServiceImpl<Sample, Long, SampleRepository> implements SampleService {

    @Override @Inject
    public void setRepository(SampleRepository sampleRepository) {
        super.setRepository(sampleRepository);
    }
}

There are various ways to configure your environment specific properties in your application: the one described below is the most simple and flexible way we have found.

Maven filtering (search and replace variables) is not recommended because it is done at compile time (not runtime) and makes usually your JAR/WAR specific to an environment. This feature can be useful when defining your target path (${project.build.directory}) in your src/test/applicationContext.xml for testing purpose.

Spring properties placeholders + @Value annotation is the best way to do that.

<context:property-placeholder location="classpath*:mymodule.properties"
                              ignore-resource-not-found="true"
                              ignore-unresolvable="true" />

You should now be able to inject dynamic values in your code, where InMemoryRepository is the default:

@Configuration
public class RequestConfiguration {

    @Value(value = "${repository:InMemoryRepository}")
    private String repository;
}
JPA support is based on Spring Data JPA and includes by default the H2 in memory database. It includes the following dependencies:

Thanks to Spring Data, it is possible to create repositories (also sometimes named DAO) by writing only the interface.

In order to use it in your project, add the following snippet to your pom.xml:

<dependency>
    <groupId>org.resthub</groupId>
    <artifactId>resthub-jpa</artifactId>
    <version>2.1.4</version>
</dependency>

In order to import its default configuration, your should activate the resthub-jpa Spring profile in your WebAppInitializer class:

XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.getEnvironment().setActiveProfiles("resthub-jpa", "resthub-web-server");

Since version 3.1, Spring allows to scan entities in different modules using the same PersitenceUnit, which is not possible with default JPA behaviour. You have to specify the packages where Spring should scan your entities by creating a database.properties file in your resources folder, with the following content:

persistenceUnit.packagesToScan = com.myproject.model

Now, entities within the com.myproject.model packages will be scanned, no need for persistence.xml JPA file.

You also need to add an applicationContext.xml file in order to scan your repository package.

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/data/jpa
                           http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <jpa:repositories base-package="com.myproject.repository" />

</beans>

You can customize the default configuration by adding a database.properties resource with one or more of the following keys customized with your values (see BoneCP documentation for details). You should include only the customized ones.

RESThub JPA default properties are:
  • dataSource.driverClassName = org.h2.Driver
  • dataSource.url = jdbc:h2:mem:resthub;DB_CLOSE_DELAY=-1;MVCC=TRUE
  • dataSource.username = sa
  • dataSource.password =
  • dataSource.minConnectionsPerPartition = 2
  • dataSource.maxConnectionsPerPartition = 4
  • dataSource.partitionCount = 3
  • dataSource.idleConnectionTestPeriodInSeconds = 60
  • dataSource.statementsCacheSize = 100
  • dataSource.connectionTestStatement = /* ping*/ SELECT 1
RESThub Hibernate default properties are:
  • hibernate.dialect = org.hibernate.dialect.H2Dialect
  • hibernate.show_sql = false
  • hibernate.format_sql = true
  • hibernate.hbm2ddl.auto = update
  • hibernate.cache.use_second_level_cache = true
  • hibernate.cache.provider_class = net.sf.ehcache.hibernate.EhCacheRegionFactory
  • hibernate.id.new_generator_mappings = true
  • persistenceUnit.packagesToScan =

If you need to do more advanced configuration, just override dataSource and entityManagerFactory beans in your applicationContext.xml.

public interface TodoRepository extends JpaRepository<Todo, String> {

    List<Todo> findByContentLike(String content);
}

H2 console allows you to provide a SQL requester for your embedded default H2 database. It is included by default in JPA archetypes.

In order to add it to your JPA based application, add these lines to your WebAppInitializer class:

public void onStartup(ServletContext servletContext) throws ServletException {
    ...
    ServletRegistration.Dynamic h2Servlet = servletContext.addServlet("h2console", WebServlet.class);
    h2Servlet.setLoadOnStartup(2);
    h2Servlet.addMapping("/console/database/*");
}
When running the webapp, the database console will be available at http://localhost:8080/console/database/ URL with following parameters:
  • JDBC URL: jdbc:h2:mem:resthub
  • Username: sa
  • Password:

MongoDB support is based on Spring Data MongoDB (reference manual and Javadoc).

In order to use it in your project, add the following snippet to your pom.xml:

<dependency>
    <groupId>org.resthub</groupId>
    <artifactId>resthub-mongodb</artifactId>
    <version>2.1.4</version>
</dependency>

In order to import the default configuration, your should activate the resthub-mongodb Spring profile in your WebAppInitializer class:

XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.getEnvironment().setActiveProfiles("resthub-mongodb", "resthub-web-server");

You also need to add an applicationContext.xml file in order to scan your repository package.

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mongo="http://www.springframework.org/schema/data/mongo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/data/mongo
                           http://www.springframework.org/schema/data/mongo/spring-mongo.xsd">

    <mongo:repositories base-package="com.myproject.repository" />

</beans>

You can customize them by adding a database.properties resource with one or more following keys customized with your values. You should include only the customized ones.

RESThub MongoDB default properties are:
  • database.dbname = resthub
  • database.host = localhost
  • database.port = 27017
  • database.username =
  • database.password =
  • database.connectionsPerHost = 10
  • database.threadsAllowedToBlockForConnectionMultiplier = 5
  • database.connectTimeout = 0
  • database.maxWaitTime = 120000
  • database.autoConnectRetry = false
  • database.socketKeepAlive = false
  • database.socketTimeout = 0
  • database.slaveOk = false
  • database.writeNumber = 0
  • database.writeTimeout = 0
  • database.writeFsync = false
public interface TodoRepository extends MongoRepository<Todo, String> {

    List<Todo> findByContentLike(String content);
}

RESThub Web Common comes with built-in XML and JSON support for serialization based on Jackson 2. RESThub uses Jackson 2 XML capabilities instead of JAXB since it is more flexible. For example, you don't need to add classes to a context. Please read Jackson annotation guide for details about configuration capabilities.

In order to use it in your project, add the following snippet to your pom.xml:

<dependency>
    <groupId>org.resthub</groupId>
    <artifactId>resthub-web-common</artifactId>
    <version>2.1.4</version>
</dependency>
// JSON
SampleResource r = (SampleResource) JsonHelper.deserialize(json, SampleResource.class);
JsonHelper.deserialize("{\"id\": 123, \"name\": \"Albert\", \"description\": \"desc\"}", SampleResource.class);

// XML
SampleResource r = (SampleResource) XmlHelper.deserialize(xml, SampleResource.class);
XmlHelper.deserialize("<sampleResource><description>desc</description><id>123</id><name>Albert</name></sampleResource>", SampleResource.class);

RESThub Web Server module is designed for REST webservices development. Both JSON (default) and XML serialization are supported out of the box.

Warning

Currently Jackson XML dataformat does not support non wrapped List serialization. As a consequence, the findAll (GET /) method is not supported for XML content-type yet. You can follow the related Jackson issue on GitHub.

It provides some abstract REST controller classes, and includes the following dependencies:
RESThub exception resolver allow to map common exceptions (Spring, JPA) to the right HTTP status codes:
  • IllegalArgumentException -> 400
  • ValidationException -> 400
  • NotFoundException, EntityNotFoundException and ObjectNotFoundException -> 404
  • NotImplementedException -> 501
  • EntityExistsException -> 409
  • Any uncatched exception -> 500

In order to use it in your project, add the following snippet to your pom.xml:

<dependency>
    <groupId>org.resthub</groupId>
    <artifactId>resthub-web-server</artifactId>
    <version>2.1.4</version>
</dependency>

In order to import the default configuration, your should activate the resthub-web-server Spring profile in your WebAppInitializer class:

XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.getEnvironment().setActiveProfiles("resthub-web-server", "resthub-mongodb");

RESThub comes with a REST controller that allows you to create a CRUD webservice in a few lines. You have the choice to use a 2 layers (Controller -> Repository) or 3 layers (Controller -> Service -> Repository) software design.

You can find more details about these generic webservices, including their REST API description, on RESThub Javadoc.

2 layers software design

@Controller @RequestMapping("/repository-based")
public class SampleRestController extends RepositoryBasedRestController<Sample, Long, WebSampleResourceRepository> {

    @Override @Inject
    public void setRepository(WebSampleResourceRepository repository) {
        this.repository = repository;
    }
}

3 layers software design

@Controller @RequestMapping("/service-based")
public class SampleRestController extends ServiceBasedRestController<Sample, Long, SampleService> {

    @Override @Inject
    public void setService(SampleService service) {
        this.service = service;
    }
}

@Named("sampleService")
public class SampleServiceImpl extends CrudServiceImpl<Sample, Long, SampleRepository> implements SampleService {

    @Override @Inject
    public void setRepository(SampleRepository SampleRepository) {
        super.setRepository(SampleRepository);
    }
}

By default, generic controller use the database identifier (table primary key for JPA on MongoDB ID) in URLs to identify a resource. You can change this behaviour by overriding controller implementations to use the field you want. For example, this is common to use a human readable identifier called reference or slug to identify a resource. You can do that with generic repositories only by overriding findById() controller method:

@Controller @RequestMapping("/sample")
public class SluggableSampleController extends RepositoryBasedRestController<Sample, String, SampleRepository> {

    @Override @Inject
    public void setRepository(SampleRepository repository) {
        this.repository = repository;
    }

    @Override
    public Sample findById(@PathVariable String id) {
        Sample sample = this.repository.findBySlug(id);
        if (sample == null) {
            throw new NotFoundException();
        }
        return sample;
    }
}

With default behaviour we have URL like GET /sample/32. With sluggable behaviour we have URL lke GET /sample/niceref.

Warning

Be aware that when you override a Spring MVC controller method, your new method automatically reuse method level annotations from parent classes, but not parameter level annotations. That's why you need to specify parameters annotations again in order to make it work, like in the previous code sample.

Spring MVC provides out-of-the-box support for returning your domain model in JSON, using Jackson under the covers. However, often you may find that you want to return different views of the data, depending on the method that is invoked. Thanks to RESThub support for custom JSON views (based on Marty Pitt implementation), it is possible easily.

Usual use cases for using custom JSON Views are :
  • Fix serialization issues in a flexible way (not like @JsonIgnore or @JsonBackReference annotation) for children-parent relations
  • Avoid loading too much data when used with JPA lazy loading + OpenSessionInView filter
  • Sometimes avoid to send some information to the client, for example a password field for a User class (needed in BO but not in FO for security reasons)

In order to use it, just add one or more JsonView interfaces (usually declared in the same java file than your domain class), in our case SummaryView. Please have a look to Jackson JsonView documentation for more details.

public class Book {

    @JsonView(SummaryView.class)
    private Integer id;

    private String title;

    @JsonView(SummaryView.class)
    private String author;

    private String review;

    public static interface SummaryView {}
}

Usage for the JsonView is activated on a per controller method or class basis with the @ResponseView annotation like bellow :

@RequestMapping("{id}/summary")
@ResponseView(Book.SummaryView.class)
public @ResponseBody Book getSummary(@PathVariable("id") Integer id)
{
    return data.get(id - 1);
}

@RequestMapping("{id}")
public @ResponseBody Book getDetail(@PathVariable("id") Integer id)
{
    return data.get(id - 1);
}

The first method getSummary() will only serialize id and author properties, and getDetail() will serialize all properties. It also work on collection (List<Book> for example).

The previous SluggableSampleController example shows one thing: when your application starts to grow, you usually want to address some specific needs:

  • tailoring data for your client (security, performance...)
  • changing your application behaviour without changing service contracts with your clients

For that, you often need to decorrelate serialized objects (DTOs) from your model.

RESThub includes ModelMapper in its resthub-common module.

ModelMapper modelMapper = new ModelMapper();
UserDTO userDTO = modelMapper.map(user, UserDTO.class);

Modelmapper has sensible defaults and can often map objects without additional configuration. For specific needs, you can use property maps.

In order to make JS client application debugging easier, RESThub provides a webservice used to send client logs to the server. In order to activate it, you should enable the resthub-client-logging Spring profile.

POST api/log webservice expect this kind of body:

{"level":"warn","message":"log message","time":"2012-11-13T08:18:52.972Z"}

POST api/logs webservice expect this kind of body:

[{"level":"warn","message":"log message 1","time":"2012-11-13T08:18:53.342Z"},
{"level":"info","message":"log message 1","time":"2012-11-13T08:18:52.972Z"}]

You should add your own Exception handlers in order to handle your application custom exceptions by using @ControllerAdvice (will be scan like a bean in your classpath) and @ExceptionHandler annotations :

@ControllerAdvice
public class ResthubExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(value={
            MyFirstException.class,
            MySecondException.class
    })
    public ResponseEntity<Object> handleCustomException(Exception ex, WebRequest request) {
        // ...

        return new ResponseEntity<Object>(body, headers, status);
    }

}

RESThub Web client module aims to give you an easy way to request other REST webservices. It is based on AsyncHttpClient and provides a client API wrapper and OAuth2 support.

In order to limit conflicts it has no dependency on Spring, but only on:

In order to use it in your project, add the following snippet to your pom.xml:

<dependency>
    <groupId>org.resthub</groupId>
    <artifactId>resthub-web-client</artifactId>
    <version>2.1.4</version>
</dependency>

You can use resthub web client in a synchronous or asynchronous way. The synchronous API is easy to use, but blocks the current Thread until the remote server sends the full Response.

// One-liner version
Sample s = httpClient.url("http//...").jsonPost(new Sample("toto")).resource(Sample.class);

// List<T> and Page<T> use TypeReference due to Java type erasure issue
List<Sample> p = httpClient.url("http//...").jsonGet().resource(new TypeReference<List<Sample>>() {});
Page<Sample> p = httpClient.url("http//...").jsonGet().resource(new TypeReference<Page<Sample>>() {});

Asynchronous API is quite the same, every HTTP request returns a Future <Response> object. Just call get() on this object in order to make the call synchronous. The Future.get() method can throw Exceptions, so the method call should be surrounded by a try/catch or let the exceptions bubble up.

// 4 lines example
Client httpClient = new Client();
Future<Response> fr = httpClient.url("http//...").asyncJsonPost(new Sample("toto"));
// do some computation while we're waiting for the response...

// calling .get() makes the code synchronous again!
Sample s = httpClient.url("http//...").asyncJsonPost(new Sample("toto")).get().resource(Sample.class);

Because the remote web server sometimes responds 4xx (client error) and 5xx (server error) HTTP status codes, RESThub HTTP Client wraps those error statuses and throws specific runtime exceptions.

Here is an example of a simple OAuth2 support

String username = "test";
String password = "t&5t";
String clientId = "app1";
String clientSecret = "";
String accessTokenUrl = "http://.../oauth/token";

Client httpClient = new Client().setOAuth2(username, password, accessTokenUrl, clientId, clientSecret);
String result = httpClient.url("http://.../api/sample").get().getBody();

You can also use a specific OAuth2 configuration. For example, you can override the HTTP Header used to send the OAuth token.

OAuth2Config.Builder builder = new OAuth2Config.Builder();
builder.setAccessTokenEndpoint("http://.../oauth/token")
       .setUsername("test").setPassword("t&5t")
       .setClientId("app1").setClientSecret("")
       .setOAuth2Scheme("OAuth"); // override default OAuth HTTP Header name

Client httpClient = new Client().setOAuth2Builder(builder);
String result = httpClient.url("http://.../api/sample").get().getBody();

In a RIA, form validation could be a heavy process because you have to implement validation on both client and server side of your application.

To be able to build, on the client side, a validation behaviour based on server side constraints definition, RESThub provides an API to export, for a given model class, the complete list of its constraints definitions.

RESThub Spring Stack integrates the JSR303 specification (BeanValidation) and its reference implementation: Hibernate Validator.

These validations constraints are, in fact, annotations held by a Java Bean Model. e.g :

@NotNull
public String getLogin() {
    return this.login;
}

All these constraints and their parameters are exported by RESThub Validation API.

RESThub provides, on the client side, a full support of this API to implement client side validation natively (see Backbone Stack documentation).

Validation API is not activated by default and should be first configured.

To activate, edit your WebAppInitializer and add resthub-validation as a spring active profile :

public class WebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.getEnvironment().setActiveProfiles("resthub-jpa", "resthub-web-server", "resthub-validation");

        ...
    }
}

Validation REST API can then be reached through /api/validation but takes some parameters :

  1. className

    Mandatory path parameter containing the complete className of the Java Bean to export (i.e. package + className - e.g. org.resthub.validation.model.User). This parameter must be provided. If not or if an invalid className is provided, a 404 NotFound response is returned.

    For example, you can reach validation API at: http://localhost:8181/api/validation/org.resthub.validation.model.User

  2. locale

    As an optional request parameter, the API takes the locale string indicating your internationalization preferences. You can then provide a valid i18n locale string to choose the desired message locale.

    e.g : http://localhost:8181/api/validation/org.resthub.validation.model.User?locale=en-us

    Available locales are those supported by Hibernate Validator or provided by your custom properties files. If no locale parameter is provided or if the locale parameter is invalid, the default server locale is used.

    If some of your validation constraints (e.g. custom ones) doesn't have any default error message, only the key is exported by the API (e.g. org.resthub.validator.constraints.TelephoneNumber.message).

The response format could be XML or JSON and contains the following:

  • The complete model className

  • A list of constraints (JSON object or dedicated XML element) containing all Java Bean property description.

  • Each property contains a list (JSON array or multiple XML element) of its constraints.

  • Each constraint contains different properties:

    • type: contains the constraint type (e.g. NotNull, Size, Email).
    • message: contains the constraint error message.
    • any other(s) property(ies) depending on the constraint type and its custom parameters (e.g. the Size constraint contains two additionals properties min and max). To get the complete list of JSR303 parameters, see specification, for hibernate validator, see documentation

JSON sample:

{
    "model": "org.resthub.validation.model.User",
    "constraints": {
        "lastName": [{
            "type": "NotBlank",
            "message": "may not be empty"
        }],
        "email": [{
            "type": "NotNull",
            "message": "may not be null"
        }, {
            "type": "Email",
            "message": "not a well-formed email address",
            "flags": [],
            "regexp": ".*"
        }],
        "login": [{
            "type": "NotNull",
            "message": "may not be null"
        }, {
            "type": "Length",
            "message": "length must be between 8 and 2147483647",
            "min": 8,
            "max": 2147483647
        }],
        "firstName": [{
            "type": "NotBlank",
            "message": "may not be empty"
        }]
    }
}

XML sample:

<ModelConstraint>
    <model>org.resthub.validation.model.User</model>
    <constraints>
        <lastName>
            <type>NotBlank</type>
            <message>may not be empty</message>
        </lastName>
        <email>
            <type>NotNull</type>
            <message>may not be null</message>
        </email>
        <email>
            <type>Email</type>
            <message>not a well-formed email address</message>
            <regexp>.*</regexp>
        </email>
        <login>
            <type>NotNull</type>
            <message>may not be null</message>
        </login>
        <login>
            <type>Length</type>
            <message>length must be between 8 and 2147483647</message>
            <min>8</min>
            <max>2147483647</max>
        </login>
        <firstName>
            <type>NotBlank</type>
            <message>may not be empty</message>
        </firstName>
    </constraints>
</ModelConstraint>

RESThub Validation API is based on JSR303 specification (BeanValidation) Validation constraints. Any standard BeanValidation Constraint is supported (and exported) by this API.

As Hibernate Validator is used as BeanValidation implementation, RESThub Validation also exports and supports specific Hibernate Validators constraints which format are JSR303 compliant are also supported. More globally, any extension of JSR303 specification would be supported if the standard BeanValidation constraint definition API is used.

The following test stack is included in the RESThub test module:
RESThub also provides generic classes in order to make testing easier.
  • AbstractTest: base class for your non transactional Spring aware unit tests
  • AbstractTransactionalTest: base class for your transactional unit tests, preconfigured with Spring test framework
  • AbstractWebTest: base class for your unit tests that need to run an embedded servlet container.

In order to use it in your project, add the following snippet to your pom.xml:

<dependency>
    <groupId>org.resthub</groupId>
    <artifactId>resthub-test</artifactId>
    <version>2.1.4</version>
    <scope>test</scope>
</dependency>

It is recommended to initialize and cleanup test data shared by your tests using methods annotated with TestNG's @BeforeMethod and @AfterMethod and using your repository or service classes.

Warning

With JPA the default deleteAll() method does not manage cascade delete, so for your data cleanup you should use the following code in order to get your entities removed with cascade delete support:

Iterable<MyEntity> list = repository.findAll();
for (MyEntity entity : list) {
    repository.delete(entity);
}

AbstractTest or AbstractTransactionalTest

@ActiveProfiles("resthub-jpa")
public class SampleRepositoryTest extends AbstractTransactionalTest {

    private SampleRepository repository;

    @Inject
    public void setRepository(SampleRepository repository) {
        this.repository = repository;
    }

    @AfterMethod
    public void tearDown() {
        for (Sample resource : repository.findAll()) {
            repository.delete(resource);
        }
    }

    @Test
    public void testSave() {
        Sample entity = repository.save(new Sample());
        Assertions.assertThat(repository.exists(entity.getId())).isTrue();
    }
}

AbstractWebTest

public class SampleRestControllerTest extends AbstractWebTest {

    public SampleRestControllerTest() {
        // Call AbstractWebTest(String profiles) constructor
        super("resthub-web-server,resthub-jpa");
    }

    // Cleanup after each test
    @AfterMethod
    public void tearDown() {
        this.request("sample").delete();
    }

    @Test
    public void testCreateResource() {
        Sample r = this.request("sample").jsonPost(new Sample("toto")).resource(Sample.class);
        Assertions.assertThat(r).isNotNull();
        Assertions.assertThat(r.getName()).isEqualTo("toto");
    }
}

A sample assertion

Assertions.assertThat(result).contains("Albert");

A good practice is to separate unit tests from integration tests. The unit tests are designed to test only a specific layer of your application, ignoring other layers by mocking them (see Mockito). The integration tests are designed to test all the layers of your application in real condition with complex scenarii.

Maven allow us to do this separation by introducing the integration-test phase. To use this phase, add the following snippet to your pom.xml:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.15</version>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
</plugin>

With this plugin, Maven will seek Java files matching "*IT.java" in test directory. And run them during the integration-test phase.

You have 2 way (mutually exclusives) for writing you integration tests. Both approaches have pros and cons, so choose the one that fit the best to your needs. In both case the test you write is not in a Spring context (Spring is runned in the embeded Jety server), so you should write your test using mainly RESThub web client (that does not ue Spring at all) and assertions.

Extend your test with AbstractWebTest (as the exemple above). This class will take care to run jetty. Jetty will run once (by default) for all tests and will stop at the end of the JVM.

Add the following snippet to the jetty configuration in your pom.xml:

<plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>start-jetty</id>
            <phase>pre-integration-test</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <scanIntervalSeconds>0</scanIntervalSeconds>
                <daemon>true</daemon>
            </configuration>
        </execution>
        <execution>
            <id>stop-jetty</id>
            <phase>post-integration-test</phase>
            <goals>
                <goal>stop</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Now if you build the project, maven will run unit tests, then package the application, then run jetty, then run integration test en finaly stop jetty. You can also run your application with jetty:run and run separately and manualy you integration test in your IDE. It's usefull to build quickly all your integration tests.

Spring MVC Router adds route mapping capacity to any "Spring MVC based" webapp à la PlayFramework or Ruby on Rails. For more details, check its detailed documentation.

Spring AMQP Hessian is a high performance and easy to monitore RPC mechanism based on RabbitMQ client and Hessian. For more details, check its detailed documentation.