Skip to content

Commit 247c9a6

Browse files
committed
Add support for JSON Collection media type
1 parent f1fd16c commit 247c9a6

28 files changed

+1771
-28
lines changed

pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@
3131
</roles>
3232
<timezone>+1</timezone>
3333
</developer>
34+
<developer>
35+
<id>gturnquist</id>
36+
<name>Greg Turnquist</name>
37+
<email>gturnquist(at)pivotal.io</email>
38+
<organization>Pivotal, Inc.</organization>
39+
<roles>
40+
<role>Contributor</role>
41+
</roles>
42+
<timezone>-6</timezone>
43+
</developer>
3444
</developers>
3545

3646
<licenses>

src/main/java/org/springframework/hateoas/MediaTypes.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
*
2323
* @author Oliver Gierke
2424
* @author Przemek Nowak
25+
* @author Greg Turnquist
2526
*/
2627
public class MediaTypes {
2728

@@ -34,4 +35,14 @@ public class MediaTypes {
3435
* Public constant media type for {@code application/hal+json}.
3536
*/
3637
public static final MediaType HAL_JSON = MediaType.valueOf(HAL_JSON_VALUE);
38+
39+
/**
40+
* A String equivalent of {@link MediaTypes#JSON_COLLECTION}.
41+
*/
42+
public static final String JSON_COLLECTION_VALUE = "application/vnd.collection+json";
43+
44+
/**
45+
* Public constant media type for {@code application/vnd.collection+json}.
46+
*/
47+
public static final MediaType JSON_COLLECTION = MediaType.valueOf(JSON_COLLECTION_VALUE);
3748
}

src/main/java/org/springframework/hateoas/Resource.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* A simple {@link Resource} wrapping a domain object and adding links to it.
3030
*
3131
* @author Oliver Gierke
32+
* @author Greg Turnquist
3233
*/
3334
@XmlRootElement
3435
public class Resource<T> extends ResourceSupport {
@@ -38,7 +39,7 @@ public class Resource<T> extends ResourceSupport {
3839
/**
3940
* Creates an empty {@link Resource}.
4041
*/
41-
Resource() {
42+
protected Resource() {
4243
this.content = null;
4344
}
4445

src/main/java/org/springframework/hateoas/alps/Alps.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
*/
1616
package org.springframework.hateoas.alps;
1717

18+
import java.util.List;
19+
1820
import lombok.Builder;
1921
import lombok.Value;
2022

21-
import java.util.List;
22-
2323
import org.springframework.hateoas.alps.Descriptor.DescriptorBuilder;
2424
import org.springframework.hateoas.alps.Doc.DocBuilder;
2525
import org.springframework.hateoas.alps.Ext.ExtBuilder;

src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
* @see LinkDiscoverer
4040
* @see EntityLinks
4141
* @author Oliver Gierke
42+
* @author Greg Turnquist
4243
*/
4344
@Retention(RetentionPolicy.RUNTIME)
4445
@Target(ElementType.TYPE)
@@ -57,6 +58,7 @@
5758
* Hypermedia representation types supported.
5859
*
5960
* @author Oliver Gierke
61+
* @author Greg Turnquist
6062
*/
6163
static enum HypermediaType {
6264

@@ -66,6 +68,13 @@ static enum HypermediaType {
6668
* @see http://stateless.co/hal_specification.html
6769
* @see http://tools.ietf.org/html/draft-kelly-json-hal-05
6870
*/
69-
HAL;
71+
HAL,
72+
73+
/**
74+
* JSON Collection
75+
*
76+
* @see http://amundsen.com/media-types/collection/format/
77+
*/
78+
JSON_COLLECTION;
7079
}
7180
}

src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
import org.springframework.hateoas.hal.CurieProvider;
5656
import org.springframework.hateoas.hal.HalLinkDiscoverer;
5757
import org.springframework.hateoas.hal.Jackson2HalModule;
58+
import org.springframework.hateoas.jsoncollection.Jackson2JsonCollectionModule;
59+
import org.springframework.hateoas.jsoncollection.JsonCollectionLinkDiscoverer;
5860
import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
5961
import org.springframework.http.converter.HttpMessageConverter;
6062
import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean;
@@ -75,12 +77,14 @@
7577
* activated as well).
7678
*
7779
* @author Oliver Gierke
80+
* @author Greg Turnquist
7881
*/
7982
class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
8083

8184
private static final String DELEGATING_REL_PROVIDER_BEAN_NAME = "_relProvider";
8285
private static final String LINK_DISCOVERER_REGISTRY_BEAN_NAME = "_linkDiscovererRegistry";
8386
private static final String HAL_OBJECT_MAPPER_BEAN_NAME = "_halObjectMapper";
87+
private static final String JSON_COLLECTION_OBJECT_MAPPER_BEAN_NAME = "_jsonCollectionObjectMapper";
8488
private static final String MESSAGE_SOURCE_BEAN_NAME = "linkRelationMessageSource";
8589

8690
private static final boolean JACKSON2_PRESENT = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
@@ -127,6 +131,21 @@ public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionR
127131
}
128132
}
129133

134+
if (types.contains(HypermediaType.JSON_COLLECTION)) {
135+
136+
if (JACKSON2_PRESENT) {
137+
138+
BeanDefinitionBuilder jsonCollectionQueryMapperBuilder = rootBeanDefinition(ObjectMapper.class);
139+
registerSourcedBeanDefinition(jsonCollectionQueryMapperBuilder, metadata, registry, JSON_COLLECTION_OBJECT_MAPPER_BEAN_NAME);
140+
141+
BeanDefinitionBuilder customizerBeanDefinition = rootBeanDefinition(DefaultObjectMapperCustomizer.class);
142+
registerSourcedBeanDefinition(customizerBeanDefinition, metadata, registry);
143+
144+
BeanDefinitionBuilder builder = rootBeanDefinition(Jackson2ModuleRegisteringBeanPostProcessor.class);
145+
registerSourcedBeanDefinition(builder, metadata, registry);
146+
}
147+
}
148+
130149
if (!types.isEmpty()) {
131150

132151
BeanDefinitionBuilder linkDiscoverersRegistryBuilder = BeanDefinitionBuilder
@@ -188,6 +207,9 @@ private AbstractBeanDefinition getLinkDiscovererBeanDefinition(HypermediaType ty
188207
case HAL:
189208
definition = new RootBeanDefinition(HalLinkDiscoverer.class);
190209
break;
210+
case JSON_COLLECTION:
211+
definition = new RootBeanDefinition(JsonCollectionLinkDiscoverer.class);
212+
break;
191213
default:
192214
throw new IllegalStateException(String.format("Unsupported hypermedia type %s!", type));
193215
}
@@ -289,21 +311,43 @@ private List<HttpMessageConverter<?>> potentiallyRegisterModule(List<HttpMessage
289311

290312
CurieProvider curieProvider = getCurieProvider(beanFactory);
291313
RelProvider relProvider = beanFactory.getBean(DELEGATING_REL_PROVIDER_BEAN_NAME, RelProvider.class);
292-
ObjectMapper halObjectMapper = beanFactory.getBean(HAL_OBJECT_MAPPER_BEAN_NAME, ObjectMapper.class);
293-
MessageSourceAccessor linkRelationMessageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
294-
MessageSourceAccessor.class);
295314

296-
halObjectMapper.registerModule(new Jackson2HalModule());
297-
halObjectMapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider,
298-
linkRelationMessageSource, beanFactory));
315+
List<HttpMessageConverter<?>> result = new ArrayList<HttpMessageConverter<?>>(converters.size());
299316

