Binding requests and marshalling responses

This recipe explains how to configure Spring MVC for REST handlers to be as integrated as possible with their business domain. We focus on designing self-explanatory method handlers, externalized type conversions, and abstracted response marshalling (serialization to specific formats such as json, xml, csv, and so on).

Getting ready

We are going to review the configuration changes applied to the cloudstreetmarket-api webapp in order to set up a Type conversion from either a request parameter or a URI template variable.

We will see how to configure automatic marshalling (for responses) into json. We will focus on two very simple method handlers created for this chapter.

How to do it...

The following steps describe the codebase changes that relate to the request binding and the response marshalling configuration:

  1. From the Git Perspective in Eclipse, check out the latest version of the branch v4.x.x. Then run a maven clean install command on the cloudstreetmarket-parent module. To do this, right-click on the module, select Run as… | Maven Clean, then select Run as… | Maven Install again. After this, select Maven Update Project to synchronize Eclipse with the Maven configuration. To do so, right-click on the module and then select Maven | Update Project….
  2. The main configuration changes are in the dispatcher-context.xml file (in the cloudstreetmarket-api module). The RequestMappingHandlerAdapter bean has been defined the three webBindingInitializer, messageConverters and customArgumentResolvers properties:
    <bean class="org.sfw.web...
      method.annotation.RequestMappingHandlerAdapter">
      <property name="webBindingInitializer">
        <bean class="org.sfw...
         support.ConfigurableWebBindingInitializer">
          <property name="conversionService" ref="conversionService"/>
        </bean>
        </property>
      <property name="messageConverters">
        <list>
            <ref bean="jsonConverter"/>
          </list>
      </property>
      <property name="customArgumentResolvers">
        <list>
          <bean class="net.kaczmarzyk.spring.data.jpa.web.
          SpecificationArgumentResolver"/>
          <bean	class="org.sfw.data.web.PageableHandlerMethodArgumentResolver">
              <property name="pageParameterName" value="pn"/>
              <property name="sizeParameterName" value="ps"/>
              </bean>
        </list>
      </property>
      <property name="requireSession" value="false"/>
    </bean>
      
    <bean id="jsonConverter" class="org.sfw...
        converter.json.MappingJackson2HttpMessageConverter">
        <property name="supportedMediaTypes" value="application/json"/>
      <property name="objectMapper">
        <bean class="com.fasterxml.jackson. databind.ObjectMapper">
          <property name="dateFormat">
         <bean class="java.text.SimpleDateFormat">
           <constructor-arg type="java.lang.String" value="yyyy-MM-dd HH:mm"/>
           </bean>
          </property>
        </bean>
        </property>
    </bean>
    <bean id="conversionService" class="org.sfw.format.support.FormattingConversionServiceFactoryBean">
      <property name="converters">
        <list>
          <bean class="edu.zc.csm.core. converters.StringToStockProduct"/>
        </list>
      </property>
    </bean>
  3. The following Maven dependencies have been added to the parent project (and indirectly to the core and API projects):
          <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
                 <artifactId>jackson-annotations</artifactId>
                 <version>2.5.1</version>
           </dependency>
             <dependency>
                 <groupId>com.fasterxml.jackson.core</groupId>
                 <artifactId>jackson-databind</artifactId>
                 <version>2.5.1</version>
             </dependency>
             <dependency>
                 <groupId>commons-collections</groupId>
                 <artifactId>commons-collections</artifactId>
                 <version>3.2</version>
             </dependency>
             <dependency>
                 <groupId>net.kaczmarzyk</groupId>
                 <artifactId>specification-arg-resolver</artifactId>
                 <version>0.4.1</version>
             </dependency>
  4. In our controllers' superclass CloudstreetApiWCI, the allowDateBinding method has been created with an @InitBinder annotation:
      private DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    
      @InitBinder
      public void allowDateBinding ( WebDataBinder binder ){
        binder.registerCustomEditor( Date.class, new CustomDateEditor( df, true ));
      }
  5. All this configuration allows us to define self-explanatory and logic-less method handlers such as the getHistoIndex() method in IndexController:
      @RequestMapping(value="/{market}/{index}/histo", method=GET)
      public HistoProductDTO getHistoIndex(
        @PathVariable("market") MarketCode market, 
        @PathVariable("index") String indexCode,
        @RequestParam(value="fd",defaultValue="") Date fromDate,
        @RequestParam(value="td",defaultValue="") Date toDate,
        @RequestParam(value="i",defaultValue="MINUTE_30") QuotesInterval interval){
        return marketService.getHistoIndex(indexCode, market, fromDate, toDate, interval);
      }
  6. Now deploy the cloudstreetmarket-api module and restart the server. To do so, start by right-clicking on the Tomcat server in the Servers tab:
    How to do it...
  7. Then select Add and Remove…from the right-click menu. In the Add and Remove… window, make sure you have the following configuration set up, and start the server.
    How to do it...
  8. Try to call the URL http://localhost:8080/api/indices/EUROPE/^GDAXI/histo.json.
  9. This URL targets the presented getHistoIndex method handler and produces the following json output:
    How to do it...
  10. Now let's have a look at StockProductController. It hosts the following method handler:
    @RequestMapping(value="/{code}", method=GET)
    @ResponseStatus(HttpStatus.OK)
    public StockProductOverviewDTO getByCode(
    @PathVariable(value="code") StockProduct stock){
      return StockProductOverviewDTO.build(stock);
    }

    Tip

    There are no explicit calls to any service layer here. Also, the return. Type of the method handler, which is StockProductOverviewDTO, is a simple POJO. The marshalling of the response body is done transparently.

  11. In the cloudstreetmarket-core module, the StringToStockProduct converter must be presented because it was required to achieve the previous step:
    @Component
    public class StringToStockProduct implements Converter<String, StockProduct> {
    
    @Autowired
    private ProductRepository<StockProduct> productRepository;
      
    @Override
    public StockProduct convert(String code) {
      StockProduct stock = productRepository.findOne(code);
      if(stock == null){
        throw new NoResultException("No result has been found for the value "+ code +" !");
      }
      return stock;
    }
    }

    Tip

    This converter was registered earlier (step 2) in conversionService.

  12. Try to call the URL http://localhost:8080/api/products/stocks/NXT.L.json. This should target the presented getByCode handler and produce the following json response:
    How to do it...

