Web server
RESThub Web Server module is designed for REST webservices development. Both JSON (default) and XML serialization are supported out of the box.
It provides some abstract REST controller classes, and includes the following dependencies:
- Spring MVC 4.1.1 (reference manual)
- Jackson (documentation)
RESThub exception resolver allow to map common exceptions (Spring, JPA) to the right HTTP status codes:
IllegalArgumentException
-> 400ValidationException
-> 400NotFoundException
,EntityNotFoundException
andObjectNotFoundException
-> 404NotImplementedException
-> 501EntityExistsException
-> 409Any uncatched exception
-> 500
Configuration
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.2.0</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");
Usage
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);
}
}
Sluggable controller
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
.
Custom JSON Views
Spring MVC provides out-of-the-box support for returning your domain model in JSON, using Jackson under the covers.
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 Jackson
@JsonView
annotation like bellow :
@RequestMapping("{id}/summary")
@JsonView(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).
Register Jackson Modules
Jackson allows to register modules to enhance its capabilities (e.g. JodaTime module). Resthub provides a simple mean to register modules to the Jackson object mapper in the spring context:
<bean id="jacksonModules" parent="resthubJacksonModules">
<property name="sourceList">
<array merge="true" >
<value type="java.lang.Class">com.fasterxml.jackson.datatype.joda.JodaModule</value>
</array>
</property>
</bean>
Note that parent="resthubJacksonModules"
is mandatory
Model and DTOs with ModelMapper
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 suggests ModelMapper 0.7.2 as an optional dependency in resthub-common module.
You can include it in your project pom.xml:
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>0.7.2</version>
</dependency>
<dependency>
<groupId>org.modelmapper.extensions</groupId>
<artifactId>modelmapper-spring</artifactId>
<version>0.7.2</version>
</dependency>
and then use it in your project:
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.
Client logging
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"}]
Exception Mapping
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);
}
}
CORS Filter
RESThub includes an external CORS filter to allow communication between front and back on different domains. CORS Filter is provided by a third party library: http://software.dzhuvinov.com/cors-filter.html, you can find exhaustive configuration options here but we provide below some minimalistic configuration samples.
Configuration below defines a filter on your webapp allowing any request from domain http://example.com
for any HTTP method in
OPTIONS, GET, POST, PUT, DELETE, HEAD
and allow headers Accept
and Content-Type
.
- in a WebappInitializer (recommended)
public class WebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
...
FilterRegistration corsFilter = servletContext.addFilter("cors", CORSFilter.class);
corsFilter.addMappingForUrlPatterns(null, false, "/*");
corsFilter.setInitParameter("cors.allowOrigin", "http://example.com");
corsFilter.setInitParameter("cors.supportedMethods", "OPTIONS, GET, POST, PUT, DELETE, HEAD");
corsFilter.setInitParameter("cors.supportedHeaders", "Accept, Content-Type");
...
}
}
- in a web.xml
...
<filter>
<filter-name>CORSFilter</filter-name>
<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
<init-param>
<param-name>cors.allowOrigin</param-name>
<param-value>http://example.com</param-value>
<param-name>cors.supportedMethods</param-name>
<param-value>OPTIONS, GET, POST, PUT, DELETE, HEAD</param-value>
<param-name>cors.supportedHeaders</param-name>
<param-value>Accept, Content-Type</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORSFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...