Skip to content

Commit 6a17e23

Browse files
committed
spring-projects#482 - Add support for Collection+JSON media type
Introduce support for media type application/vnd.collection+json. Collection+JSON doesn't allow metadata at the top, so paging data can't be covered, however, everything else fits.
1 parent 1e32bb5 commit 6a17e23

File tree

59 files changed

+3575
-105
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+3575
-105
lines changed

readme.adoc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
image:https://spring.io/badges/spring-hateoas/ga.svg[http://projects.spring.io/spring-hateoas/#quick-start]
2+
image:https://spring.io/badges/spring-hateoas/snapshot.svg[http://projects.spring.io/spring-hateoas/#quick-start]
3+
4+
= Spring HATEOAS
5+
6+
This project provides some APIs to ease creating REST representations that follow the http://en.wikipedia.org/wiki/HATEOAS[HATEOAS] principle when working with Spring and especially Spring MVC. The core problem it tries to address is link creation and representation assembly.
7+
8+
== Working with Spring HATEOAS
9+
10+
Since all commits are headlined with its github issue, git will treat it as a comment. To get around this, apply the following configuration to your clone:
11+
12+
[source]
13+
----
14+
git config core.commentchar "/"
15+
----
16+
17+
== Resources
18+
19+
* Reference documentation - http://docs.spring.io/spring-hateoas/docs/current/reference/html/[html], http://docs.spring.io/spring-hateoas/docs/current/reference/pdf/spring-hateoas-reference.pdf[pdf]
20+
* http://docs.spring.io/spring-hateoas/docs/current-SNAPSHOT/api/[JavaDoc]
21+
* https://spring.io/guides/gs/rest-hateoas/[Getting started guide]

readme.md

Lines changed: 0 additions & 11 deletions
This file was deleted.

src/main/asciidoc/index.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ Since the purpose of the `CurieProvider` API is to allow for automatic curie cre
363363
[[client.traverson]]
364364
=== Traverson
365365

366-
As of version 0.11 Spring HATEOAS provides an API for client side service traversal inspired by the https://blog.codecentric.de/en/2013/11/traverson/[Traverson JavaScript library].
366+
Spring HATEOAS provides an API for client side service traversal inspired by the https://blog.codecentric.de/en/2013/11/traverson/[Traverson JavaScript library].
367367