How it works...

To understand how the preceding elements work together, we must introduce the key role of RequestMappingHandlerAdapter.

A super RequestMappingHandlerAdapter bean

We briefly introduced RequestMappingHandlerAdapter in Chapter 2, Designing a Microservice Architecture with Spring MVC. This bean implements the high-level HandlerAdapter interface, which allows custom MVC core-workflow implementations. RequestMappingHandlerAdapter is the native implementation that comes with the framework.

We mentioned that RequestMappingHandlerAdapter and RequestMappingHandlerMapping respectively are two replacement classes for the now deprecated AnnotationMethodHandlerAdapter and DefaultAnnotationHandlerMapping.

In fact, RequestMappingHandlerAdapter provides better centralization for all the method handlers. Also, some new capabilities have been opened for HandlerInterceptors and HandlerExceptionResolver.

Tip

Practically, the handler argument that can be found in the preHandle, postHandle, and afterCompletion methods’ signature (WebContentInterceptors) can be casted into HandlerMethod objects. The HandlerMethod Type offers interesting examination methods such as getReturnType, getMethodAnnotation, getMethodParameters.

Also, in regard to RequestMappingHandlerAdapter and RequestMappingHandlerMapping, the Spring documentation specifies that:

 

"The new support classes are enabled by default by the MVC namespace and the MVC Java config but must be configured explicitly if using neither."

 
 --JavaDoc

In both our web apps, we make use of the MVC namespace specifically with the <mvc:annotation-driven/> element.

This element is enjoyable from the configuration-by-default feature it activates on a couple of web features. However, in a lot of situations, different behaviors might still be expected.

