Extending REST handlers to all HTTP methods

This is the core recipe of the chapter. We will detail how to use the Spring MVC method-handlers for HTTP methods that we haven't covered yet: the non-readonly ones.

Getting ready

We will see the returned status codes and the HTTP standards driving the use of the PUT, POST, and DELETE methods. This will get us to configure HTTP-compliant Spring MVC controllers.

We will also review how request-payload mapping annotations such as @RequestBody work under the hood and how to use them efficiently.

Finally, we open a window on Spring transactions, as it is a broad and important topic in itself.

How to do it…

Following the next steps will present the changes applied to two controllers, a service and a repository:

  1. From the Git Perspective in Eclipse, checkout the latest version of the branch v7.x.x. Then, run a maven clean install on the cloudstreetmarket-parent module (right-click on the module and go to Run as… | Maven Clean and then again go to Run as… | Maven Install) followed by a Maven Update project to synchronize Eclipse with the maven configuration (right-click on the module and then go to Maven | Update Project…).
  2. Run the Maven clean and Maven install commands on zipcloud-parent and then on cloudstreetmarket-parent. Then, go to Maven | Update Project.
  3. In this chapter, we are focused on two REST controllers: the UsersController and a newly created TransactionController.

    Note

    The TransactionController allows users to process financial transactions (and thus to buy or sell products).

  4. A simplified version of UserController is given here:
    @RestController
    @RequestMapping(value=USERS_PATH, produces={"application/xml", "application/json"})
    public class UsersController extends CloudstreetApiWCI{
      @RequestMapping(method=POST)
      @ResponseStatus(HttpStatus.CREATED)
      public void create(@RequestBody User user, 
      @RequestHeader(value="Spi", required=false) String 	guid, @RequestHeader(value="OAuthProvider", required=false) String provider,
      HttpServletResponse response) throws IllegalAccessException{
      ...
      response.setHeader(LOCATION_HEADER, USERS_PATH + user.getId());
      }
      @RequestMapping(method=PUT)
      @ResponseStatus(HttpStatus.OK)
      public void update(@RequestBody User user, 
        BindingResult result){
        ...
      }
      @RequestMapping(method=GET)
      @ResponseStatus(HttpStatus.OK)
      public Page<UserDTO> getAll(@PageableDefault(size=10, page=0) Pageable pageable){
      return communityService.getAll(pageable);
      }
      @RequestMapping(value="/{username}", method=GET)
      @ResponseStatus(HttpStatus.OK)
      public UserDTO get(@PathVariable String username){
        return communityService.getUser(username);
      }
      @RequestMapping(value="/{username}", method=DELETE)
      @ResponseStatus(HttpStatus.NO_CONTENT)
      public void delete(@PathVariable String username){
        communityService.delete(username);
      }
    }
  5. The TransactionController is represented here in a simplified version:
    @RestController
    @ExposesResourceFor(Transaction.class)
    @RequestMapping(value=ACTIONS_PATH + TRANSACTIONS_PATH, produces={"application/xml", "application/json"})
    public class TransactionController extends CloudstreetApiWCI<Transaction> {

    (The GET method-handlers given here come from previous recipes.)

      @RequestMapping(method=GET)
      @ResponseStatus(HttpStatus.OK)
      public PagedResources<TransactionResource> search(
        @RequestParam(value="user", required=false) String userName,
        @RequestParam(value="quote:[\d]+", required=false) Long quoteId,
        @RequestParam(value="ticker:[a-zA-Z0-9-:]+", required=false) String ticker,
        @PageableDefault(size=10, page=0, sort={"lastUpdate"}, direction=Direction.DESC) Pageable pageable){
        Page<Transaction> page = transactionService.findBy(pageable, userName, quoteId, ticker);
          return pagedAssembler.toResource(page, assembler);
      }
      @RequestMapping(value="/{id}", method=GET)
      @ResponseStatus(HttpStatus.OK)
    public TransactionResource get(@PathVariable(value="id") Long transactionId){
      return assembler.toResource(
        transactionService.get(transactionId));
      }

    (The PUT and DELETE method-handlers introduced here are non-readonly methods.)

      @RequestMapping(method=POST)
      @ResponseStatus(HttpStatus.CREATED)
    public TransactionResource post(@RequestBody Transaction transaction) {
        transactionService.hydrate(transaction);
        ...
      TransactionResource resource = assembler.toResource(transaction);
      response.setHeader(LOCATION_HEADER, resource.getLink("self").getHref());
        return resource;
      }
      @PreAuthorize("hasRole('ADMIN')")
      @RequestMapping(value="/{id}", method=DELETE)
      @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable(value="id") Long transactionId){
        transactionService.delete(transactionId);
      }
    }
  6. The call to the hydrate method in the post method prepares the Entity for underlying service uses. It populates its relationships from IDs received in the request payload.

    Note

    This technique will be applied to all the REST resources used for CRUD.

  7. Here are the details of the hydrate method in transactionServiceImpl:
    @Override
    public Transaction hydrate(final Transaction transaction) {
      
      if(transaction.getQuote().getId() != null){
        transaction.setQuote(
          stockQuoteRepository.findOne(
            transaction.getQuote().getId()));
      }
      if(transaction.getUser().getId() != null){
       transaction.setUser(userRepository.findOne(transaction.getUser().getId()));
      }
      if(transaction.getDate() == null){
        transaction.setDate(new Date());
      }
      return transaction;
    }

    Note

    Nothing amazing here; it is mainly about building our Entity to suit our needs. An interface can be created to standardize the practice.

  8. All the service layers have been reviewed to drive uniform database transactions.
  9. The service implementations are now annotated by default with @Transactional(readOnly = true). Check the following TransactionServiceImpl example:
    @Service
    @Transactional(readOnly = true)
    public class TransactionServiceImpl implements TransactionService{
      ...
    }
  10. The non-readonly methods of these service implementations override the class definition with the @Transactional annotation:
      @Override
      @Transactional
      public Transaction create(Transaction transaction) {
      if(!transactionRepository.findByUserAndQuote(transaction.getUser(), transaction.getQuote()).isEmpty()){
          throw new DataIntegrityViolationException("A transaction for the quote and the user already exists!");
        }
        return transactionRepository.save(transaction);
      }
  11. This principle has also been applied to custom repository implementations (such as IndexRepositoryImpl):
    @Repository
    @Transactional(readOnly = true)
    public class IndexRepositoryImpl implements IndexRepository{
      @PersistenceContext 
      private EntityManager em;
      
      @Autowired
      private IndexRepositoryJpa repo;
      ...
      @Override
      @Transactional
      public Index save(Index index) {
        return repo.save(index);
      }
      ...
    }