368368
[source, java]
369369
----

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
*/
5050
@XmlType(name = "link", namespace = Link.ATOM_NAMESPACE)
5151
@JsonInclude(JsonInclude.Include.NON_NULL)
52-
@JsonIgnoreProperties("templated")
52+
@JsonIgnoreProperties(value = "templated", ignoreUnknown = true)
5353
@AllArgsConstructor(access = AccessLevel.PACKAGE)
5454
@Getter
5555
@EqualsAndHashCode(of = { "rel", "href", "hreflang", "media", "title", "deprecation", "affordances" })

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,13 @@ public class MediaTypes {
6767
*/
6868
public static final MediaType HAL_FORMS_JSON = MediaType.parseMediaType(HAL_FORMS_JSON_VALUE);
6969

70+
/**
71+
* A String equivalent of {@link MediaTypes#COLLECTION_JSON}.
72+
*/
73+
public static final String COLLECTION_JSON_VALUE = "application/vnd.collection+json";
74+
75+
/**
76+
* Public constant media type for {@code application/vnd.collection+json}.
77+
*/
78+
public static final MediaType COLLECTION_JSON = MediaType.valueOf(COLLECTION_JSON_VALUE);
7079
}

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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.collectionjson;
17+
18+
import lombok.Data;
19+
import lombok.Value;
20+
import lombok.experimental.Wither;
21+
22+
import java.util.List;
23+
24+
import org.springframework.hateoas.Link;
25+
26+
import com.fasterxml.jackson.annotation.JsonCreator;
27+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
28+
import com.fasterxml.jackson.annotation.JsonInclude;
29+
import com.fasterxml.jackson.annotation.JsonInclude.Include;
30+
import com.fasterxml.jackson.annotation.JsonProperty;
31+
32+
/**
33+
* Representation of the "collection" part of a Collection+JSON document.
34+
*
35+
* @author Greg Turnquist
36+
*/
37+
@Data
38+
@Value
39+
@Wither
40+
@JsonIgnoreProperties(ignoreUnknown = true)
41+
class CollectionJson<T> {
42+
43+
private String version;
44+
private String href;
45+
private List<Link> links;
46+
private List<CollectionJsonItem<T>> items;
47+
48+
@JsonInclude(Include.NON_NULL)
49+
private CollectionJsonTemplate template;
50+
51+
@JsonCreator
52+
CollectionJson(@JsonProperty("version") String version, @JsonProperty("href") String href,
53+
@JsonProperty("links") List<Link> links, @JsonProperty("items") List<CollectionJsonItem<T>> items,
54+
@JsonProperty("template") CollectionJsonTemplate template) {
55+
this.version = version;
56+
this.href = href;
57+
this.links = links;
58+
this.items = items;
59+
this.template = template;
60+
}
61+
62+
CollectionJson() {
63+
this("1.0", null, null, null, null);
64+
}
65+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2018 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.collectionjson;
17+
18+
import lombok.extern.slf4j.Slf4j;
19+
20+
import java.lang.reflect.Method;
21+
import java.util.Arrays;
22+
import java.util.Collection;
23+
import java.util.Collections;
24+
import java.util.List;
25+
import java.util.stream.Collectors;
26+
27+
import org.springframework.core.ResolvableType;
28+
import org.springframework.hateoas.Affordance;
29+
import org.springframework.hateoas.AffordanceModel;
30+
import org.springframework.hateoas.MediaTypes;
31+
import org.springframework.hateoas.core.DummyInvocationUtils.MethodInvocation;
32+
import org.springframework.hateoas.core.MethodParameters;
33+
import org.springframework.hateoas.support.PropertyUtils;
34+
import org.springframework.http.HttpMethod;
35+
import org.springframework.http.MediaType;
36+
import org.springframework.web.bind.annotation.RequestBody;
37+
import org.springframework.web.util.UriComponents;
38+
39+
/**
40+
* @author Greg Turnquist
41+
*/
42+
@Slf4j
43+
class CollectionJsonAffordanceModel implements AffordanceModel {
44+
45+
private static final List<HttpMethod> METHODS_FOR_INPUT_DETECTTION = Arrays.asList(HttpMethod.POST, HttpMethod.PUT,
46+
HttpMethod.PATCH);
47+
48+
private final Affordance affordance;
49+
private final MethodInvocation invocationValue;
50+
private final UriComponents components;
51+
private final List<String> properties;
52+
53+
CollectionJsonAffordanceModel(Affordance affordance, MethodInvocation invocationValue, UriComponents components) {
54+
55+
this.affordance = affordance;
56+
this.invocationValue = invocationValue;
57+
this.components = components;
58+
59+
this.properties = METHODS_FOR_INPUT_DETECTTION.contains(affordance.getHttpMethod()) //
60+
? determineAffordanceInputs(invocationValue.getMethod()) //
61+
: Collections.emptyList();
62+
}
63+
64+
public List<CollectionJsonData> getProperties() {
65+
66+
return this.properties.stream()
67+
.map(property -> new CollectionJsonData().withName(property).withValue(""))
68+
.collect(Collectors.toList());
69+
}
70+
71+
@Override
72+
public Collection<MediaType> getMediaTypes() {
73+
return Collections.singleton(MediaTypes.COLLECTION_JSON);
74+
}
75+
76+
private List<String> determineAffordanceInputs(Method method) {
77+
78+
if (method == null) {
79+
return Collections.emptyList();
80+
}
81+
82+
LOG.debug("Gathering details about " + method.getDeclaringClass().getCanonicalName() + "." + method.getName());
83+
84+
MethodParameters parameters = new MethodParameters(method);
85+
86+
return parameters.getParametersWith(RequestBody.class).stream()
87+
.findAny()
88+
.map(methodParameter -> {
89+
ResolvableType resolvableType = ResolvableType.forMethodParameter(methodParameter.getMethod(), methodParameter.getParameterIndex());
90+
return PropertyUtils.findProperties(resolvableType);
91+
})
92+
.orElse(Collections.emptyList());
93+
}
94+
95+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2018 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.collectionjson;
17+
18+
import org.springframework.hateoas.Affordance;
19+
import org.springframework.hateoas.AffordanceModel;
20+
import org.springframework.hateoas.MediaTypes;
21+
import org.springframework.hateoas.core.AffordanceModelFactory;
22+
import org.springframework.hateoas.core.DummyInvocationUtils.MethodInvocation;
23+
import org.springframework.http.MediaType;
24+
import org.springframework.web.util.UriComponents;
25+
26+
/**
27+
* @author Greg Turnquist
28+
*/
29+
class CollectionJsonAffordanceModelFactory implements AffordanceModelFactory {
30+
31+
/**
32+
* Look up the {@link AffordanceModel} for this factory.
33+
*
34+
* @param affordance
35+
* @param invocationValue
36+
* @param components
37+
* @return
38+
*/
39+
@Override
40+
public AffordanceModel getAffordanceModel(Affordance affordance, MethodInvocation invocationValue, UriComponents components) {
41+
return new CollectionJsonAffordanceModel(affordance, invocationValue, components);
42+
}
43+
44+
/**
45+
* Returns if a plugin should be invoked according to the given delimiter.
46+
*
47+
* @param delimiter
48+
* @return if the plugin should be invoked
49+
*/
50+
@Override
51+
public boolean supports(MediaType delimiter) {
52+
return MediaTypes.COLLECTION_JSON.equals(delimiter);
53+
}
54+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2017 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.collectionjson;
17+
18+
import lombok.Data;
19+
import lombok.Value;
20+
import lombok.experimental.Wither;
21+
22+
import com.fasterxml.jackson.annotation.JsonCreator;
23+
import com.fasterxml.jackson.annotation.JsonInclude;
24+
import com.fasterxml.jackson.annotation.JsonInclude.Include;
25+
import com.fasterxml.jackson.annotation.JsonProperty;
26+
27+
/**
28+
* @author Greg Turnquist
29+
*/
30+
@Data
31+
@Value
32+
@Wither
33+
class CollectionJsonData {
34+
35+
@JsonInclude(Include.NON_NULL) //
36+
private String name;
37+
38+
@JsonInclude(Include.NON_NULL) //
39+
private Object value;
40+
41+
@JsonInclude(Include.NON_NULL) //
42+
private String prompt;
43+
44+
@JsonCreator
45+
CollectionJsonData(@JsonProperty("name") String name, @JsonProperty("value") Object value,
46+
@JsonProperty("prompt") String prompt) {
47+
48+
this.name = name;
49+
this.value = value;
50+
this.prompt = prompt;
51+
}
52+
53+
CollectionJsonData() {
54+
this(null, null, null);
55+
}
56+
}

0 commit comments

Comments
 (0)