In most cases, custom definitions are made either on the namespace itself or on with RequestMappingHandlerAdapter.

Broad support for @RequestMapping annotations

The main role of RequestMappingHandlerAdapter is to provide support and customization for handlers of the Type HandlerMethod. These handlers are bound to @RequestMapping annotations.

 

"A HandlerMethod object encapsulates information about a handler method consisting of a method and a bean. Provides convenient access to method parameters, the method return value, method annotations."

 
 --JavaDoc

The RequestMappingHandlerAdapter gets most of its support methods from the historical DefaultAnnotationHandlerMapping. Let's take a closer look at the methods that particularly interest us.

setMessageConverters

The messageConverters template can be registered through the setMessageConverters setter as List<HttpMessageConverter>. Spring will perform the unmarshalling of an HTTP request's body for us into Java object(s) and the marshalling of a Java resource into an HTTP response's body.

It is important to remember that the framework provides converter implementations for the main media types. These are registered by default with RequestMappingHandlerAdapter and RestTemplate (on the client side).

The following table summarizes the native converters we can make use of:

Provided implementations

Supported media types by default

(Default) behavior

StringHttpMessageConverter

text/*

Writes with a text/plain content type.

FormHttpMessageConverter

application/x-www-form-urlencoded

Form data is read from and written into MultiValueMap<String, String>.

ByteArrayHttpMessageConverter

*/*

Writes with an application/octet-stream content type (can be overridden).

MarshallingHttpMessageConverter

text/xml and application/xml

Requires org.springframework.oxm and a Marshaller /Unmarshaller.

MappingJackson2HttpMessageConverter

application/json

JSON mapping can be customized with Jackson annotations. If specific types need to be mapped, a custom ObjectMapper property has to be injected.

MappingJackson2XmlHttpMessageConverter

application/xml

XML mapping can be customized with JAXB or Jackson annotations. If specific types need to be mapped, a custom XmlMapper property has to be injected into the ObjectMapper property.

SourceHttpMessageConverter

text/xml and application/xml

Can read and write javax.xml.transform.Source from the HTTP request and response. Only DOMSource, SAXSource, and StreamSource are supported.

BufferedImageHttpMessageConverter

 

Can read and write java.awt.image.BufferedImage from the HTTP request and response.

Have a look at the following address to get information on remoting and web services using Spring: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/remoting.html.

In our application, we have overridden the definition of the two native MappingJackson2HttpMessageConverter and MarshallingHttpMessageConverter classes.

setCustomArgumentResolvers

The setCustomArgumentResolvers setter provides to RequestMappingHandlerAdapter a support for custom arguments. If you remember back in Chapter 2, Using Spring MVC to Support Responsive Designs, the very first recipe talks about supported annotations for arguments. At the time, we saw @PathVariable, @MatrixVariable, @RequestBody, @RequestParam, and so on.

All these annotations are built-in ArgumentResolver. They are mapped to registered implementations to externally prepopulate arguments from different sources.

We have the possibility to define our own annotations and prepopulate our method arguments following the required business logic. These resolvers must implement the HandlerMethodArgumentResolver interface.

The development of our application didn't specifically require the development of customArgumentResolver. However, we have registered two of them:

  • net.kaczmarzyk.spring.data.jpa.web.SpecificationArgumentResolver: This resolver is a third-party library that we are going to explain in the 3rd recipe of this chapter
  • org.springframework.data.web.PageableHandlerMethodArgumentResolver: This will allow the automatic resolution of pagination parameters in order to use the native Spring Data pagination support
setWebBindingInitializer

A WebBindingInitializer interface is a callback interface to globally initialize WebDataBinder and perform data binding in the context of web requests.

Before going forward, we must stop and revisit the 4th step of the recipe that defined the following method:

  @InitBinder
  public void allowDateBinding(WebDataBinder binder){
    binder.registerCustomEditor(Date.class, new CustomDateEditor( df, true ));
  }

We define this method in a controller to register an abstracted Date conversion binding using a PropertyEditor.

