© 2008-2017 The original authors.

Note
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.

Preface

Project metadata

1. Introduction

REST web services have become the number one means for application integration on the web. In its core, REST defines that a system consists of resources that clients interact with. These resources are often also implemented in a hypermedia driven way. Spring MVC offers a solid foundation to build theses kinds of services. But implementing even the simplest REST web services for a multi-domain object system can be quite tedious and result in a lot of boilerplate code.

Spring Content REST builds on top of Spring Content stores and automatically exports those as REST resources. It leverages REST to expose end-points for each content resource and it also optionally integrates with Spring Data REST’s hypermedia API to allow clients to find content resources that have been associated with Spring Data entities.

Spring Content REST officially supports:

2. Getting Started

2.1. Introduction

Spring Content REST is itself a Spring MVC application and is designed in such a way that it should integrate with your existing Spring MVC applications with very little effort.

2.2. Adding Spring Content REST to a Spring Boot project

The simplest way to get to started is if you are building a Spring Boot application. That’s because Spring Content REST has both a starter as well as auto-configuration.

Example 1. Spring Boot configuration with Gradle
dependencies {
    ...
    compile("com.github.paulcwarren:spring-content-rest-boot-starter:${version}")
	...
}
Example 2. Spring Boot configuration with Maven
 <dependencies>
	...
    <dependency>
      <groupId>com.github.paulcwarren</groupId>
      <artifactId>spring-content-rest-boot-starter</artifactId>
      <version>${version}</version>
    </dependency>
	...
  </dependencies>

When using Spring Boot, Spring Content REST gets configured automatically.

2.3. Adding Spring Content REST to a Gradle Project

To add Spring Content REST to a Gradle-based project, add the spring-content-rest artifact to your compile-time dependencies:

dependencies {
    ...
    compile("com.github.paulcwarren:spring-content-rest:${version}")
	...
}

2.4. Adding Spring Content REST to a Maven Project

To add Spring Content REST to a Maven-based project, add the spring-content-rest artifact to your compile-time dependencies:

 <dependencies>
	...
    <dependency>
      <groupId>com.github.paulcwarren</groupId>
      <artifactId>spring-content-rest</artifactId>
      <version>${version}</version>
    </dependency>
	...
  </dependencies>

2.5. Configuring Spring Content REST

To install Spring Content REST alongside your existing Spring MVC application, you need to include the appropriate MVC configuration. Spring Content REST configuration is defined in two classes called; RestConfiguration and HypermediaConfiguration and they can just be imported into your applications configuration.

Important
This step is unnecessary if you are using Spring Boot’s auto-configuration. Spring Boot will automatically enable Spring Content REST when you include com.github.paulcwarren:spring-content-rest-boot-starter and your app is flagged as a @SpringBootApplication.

Make sure you also configure Spring Content stores for the store you wish to use. For details on that, please consult the reference documentation for the corresponding Spring Content module.

3. Store Resources

3.1. Fundamentals

The core functionality of Spring Content REST, enabled through @Import(RestConfiguration.class), is to export resources for Spring Content stores. This is often closely related to Spring Data repositories.

The following describes typical store scenarios and how they are exported with Spring Content REST.

3.1.1. Store Resources

Spring Content Store manages Spring Resources that, when exported using Spring Content REST, are accessible by REST endpoint.

Consider the following Store interface:

  public interface DvdStore extends Store<String> {}

In this example, the Store’s Resources are exported to the URI /dvds. The path is derived from the uncapitalized, pluralized, simple class name of the interface. When interacting with this endpoint any additional path is deemed to be the Resource’s location and will be used to fetch the Resource using the Store’s getResource method. For example, a GET request to /dvds/comedy/monty_pythons_flying_circus.mp4 will fetch from the DvdStore (/dvds), the Resource /comedy/monty_pythons_flying_circus.mp4.

3.1.2. AssociativeStore/ContentStore Resources

AssociativeStore and ContentStore both manage and associate Spring Resources with JPA Entities. When exported using Spring Content REST these can also be accessed via REST endpoints.