How it works...

First, let's quickly review the different CRUD services presented in the controllers of this recipe. The following table summarizes them:

URI

Method

Purpose

Normal response codes

/actions/transactions

GET

Search transactions

200 OK

/actions/transactions/{id}

GET

Get a transaction

200 OK

/actions/transactions

POST

Create a transaction

201 Created

/actions/transactions/{id}

DELETE

Delete a transaction

204 No Content

/users/login

POST

Logs in a user

200 OK

/users

GET

Get all

200 OK

/users/{username}

GET

Get a user

200 OK

/users

POST

Create a user

201 Created

/users/{username}

PUT

Update a user

200 OK

/users/{username}

DELETE

Delete a user

204 No Content

HTTP/1.1 specifications – RFC 7231 semantics and content

To understand the few decisions that have been taken in this recipe (and to legitimate them), we must shed some light on a few points of the HTTP specification.

Before starting, feel free to visit Internet standards track document (RFC 7231) for HTTP 1/1 related to Semantics and Content:

https://tools.ietf.org/html/rfc7231

Basic requirements

In the HTTP specification document, the request methods overview (section 4.1) states that it is a requirement for a server to support the GET and HEAD methods. All other request methods are optional.

The same section also specifies that a request made with a recognized method name (GET, POST, PUT, DELETE, and so on) but that doesn't match any method-handler should be responded with a 405 Not supported status code. Similarly, a request made with an unrecognized method name (nonstandard) should be responded with a 501 Not implemented status code. These two statements are natively supported and auto-configured by Spring MVC.

Safe and Idempotent methods

The document introduces introduces the Safe and Idempotent qualifiers that can be used to describe a request method. Safe methods are basically readonly methods. A client using such a method does not explicitly requests a state change and cannot expect a state change as a result of the request.

As the Safe word suggests, such methods can be trusted to not cause any harm to the system.

An important element is that we are considering the client's point of view. The concept of Safe methods don't prohibit the system from implementing "potentially" harmful operations or processes that are not effectively read only. Whatever happens, the client cannot be held responsible for it. Among all the HTTP methods, only the GET, HEAD, OPTIONS, and TRACE methods are defined as safe.

The specification makes use of the idempotent qualifier to identify HTTP requests that, when identically repeated, always produce the same consequences as the very first one. The client's point of view must be considered here.

The idempotent HTTP methods are GET, HEAD, OPTIONS, TRACE (the Safe methods) as well as PUT and DELETE.

A method's idempotence guarantees a client for example that sending a PUT request can be repeated even if a connection problem has occurred before any response is received.

Note

The client knows that repeating the request will have the same intended effect, even if the original request succeeded, though the response might differ.

Other method-specific constraints

The POST methods are usually associated with the creation of resources on a server. Therefore, this method should return the 201 (Created) status code with a location header that provides an identifier for the created resource.

However, if there hasn't been creation of resource, a POST method can (in practice) potentially return all types of status codes except 206 (Partial Content), 304 (Not Modified), and 416 (Range Not Satisfiable).

The result of a POST can sometimes be the representation of an existing resource. In that case, for example, the client can be redirected to that resource with a 303 status code and a Location header field. As an alternative to POST methods, PUT methods are usually chosen to update or alter the state of an existing resource, sending a 200 (OK) or a 204 (No Content) to the client.

Edge cases with inconsistent matches raise errors with 409 (Conflict) or 415 (Unsupported Media Type).

Edge cases of no match found for an update should induce the creation of the resource with a 201 (Created) status code.

Another set of constraints applies on the DELETE requests that are successfully received. Those should return a 204 (No Content) status code or a 200 (OK) if the deletion has been processed. If not, the status code should be 202 (Accepted).

Mapping request payloads with @RequestBody

In Chapter 4, Building a REST API for a Stateless Architecture, we have presented the RequestMappingHandlerAdapter. We have seen that Spring MVC delegates to this bean to provide an extended support to @RequestMapping annotations.

In this perspective, RequestMappingHandlerAdapter is the central piece to access and override HttpMessageConverters through getMessageConverters() and setMessageConverters(List<HttpMessageConverter<?>> messageConverters).

The role of @RequestBody annotations is tightly coupled to HttpMessageConverters. We will introduce the HttpMessageConverters now.

HttpMessageConverters

HttpMessageConverters, custom or native, are bound to specific mime types. They are used in the following instances:

  • To convert Java objects into HTTP response payloads. Selected from Accept request header mime types, they serve the @ResponseBody annotation's purposes (and indirectly @RestController annotations that abstract the @ResponseBody annotations).
  • To convert HTTP request payloads into Java objects. Selected from the Content-Type request header mime types, these converters are called when the @RequestBody annotation are present on a method handler argument.

More generally, HttpMessageConverters match the following HttpMessageConverter interface:

public interface HttpMessageConverter<T> {
  boolean canRead(Class<?> clazz, MediaType mediaType);
  boolean canWrite(Class<?> clazz, MediaType mediaType);
  List<MediaType> getSupportedMediaTypes();
  T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
  void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}

The getSupportedMediaTypes() method returns the list of mediaTypes (mime types) that a specific converter supports. This method is mainly used for reporting purposes and for the canRead and canWrite implementations. These canRead and canWrite eligibility methods are used by the framework to pick up at runtime the first HttpMessageConverter that either:

  • Matches the client-provided Content-Type request header for the given Java class targeted by @RequestBody
  • Matches the client-provided Accept request header for the Java class that the HTTP response-payload will correspond to (the Type targeted by @ResponseBody)

Provided HttpMessageConverters

With the latest versions of Spring MVC (4+), a few extra HttpMessageConverters come natively with the framework. We have thought that summarizing them would be helpful. The following table represents all the native HttpMessageConverters, the mime types, and the Java Types they can be associated with. Short descriptions, mostly coming from the JavaDoc, give more insight about each of them.

URI

Supported MediaTypes (by default)

Convert to/from

FormHttpMessage Converter

Can READ/WRITE application/x-www-form-urlencoded,

Can READ multipart/form-data.

MultiValueMap<String, ?>

For part conversions, it also embeds (by default) ByteArrayHttpMessageConverter, StringHttpMessageConverter and ResourceHttpMessageConverter.