300-
MappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(
301-
ResourceSupport.class);
302-
halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON));
303-
halConverter.setObjectMapper(halObjectMapper);
317+
if (beanFactory.containsBean(HAL_OBJECT_MAPPER_BEAN_NAME)) {
318+
319+
ObjectMapper halObjectMapper = beanFactory.getBean(HAL_OBJECT_MAPPER_BEAN_NAME, ObjectMapper.class);
320+
MessageSourceAccessor linkRelationMessageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
321+
MessageSourceAccessor.class);
322+
323+
halObjectMapper.registerModule(new Jackson2HalModule());
324+
halObjectMapper.setHandlerInstantiator(
325+
new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider, linkRelationMessageSource, beanFactory));
326+
327+
MappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(
328+
ResourceSupport.class);
329+
halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON));
330+
halConverter.setObjectMapper(halObjectMapper);
331+
result.add(halConverter);
332+
}
333+
334+
if (beanFactory.containsBean(JSON_COLLECTION_OBJECT_MAPPER_BEAN_NAME)) {
335+
336+
ObjectMapper jsonCollectionObjectMapper = beanFactory.getBean(JSON_COLLECTION_OBJECT_MAPPER_BEAN_NAME, ObjectMapper.class);
337+
MessageSourceAccessor linkRelationMessageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
338+
MessageSourceAccessor.class);
339+
340+
jsonCollectionObjectMapper.registerModule(new Jackson2JsonCollectionModule());
341+
jsonCollectionObjectMapper.setHandlerInstantiator(
342+
new Jackson2JsonCollectionModule.JsonCollectionHandlerInstantiator(linkRelationMessageSource));
343+
344+
MappingJackson2HttpMessageConverter jsonCollectionConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(
345+
ResourceSupport.class);
346+
jsonCollectionConverter.setSupportedMediaTypes(Arrays.asList(JSON_COLLECTION));
347+
jsonCollectionConverter.setObjectMapper(jsonCollectionObjectMapper);
348+
result.add(jsonCollectionConverter);
349+
}
304350

305-
List<HttpMessageConverter<?>> result = new ArrayList<HttpMessageConverter<?>>(converters.size());
306-
result.add(halConverter);
307351
result.addAll(converters);
308352
return result;
309353
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.hateoas.jsoncollection;
17+
18+
import java.util.List;
19+
20+
import lombok.Builder;
21+
import lombok.Data;
22+
import lombok.Singular;
23+
import lombok.Value;
24+
25+
import org.springframework.hateoas.Link;
26+
27+
import com.fasterxml.jackson.annotation.JsonCreator;
28+
import com.fasterxml.jackson.annotation.JsonProperty;
29+
30+
/**
31+
* Representation of an item in a JSON Collection.
32+
*
33+
* @author Greg Turnquist
34+
*/
35+
@Data
36+
@Value
37+
@Builder
38+
public class Item<T> {
39+
40+
private final String href;
41+
@Singular("data") private final List<T> data;
42+
private final List<Link> links;
43+
44+
@JsonCreator
45+
public Item(@JsonProperty("href") String href, @JsonProperty("data") List<T> data,
46+
@JsonProperty("links") List<Link> links) {
47+
48+
this.href = href;
49+
this.data = data;
50+
this.links = links;
51+
}
52+
53+
}

0 commit comments

Comments
 (0)