Assume the following Entity class, Repository and Store interfaces:

  @Entity
  @Data
  public class Dvd {
  	@Id
  	private Long id;

    @ContentId
    private UUID contentId;

  	@ContentLength
  	private Long contentLength;

  	@MimeType
  	private String contentType;

    @OiginalFileName
    private String contentName;
  }

  public interface DvdRepository extends CrudRepository<Dvd, Long> {}

  public interface DvdStore extends ContentStore<Dvd, UUID> {}

In this example a single Spring Resource (the DVD’s video stream) is associated with a Dvd Entity by annotating additional fields on the Entity using the @ContentId, @ContentLength and @MimeType annotations.

In this example Spring Data REST exports a collection resource to /dvds. The path is derived from the uncapitalized, pluralized, simple class name of the domain class. Item resources are also exported to the URI /dvds {id}. The HTTP methods used to request this endpoint map onto the methods of CrudRepository.

Similarly, Spring Content REST also exports any associated Spring Resources to the URI /dvds/{id}/{contentPropertyPath}. In this case /dvds/{id}/content because the Spring Content metadata is correlated using the prefix content.

For an AssociativeStore the HTTP methods are mapped as follows:- - GET → getResource → getInputStream - POST/PUT → getResource → getOutputStream, associate - DELETE → unassociate

For a ContentStore the HTTP methods are mapped as follows:- - GET → getContent - POST/PUT → setContent - DELETE → unsetContent

Additional Annotations

Spring Content REST adds two annotations to the core set of annotations provided by Spring Content Commons for capturing, on the entity, information that is available from the HTTP requests made by clients.

  • @MimeType; captures the Content-Type header of POST/PUT requests and is re-used on subsequent GET request responses

  • @OriginalFileName; captures the filename sent by POST/PUT requests, if available, and is re-used to set the content dispostion attachment filename on subsequent GET requests

3.1.3. Multiple Content Properties

By correlating (i.e. using a common prefix for) the field names of the Spring Content annotations it is possible to associate multiple Spring Resources with a single Entity:-

@Entity
@Data
public class Dvd {
	private @Id @GeneratedValue Long id;
	private String title;

	// Content property 'video' storing the DVD video content
	private @ContentId UUID videoId;
	private @ContentLength Long videoLen;
	private @MimeType String videoType;

	// Content property 'image' storing the Dvd's image
	private @ContentId UUID imageId;
	private @ContentLength Long imageLen;
	private @MimeType String imageType;

	...
}

Spring Content REST will export these two associated Spring Resources to the URI /dvds/{id}/video and /dvds/{id}/image.

These endpoints can be found in the similarly named link relations in the Entity’s application/hal+json response:

 "_links" : {
    ...
    "image" : {
      "href" : "http://localhost:8080/dvds/1/image"
    },
    "video" : {
      "href" : "http://localhost:8080/dvds/1/video"
    }
}

3.1.4. Nested Content Properties

With its @Embeddable/@Embedded annotations JPA along with some of the newer database technologies like Mongo it is possible to model Entities as complex objects. It may be appropriate to associate Spring Resources as nested properties on these complex objects, as in the following example:-

@Entity
@Data
public class Book {
	private @Id @GeneratedValue Long id;
	private String title;

	private @ContentId UUID coverId;
	private @ContentLength Long coverLen;
	private @MimeType String coverType;

	private @Embedded Chapter chapterOne = new Chapter();
	private @Embedded Chapter chapterTwo = new Chapter();
	...
}

@Embeddable
@Data
public class Chapter {

	// Content property 'video' storing the DVD video content
	private @ContentId UUID contentId;
	private @ContentLength Long contentLen;
	private @MimeType String contentType;
}

Spring Content REST will export these associated Spring Resources under the URIs; /books/{id}/cover, /books/{id}/chapterOne and /books/{id}/chapterTwo and so on.

These endpoints can be found in the similarly named link relations in the Entity’s application/hal+json response:

  "_links" : {
    ...
    "cover" : {
      "href" : "http://localhost:8080/dvds/1/cover"
    },
    "chapterTwo" : {
      "href" : "http://localhost:8080/dvds/1/chapterTwo"
    },
    "chapterOne" : {
      "href" : "http://localhost:8080/dvds/1/chapterOne"
    }

3.1.5. Nested Multiple Content Properties

With its @Embeddable/@Embedded annotations JPA along with some of the newer database technologies like Mongo it is possible to model Entities as complex objects. It may be appropriate to associate Spring Resources as nested properties on these complex objects, as in the following example:-

@Entity
@Data
public class Book {
    private @Id @GeneratedValue Long id;
    private String title;

    private @ContentId UUID coverId;
    private @ContentLength Long coverLen;
    private @MimeType String coverType;

    private @Embedded Chapter chapterOne = new Chapter();
    private @Embedded Chapter chapterTwo = new Chapter();
    ...
}

@Embeddable
@Data
public class Chapter {

    // Content property 'video' storing the DVD video content
    private @ContentId UUID contentId;
    private @ContentLength Long contentLen;
    private @MimeType String contentType;

    private @ContentId UUID epigraphId;
    private @ContentLength Long epigraphLen;
    private @MimeType String epigraphType;
}

With nested multiple content properties Spring Content REST will export these associated Spring Resources under the URIs; /books/{id}/cover, /books/{id}/chapterOne/content, /books/{id}/chapterOne/epigraph, /books/{id}/chapterTwo/content and /books/{id}/chapterTwo/epigraph.

These endpoints can be found in the following link relations:

  "_links" : {
    ...
    "cover" : {
      "href" : "http://localhost:8080/dvds/1/cover"
    },
    "chapterOne/content" : {
      "href" : "http://localhost:8080/dvds/1/chapterOne/content"
    }
    "chapterOne/epigraph" : {
      "href" : "http://localhost:8080/dvds/1/chapterOne/epigraph"
    },
    "chapterTwo/content" : {
      "href" : "http://localhost:8080/dvds/1/chapterTwo/content"
    },
    "chapterTwo/epigraph" : {
      "href" : "http://localhost:8080/dvds/1/chapterTwo/epigraph"
    },

Exported content stores may be marked as Searchable. Assuming the following content store interface:

  public interface DvdStore extends ContentStore<Dvd, UUID>, Searchable<UUID> {}

When the store is exported, Spring Content REST exposes a fulltext query resource for the Searchable.search methods. These resources are exported to the URI /dvds/searchContent. Method parameters can be supplied as query parameters:

  curl -H 'Accept: application/hal+json'  http://localhost:8080/searchContent?queryString=foo

3.1.7. Default status codes

For the content resources exposed, we use a set of default status codes:

  • 200 OK - for plain GET requests and POST and PUT requests that overwrite existing content resources

  • 201 Created - for POST and PUT requests that create new content resources

  • 204 No Content - for DELETE requests

  • 206 Partial Content - for range GET requests

3.1.8. Resource Discoverability

A core principle of HATEOAS is that Resources should be discoverable through the publication of links that point to the available resources. There are a few competing de-facto standards specifying how to represent links in JSON. By default, Spring Data REST uses HAL to render responses. HAL defines links to be contained in a property of the returned document.

Resource discovery starts at the top level of the application. By issuing a request to the root URL under which the Spring Data REST application is deployed, the client can extract a set of links from the returned JSON object that represent the next level of resources that are available to the client.

When enabled through @Import(HypermediaConfiguration.class) Spring Content REST will inject Store, Entity and Property Resources links for both into the HAL responses created by Spring Data REST.

3.2. The Store Resource

Spring Content REST exports Store Resources to /{store}/**. The resource path and linkrel can be customized using the @StoreRestResource annotation on the Store interface.

3.2.1. Supported HTTP Methods

Store Resources support GET, PUT, POST, and DELETE. All other HTTP methods will cause a 405 Method Not Allowed.

GET

Returns the Resource’s content

Supported media types

All content types except application/json

PUT/POST

Sets the Resources’s content

Supported media types

All content types except application/json

DELETE

Removes the Resource’s content

Supported media types

All content types except application/json

3.3. The Entity Resource

Requests to Store Resource exist (partially) in the same URL space as Spring Data’s Entity Resource. When a single piece of content is associated with an entity then "shortcut" requests to /{store}/{id} will return content (or 404 if no content is set) instead of the entity’s json.

This behavior can be customized by preventing the Store Resource from responding to shortcut requests with certain media types using exclusions, or by completely disabling the Store Resource from responding to all shortcut requests, forcing full qualified requests only. Note, this is likely to become the default in future versions of Spring Content REST.

With Spring Boot 1.2 and later, you can customize the exclusions or disable shortcut requests entirely by setting either of the following properties in application.properties:

spring.content.rest.shortcut-request-mappings.excludes=<VERB>=<MEDIA_TYPE>[[,<MEDIA_TYPE>]:<VERB>=<MEDIA_TYPE>[,<MEDIA_TYPE>]]

spring.content.rest.shortcut-request-mappings.disabled=true|false

where:

  • VERB is GET, PUT, POST or DELETE

  • MEDIA_TYPE is any valid media type including */*

Or if you’re not using Spring Boot, you can customize as follows:

@Configuration
class CustomContentRestMvcConfiguration {

  @Bean
  public ContentRestConfigurer contentRestConfigurer() {

    return new ContentRestConfigurer() {

      @Override
      public void configure(RestConfiguration config) {
        config.shortcutExclusions().exclude("GET", "*/*");

        // or
        // config.setShortcutLinks(false)
      }
    };
  }
}

3.4. The Content Property Resource

Spring Content REST exports Property Resources to /{store}/{id}/{contentPropertyPath}. The resource path and link relation prefix can be customized using the @StoreRestResource annotation on the Store interface.

3.4.1. Supported HTTP Methods

Property Resources support GET, PUT, POST, and DELETE. All other HTTP methods will cause a 405 Method Not Allowed.

GET

Returns the Resource’s content

Supported media types

All content types except application/json

PUT/POST

Sets the Resources’s content

Supported media types

All content types except application/json

DELETE

Removes the Resource’s content

4. Repository Resources (Spring Data REST Extensions)

4.1.1. The SearchContent Resource

When a Store extending Searchable is exported, a searchContent endpoint will be available at the /{store}/searchContent URI.

  curl -H 'Accept: application/hal+json'  http://localhost:8080/searchContent?queryString=foo
Supported HTTP Methods

As the SearchContent resource is read-only it supports GET only. All other HTTP methods will cause a 405 Method Not Allowed.

Supported media types
  • application/hal+json

  • application/json.

Format of the response payload

This resource can return entities, or a custom search result type, depending on how the Searchable interface is specified and the type of Store it decorates.

When Searchable decorates an AssociativeStore this resource will lookup and return representations of the content’s associated entities. These lookups can be made more efficient by specifying a @FulltextEntityLookupQuery query method. This is a custom findAll method that accepts a single Collection parameter annotated with the name contentIds, as follows:

    public interface MyRepository extends CrudRepository<MyEntity, Long> {

        @FulltextEntityLookupQuery
        List<MyEntity> findAllByContentIdIn(@Param("contentIds") List<UUID> contentIds);
    }

    public interface MyStore extends AssociativeStore<MyEntity, UUID>, Searchable<UUID> {}

When Searchable is typed to your own search return type the resource will return a representation of this type instead. See Search Return Types in the respective documentation for your chosen Spring Content fulltext module; solr or elasticsearch, for more information on specifying a custom search return types.

4.2. Locking and Versioning

4.2.1. The Lock Resource

When a repository extending LockingAndVersioningRepository is exported a lock endpoint will be available at the /{repository}/{id}/lock URI.

  curl -X PUT http://localhost:8080/docs/1234/lock
  curl -X DELETE http://localhost:8080/docs/1234/lock
Supported HTTP Methods

The Lock resource supports PUT and DELETE. All other HTTP methods will cause a 405 Method Not Allowed.

PUT

Acquires a pessimistic lock on the resource.

DELETE

If held, releases the pessimistic lock on the resource.

Supported media types

Not applicable.

4.2.2. The Version Resource

When a repository extending LockingAndVersioningRepository is exported a version endpoint will be available at the /{repository}/{id}/version URI.

  curl -X PUT http://localhost:8080/docs/1234/version -d '{"number": "1.1", "label": "a minor change"}'
Supported HTTP Methods

The version resource supports PUT. All other HTTP methods will cause a 405 Method Not Allowed.

PUT

Creates a new version of the entity.

Supported media types

application/json

4.2.3. The FindAllVersionsLatest Resource

When a repository extending LockingAndVersioningRepository is exported a findAllVersionsLatest endpoint will be available at the /{repository}/findAllVersionsLatest URI.

  curl -X GET http://localhost:8080/docs/findAllVersionsLatest
Supported HTTP Methods

The version resource supports GET. All other HTTP methods will cause a 405 Method Not Allowed.

GET

Returns the latest version of all entities.

Supported media types

Not applicable.

4.2.4. The FindAllVersions Resource

When a repository extending LockingAndVersioningRepository is exported a findAllLatestVersions endpoint will be available at the /{repository}/{id}/findAllVersions URI.

  curl -X GET http://localhost:8080/docs/1234/findAllVersions
Supported HTTP Methods

The version resource supports GET. All other HTTP methods will cause a 405 Method Not Allowed.

GET

Returns all versions of the given entity.

Supported media types

Not applicable.

5. Customizing Store Resources

5.1. Configuring CORS

For security reasons, browsers prohibit AJAX calls to resources residing outside the current origin. When working with client-side HTTP requests issued by a browser you may want to enable specific HTTP resources to be accessible.

Spring Data Content supports Cross-Origin Resource Sharing (CORS) through Spring’s CORS support.

5.1.1. Store Interface CORS Configuration

You can add a @CrossOrigin annotation to your store interfaces to enable CORS for the whole store. By default @CrossOrigin allows all origins and HTTP methods:

@CrossOrigin
interface DvdStore extends ContentStore<Dvd, UUID> {}

In the above example CORS support is enabled for the whole DvdStore. @CrossOrigin also provides attributes to perform more granular configuration.

@CrossOrigin(origins = "http://mydomain.com",
  methods = { RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE },
  maxAge = 3600)
interface DvdStore extends ContentStore<Dvd, UUID> {}

This example enables CORS support for the whole DvdStore providing one origin, restricted to GET, POST and DELETE methods with a max age of 3600 seconds.

5.1.2. Global CORS Configuration

In addition to fine-grained, annotation-based configuration, you probably want to define some global CORS configuration as well. This is similar to Spring Web MVC’S CORS configuration but can be declared within Spring Content REST and combined with fine-grained @CrossOrigin configuration. By default, all origins and GET, HEAD, and POST methods are allowed.

The following example sets up the same CORS configuration but as a global configuration:

@Configuration
public class SpringContentRestCustomization {

	@Bean
	private ContentRestConfigurer contentRestConfigurer() {

		return new ContentRestConfigurer() {
			@Override
			public void configure(RestConfiguration config) {
				config.getCorsRegistry().addMapping("/dvds/**")
						.allowedMethods("GET", "POST", "DELETE")
						.allowedOrigins("http://mydomain.com")
						.maxAge(3600);
			}
		};
	}
}

5.2. Changing the Base URI

By default, Spring Content REST serves up REST resources at the root URI, '/'.

With Spring Boot 1.2 and later versions, you can change the base URI by setting a single property in application.properties, as follows:

spring.content.rest.baseUri=/api

Or if you are not using Spring Boot, you can do the following:

@Configuration
class CustomContentRestMvcConfiguration {

  @Bean
  public ContentRestConfigurer contentRestConfigurer() {

    return new ContentRestConfigurer() {

      @Override
      public void configure(RestConfiguration config) {
        config.setBaseUri(URI.create("/contentApi"));
      }
    };
  }
}

5.3. Cache Control

By using cache control headers effectively, we can instruct the browser to cache resources and avoid network hops. This decreases latency, and also the load on our applications.

Spring Content REST allows us to control these headers by configuring a set of cache control rules applied, in the order they are defined, to all GET requests for content.

One, or more, rules can be configured as follows:

@Configuration
class CustomContentRestMvcConfiguration {

   @Bean
   public ContentRestConfigurer configurer() {

       return new ContentRestConfigurer() {
           @Override
           public void configure(RestConfiguration config) {
               config
                   .cacheControl()
                       .antMatcher("/testEntities/*", CacheControl.maxAge(Duration.ofSeconds(60)));
           }
       };
   }
}

For each Spring Resource associated with an Entity, Spring Content REST will generate a fully-qualified link and inject it into the Entity’s Spring Data REST HAL response.

Given the following domain model, Repository and Store:

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

    @ContentId
    private UUID videoId;

    @ContentLength
    private Long videoLength;

    @MimeType
    private String videoMimeType;

    // getters and setters
  }

  public interface DvdRepository extends CrudRepository<Dvd, Long> {}

  public interface DvdStore extends ContentStore<Dvd, UUID> {}

Spring Content REST will inject the following link:

  "_links" : {
    "self" : {
      ...
    },
    "dvd" : {
      ...
    },
    "video" : {
      "href" : "http://localhost:8080/dvds/1/video"
    }
  }

Where the linkrel is named after the content property and the link URI is the fully-qualified URI of that content property.

Sometimes it can be useful to configure the linkrel for a content property. This is done by specifying the linkRel attribute on the StoreRestResource, as follows:

  @StoreRestResource(linkRel="custom-linkrel")
  public interface DvdStore extends ContentStore<Dvd, UUID> {}

This will result in the following linkrel instead:

 "_links" : {
    "self" : {
      ...
    },
    "dvd" : {
      ...
    },
    "custom-linkrel/video" : {
      "href" : "http://localhost:8080/dvds/1/video"
    }
  }

5.5. Store Resolver

Every REST request must map to one store and one store only. However, it is entirely possible to define an application with multiple stores that make this impossible.

For these situations you need to provide a StoreResolver that, based on runtime context, can resolve these conflicts.

Example 3. Configuring a StoreResolver
public interface S3ExampleStore extends S3ContentStore<Example, UUID>{}
public interface FSExampleStore extends FilesystemContentStore<Example, UUID>{}     (1)

@Configuration
@EnableS3Stores
@EnableFilesystemStores
public static class ApplicationConfig {

    @Bean
    public ContentRestConfigurer contentRestConfigurer() {
        return new ContentRestConfigurer() {
            @Override
            public void configure(RestConfiguration config) {
                config.addStoreResolver("examples", new StoreResolver() {           (2)
                    @Override
                    public StoreInfo resolve(StoreInfo... stores) {
                        /* your resolver implementation */
                    }
                });
            }
        };
    }
}
  1. Both stores are typed to the domain type Example and will therefore be exported to the URI /examples

  2. Store resolver resolves requests to /examples/…​ to either S3ExamplesStore or FSExampleStore, depending on request context.

5.6. Put/Post Content Handling

A ContentStore provides two methods for setting content. Spring Content REST prefers to use setContent(S entity, InputStream) over setContent(S entity, Resource) so that content is streamed through the application. This is usually the best approach.

However, under some circumstances this can be inconvenient when event handlers and store customizations must handle the content as it moves through the application. This also becomes inefficient when the content is large and the content needs to be processed several times.

For these situations, it is possible to configure Spring Content REST to call setContent(S entity, Resource) as follows:

Example 4. Configuring PUT/POST Handling
public interface ExampleStore extends ContentStore<Example, UUID>{}

@Configuration
public static class ApplicationConfig {

   @Bean
   public ContentRestConfigurer configurer() {
       return new ContentRestConfigurer() {
           @Override
           public void configure(RestConfiguration config) {
               config.forDomainType(Example.class).putAndPostPreferResource();
           }
       };
   }
}
Note
if preferred, it is also possible to configure your ContentStore to hide the setContent(S entity, InputStream) method by annotating this method with the RestResource(export=false) annotation.

When configured this way, Spring Content REST will create a file backed Resource allowing event handlers and store customizations to use File, RandomAccessFile and Channel Java io/nio APIs to process the content stream.

If you would like to defer until runtime so that a decision can be based on the request, you can instead provide a Resource<Method, HttpHeaders>, as follows:

Example 5. Deferring PUT/POST Handling until runtime
public interface ExampleStore extends ContentStore<Example, UUID>{}

@Configuration
public static class ApplicationConfig {

   @Bean
   public ContentRestConfigurer configurer() {
       return new ContentRestConfigurer() {
           @Override
           public void configure(RestConfiguration config) {

               config.forDomainType(TestEntity.class).setSetContentResolver(new Resolver<Method, HttpHeaders>(){

                @Override
                public boolean resolve(Method method, HttpHeaders context) {

                    return // your logic here
               }});
           }
       };
   }
}