AllEncompassing FormHttpMessage Converter

Can READ/WRITE application/x-www-form-urlencoded,

Can READ multipart/form-data.

MultiValueMap<String, ?>

This converter extends FormHttpMessageConverter embedding extra HttpMessageConverters JAXB or Jackson if they are found in the classpath for XML/JSON-based parts.

XmlAwareFormHttp MessageConverter

Can READ/WRITE application/x-www-form-urlencoded,

Can READ multipart/form-data.

MultiValueMap<String, ?>

This converter extends FormHttpMessageConverter, adding support for XML-based parts through a SourceHttpMessageConverter.

BufferedImageHttp MessageConverter

Can READ all media types that are supported by the registered image readers.

Can WRITE the media type of the first available registered image writer.

java.awt.image.BufferedImage

ByteArrayHttp MessageConverter

Can READ */*,

WRITE with application/octet-stream.

byte[]

GsonHttpMessage Converter

CAN READ/WRITE application/json, application/*+json.

java.lang.Object

Uses the Google Gson library's Gson class. This converter can be used to bind with typed beans or untyped HashMaps.

Jaxb2Collection HttpMessage Converter

Can READ XML collections.

T extends java.util.Collection

This converter can read collections that contain classes annotated with XmlRootElement and XmlType. Note that this converter does not support writing. (JAXB2 must be present on the classpath.)

Jaxb2RootElement HttpMessage Converter

Can READ/WRITE XML

java.lang.Object

This converter can read classes annotated with XmlRootElement and XmlType, and write classes annotated with XmlRootElement or subclasses thereof. (JAXB2 must be present on the classpath.)

MappingJackson2 HttpMessage Converter

Can READ/WRITE application/json, application/*+json.

java.lang.Object

Uses Jackson 2.x ObjectMapper. This converter can be used to bind with typed beans or untyped HashMap instances. (Jackson 2 must present on the classpath.)

MappingJackson2 XmlHttpMessage Converter

Can READ/WRITE application/xml, text/xml, application/*+xml.

java.lang.Object

This uses the Jackson 2.x extension component for reading and writing XML encoded data (https://github.com/FasterXML/jackson-dataformat-xml). (Jackson 2 must be present on the classpath.)

MarshallingHttp MessageConverter

Can READ/WRITE text/xml application/xml.

java.lang.Object

This uses Spring's Marshaller and Unmarshaller abstractions (OXM).

ObjectToStringHttp MessageConverter

Can READ/WRITE text/plain.

java.lang.Object

This uses StringHttpMessageConverter for reading and writing content and a ConversionService for converting the String content to and from the target object type. (It must be configured).

ProtobufHttp MessageConverter

Can READ application/json, application/xml, text/plain and application/x-protobuf.

Can WRITE application/json, application/xml, text/plain and application/x-protobuf, text/html.

javax.mail.Message

This uses Google protocol buffers (https://developers.google.com/protocol-buffers) to generate message Java classes you need to install the protoc binary.

ResourceHttp MessageConverter

Can READ/WRITE */*.

org.springframework.core.io.Resource

The Java Activation Framework (JAF), if available, is used to determine the content-type of written resources. If JAF is not available, application/octet-stream is used.

RssChannelHttp MessageConverter

Can READ/WRITE application/rss+xml.

com.rometools.rome.feed.rss.Channel

This converter can handle Channel objects from the ROME project (https://github.com/rometools). (ROME must be present on the classpath.)

AtomFeedHttp MessageConverter

Can READ/WRITE application/atom+xml.

com.rometools.rome.feed.atom.Feed

This can handle Atom feeds from the ROME project (https://github.com/rometools). (ROME must be present on the classpath.)

SourceHttpMessageConverter

Can READ/WRITE text/xml, application/xml, application/*-xml.

javax.xml.transform.Source

StringHttpMessageConverter

Can READ/WRITE */*.

java.lang.String

Using MappingJackson2HttpMessageConverter

In this recipe, the MappingJackson2HttpMessageConverter is used extensively. We used this converter for both the financial transaction creation/update side and the User-Preferences update side.

Alternatively, we used AngularJS to map an HTML form to a built json object whose properties match our Entities. Proceeding this way, we POST/PUT the json object as the application/json mime type.

This method has been preferred to posting an application/x-www-form-urlencoded form content, because we can actually map the object to an Entity. In our case, the form matches exactly a backend resource. This is a beneficial result (and constraint) of a REST design.

Using @RequestPart to upload an image

The @RequestPart annotation can be used to associate part of a multipart/form-data request with a method argument. It can be used with argument Types such as org.springframework.web.multipart.MultipartFile and javax.servlet.http.Part.

For any other argument Types, the content of the part is passed through an HttpMessageConverter just like @RequestBody.

The @RequestBody annotation has been implemented to handle the user-profile picture. Here's our sample implementation from the UserImageController:

    @RequestMapping(method=POST, produces={"application/json"})
    @ResponseStatus(HttpStatus.CREATED)
    public String save( @RequestPart("file") MultipartFile file, HttpServletResponse response){
    String extension = ImageUtil.getExtension(file.getOriginalFilename());
    String name = UUID.randomUUID().toString().concat(".").concat(extension);
    if (!file.isEmpty()) {
       try {
                byte[] bytes = file.getBytes();
                Path newPath = Paths.get(pathToUserPictures);
                Files.write(newPath, bytes, 	StandardOpenOption.CREATE);
       ...
  ...
  response.addHeader(LOCATION_HEADER, env.getProperty("pictures.user.endpoint").concat(name));
  return "Success";
  ...
  }

The file part of the request is injected as an argument. A new file is created on the server filesystem from the content of the request file. A new Location header is added to the Response with a link to the created image.

On the client side, this header is read and injected as background-image CSS property for our div (see user-account.html).

Transaction management

The recipe highlights the basic principles we applied to handle transactions across the different layers of our REST architecture. Transaction management is a whole chapter in itself and we are constrained here to present just an overview.

The simplistic approach

To build our transaction management, we kept in mind that Spring MVC Controllers are not transactional. Under this light, we cannot expect a transaction management over two different service calls in the same method handler of a Controller. Each service call starts a new transaction, and this transaction is expected to terminate when the result is returned.

We defined our services as @Transactional(readonly="true") at the Type level, then methods the that need Write access override this definition with an extra @Transactional annotation at the method level. The tenth step of our recipe presents the Transactional changes on the TransactionServiceImpl service. With the default propagation, transactions are maintained and reused between Transactional services, repositories, or methods.

By default, abstracted Spring Data JPA repositories are transactional. We only had to specify transactional behaviors to our custom repositories, as we did for our services.

The eleventh step of our recipe shows the Transactional changes made on the custom repository IndexRepositoryImpl.

There's more…

As mentioned earlier, we configured a consistent transaction management over the different layers of our application.

Transaction management

Our coverage is limited and we advise you to find external information about the following topics if you are not familiar with them.

ACID properties

Four properties/concepts are frequently used to assess the transaction's reliability. It is therefore useful and important to keep them in mind when designing transactions. Those properties are Atomicity, Consistency, Isolation and Durability. Read more about ACID transactions on the Wikipedia page:

https://en.wikipedia.org/wiki/ACID

Global versus local transactions

We only defined local transactions in the application. Local transactions are managed at the application level and cannot be propagated across multiple Tomcat servers. Also, local transactions cannot ensure consistency when more than one transactional resource type is involved. For example, in a use case of database operations associated with messaging, when we rollback a message that couldn’t have been delivered, we might need to also rollback the related database operations that have happened beforehand. Only global transactions implementing 2-step commits can take on this kind of responsibility. Global transactions are handled by JTA transaction manager implementations.

Read more about the difference in this Spring reference document:

http://docs.spring.io/spring/docs/2.0.8/reference/transaction.html

Historically, JTA transaction managers were exclusively provided by J2EE/JEE containers. With application-level JTA transaction manager implementations, we now have other alternatives such as Atomikos (http://www.atomikos.com), Bitronix (https://github.com/bitronix/btm), or JOTM (http://jotm.ow2.org/xwiki/bin/view/Main/WebHome) to assure global transactions in J2SE environments.

Tomcat (7+) can also work along with application-level JTA transaction manager implementations to reflect the transaction management in the container using the TransactionSynchronizationRegistry and JNDI datasources.

https://codepitbull.wordpress.com/2011/07/08/tomcat-7-with-full-jta

See also

Performance and useful metadata benefits can be obtained from these three headers that are not detailed in the recipe.

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

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