Now let's focus on the WebDataBinder argument. In this section, we are talking about the initialized part. The WebDataBinder interface provides a couple of interesting methods. These methods are mostly validation-related (validate, setRequiredFields, isAllowed, getErrors, and so on) and conversion-related (getTypeConverter, registerCustomEditor, setBindingErrorProcessor, getBindingResult, and so on).

A WebDataBinder argument can also be set as a ConversionService object. Rather than doing this locally in our allowDateBinding method (with the WebDataBinder.setConversion setter), we are going to use a global and declarative initialization.

The WebBindingInitializer implementation we have chosen is the Spring ConfigurableWebBindingInitializer bean. It is indeed a convenient class for declarative configurations in a Spring application context. It enables the reusability of preconfigured initializers over multiple controllers/handlers.

In our case, the WebBindingInitializer will be useful to globally initialize registered Type converters such as StringToStockProduct, but also to achieve the global exception handling we are aiming for.

The ConversionService API

The 11th step defines a StringToStockProduct converter that allows the definition of a lean and clean getByCode method handler:

@RequestMapping(value="/{code}", method=GET)
@ResponseStatus(HttpStatus.OK)
public StockProductOverviewDTO getByCode(
@PathVariable(value="code") StockProduct stock){
  return StockProductOverviewDTO.build(stock);
}

These converters can be used broadly among the Spring application for any conversion without being restricted to a request scope. Their use of Generics can be very beneficial. They are bound to a conversionService bean and there is no specific way to avoid their individual declaration.

Choosing between PropertyEditors or converters

The PropertyEditors and the converters from ConversionService might appear as an alternative to each other in their String-to-type use.

Spring heavily uses the concept of PropertyEditors to set properties for beans. In Spring MVC, they are meant to parse HTTP requests. Their declaration in Spring MVC is bound to the request scope.

Even if they can be initialized globally, you must see PropertyEditors as initially restricted scope elements. Seeing them this way legitimates their attachment to @InitBinder methods and WebBinderData. They are less generic than converters.

When using PropertyEditors for enums, Spring offers a naming convention that can avoid the individual declaration of enums. We will make use of this handy convention later on.

There's more...

We are going to look at other RequestMappingHandlerAdapter properties in the next recipes. For now, there is more to discuss about PropertyEditors and especially the built-in ones.

Built-in PropertyEditor implementations

The following PropertyEditors implementations come natively with Spring. They can be applied manually in all controllers for the purpose of binding. You will probably recognize CustomDateEditor, which has been registered in CloudstreetApiWCI.

Provided implementations

Default behavior

ByteArrayPropertyEditor

This is the editor for byte arrays. Strings will simply be converted to their corresponding byte representations. It is registered by default by BeanWrapperImpl.

ClassEditor

Parses strings represent classes to actual classes and the other way around. When a class is not found, an IllegalArgumentException exception is thrown. It is registered by default by BeanWrapperImpl.

CustomBooleanEditor

This is a customizable property editor for Boolean properties. It is registered by default by BeanWrapperImpl, but it can be overridden by registering a custom instance of it as a custom editor.

CustomCollectionEditor

This is the property editor for collections, converting any source collection to a given target collection type.

CustomDateEditor

This is a customizable property editor for java.util.Date, and supports a custom DateFormat. It is not registered by default. The user must register it as required in the appropriate format.

CustomNumberEditor

This is a customizable property editor for any number subclass such as Integer, Long, Float, or Double. It is registered by default by BeanWrapperImpl, but it can be overridden by registering a custom instance of it as a custom editor.

FileEditor

This editor is capable of resolving strings to java.io.File objects. It is registered by default by BeanWrapperImpl.

InputStreamEditor

This is a one-way property editor capable of taking a text string and producing InputStream (via an intermediate ResourceEditor and Resource). The InputStream properties may be directly set as strings. The default usage will not close the InputStream property. It is registered by default by BeanWrapperImpl.

The Spring IO reference document

Find more details about Type conversion and PropertyEditors in the Spring IO Reference document, check out: http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/validation.html.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset