© 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
-
Version control - http://github.com/paulcwarren/spring-content/
-
Bugtracker - http://github.com/paulcwarren/spring-content/issues
-
Release repository - https://repo1.maven.org/maven2/
-
Snapshots repository - https://oss.sonatype.org/content/repositories/snapshots
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.
dependencies {
...
compile("com.github.paulcwarren:spring-content-rest-boot-starter:${version}")
...
}
<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 theContent-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"
},
3.1.6. Search
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
orDELETE
-
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. Search
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.
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.
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.
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.
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)));
}
};
}
}
5.4. Store Links
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.
5.4.1. Customizing the link relation
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.
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 */
}
});
}
};
}
}
-
Both stores are typed to the domain type
Example
and will therefore be exported to the URI/examples
-
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:
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:
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
}});
}
};
}
}