Skip to content

#340 - Adds new Affordances API + HAL-Forms mediatype #612

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lombok.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
lombok.anyConstructor.suppressConstructorProperties=true
lombok.nonNull.exceptionType = IllegalArgumentException
lombok.log.fieldName = LOG
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<version>1.0.0.AFFORDANCES-SNAPSHOT</version>

<name>Spring HATEOAS</name>
<url>http://github.com/SpringSource/spring-hateoas</url>
Expand Down
2 changes: 1 addition & 1 deletion src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ A `RelProvider` is exposed as Spring bean when using `@EnableHypermediaSupport`
[[spis.curie-provider]]
=== CurieProvider API

The http://tools.ietf.org/html/rfc5988=section-4[Web Linking RFC] describes registered and extension link relation types. Registered rels are well-known strings registered with the http://www.iana.org/assignments/link-relations/link-relations.xhtml[IANA registry of link relation types]. Extension rels can be used by applications that do not wish to register a relation type. They are a URI that uniquely identifies the relation type. The rel URI can be serialized as a compact URI or http://www.w3.org/TR/curie[Curie]. E.g. a curie `ex:persons` stands for the link relation type `http://example.com/rels/persons` if `ex` is defined as `http://example.com/rels/{rels}`. If curies are used, the base URI must be present in the response scope.
The http://tools.ietf.org/html/rfc5988=section-4[Web Linking RFC] describes registered and extension link relation types. Registered rels are well-known strings registered with the http://www.iana.org/assignments/link-relations/link-relations.xhtml[IANA registry of link relation types]. Extension rels can be used by applications that do not wish to register a relation type. They are a URI that uniquely identifies the relation type. The rel URI can be serialized as a compact URI or http://www.w3.org/TR/curie[Curie]. E.g. a curie `ex:persons` stands for the link relation type `http://example.com/rels/persons` if `ex` is defined as `http://example.com/rels/{rel}`. If curies are used, the base URI must be present in the response scope.

The rels created by the default `RelProvider` are extension relation types and as such must be URIs, which can cause a lot of overhead. The `CurieProvider` API takes care of that: it allows to define a base URI as URI template and a prefix which stands for that base URI. If a `CurieProvider` is present, the `RelProvider` prepends all rels with the curie prefix. Furthermore a `curies` link is automatically added to the HAL resource.

Expand Down
50 changes: 50 additions & 0 deletions src/main/java/org/springframework/hateoas/Affordance.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.hateoas;

import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;

/**
* Abstract representation of an action a link is able to take. Web frameworks must provide concrete implementation.
*
* @author Greg Turnquist
* @author Oliver Gierke
*/
public interface Affordance {

/**
* HTTP method this affordance covers. For multiple methods, add multiple {@link Affordance}s.
*
* @return
*/
HttpMethod getHttpMethod();

/**
* Name for the REST action this {@link Affordance} can take.
*
* @return
*/
String getName();

/**
* Look up the {@link AffordanceModel} for the requested {@link MediaType}.
*
* @param mediaType
* @return
*/
<T extends AffordanceModel> T getAffordanceModel(MediaType mediaType);
}
37 changes: 37 additions & 0 deletions src/main/java/org/springframework/hateoas/AffordanceModel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.hateoas;

import java.util.Collection;

import org.springframework.http.MediaType;

/**
* An affordance model is a media type specific description of an affordance.
*
* @author Greg Turnquist
* @author Oliver Gierke
*/
public interface AffordanceModel {

/**
* The media types this is a model for. Can be multiple ones as often media types come in different flavors like an
* XML and JSON one and in simple cases a single model might serve them all.
*
* @return will never be {@literal null}.
*/
Collection<MediaType> getMediaTypes();
}
78 changes: 75 additions & 3 deletions src/main/java/org/springframework/hateoas/Link.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.Wither;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand All @@ -39,6 +39,7 @@

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;

/**
* Value object for links.
Expand All @@ -47,11 +48,11 @@
* @author Greg Turnquist
*/
@XmlType(name = "link", namespace = Link.ATOM_NAMESPACE)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties("templated")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@Getter
@EqualsAndHashCode(of = { "rel", "href", "hreflang", "media", "title", "deprecation" })
@EqualsAndHashCode(of = { "rel", "href", "hreflang", "media", "title", "deprecation", "affordances" })
public class Link implements Serializable {

private static final long serialVersionUID = -9037755944661782121L;
Expand All @@ -73,6 +74,7 @@ public class Link implements Serializable {
private @XmlAttribute @Wither String type;
private @XmlAttribute @Wither String deprecation;
private @XmlTransient @JsonIgnore UriTemplate template;
private @XmlTransient @JsonIgnore List<Affordance> affordances;

/**
* Creates a new link to the given URI with the self rel.
Expand Down Expand Up @@ -108,6 +110,32 @@ public Link(UriTemplate template, String rel) {
this.template = template;
this.href = template.toString();
this.rel = rel;
this.affordances = new ArrayList<Affordance>();
}

public Link(String href, String rel, List<Affordance> affordances) {

this(href, rel);

Assert.notNull(affordances, "affordances must not be null!");

this.affordances = affordances;
}

/**
* Empty constructor required by the marshalling framework.
*/
protected Link() {
this.affordances = new ArrayList<Affordance>();
}

/**
* Returns safe copy of {@link Affordance}s.
*
* @return
*/
public List<Affordance> getAffordances() {
return Collections.unmodifiableList(this.affordances);
}

/**
Expand All @@ -119,6 +147,50 @@ public Link withSelfRel() {
return withRel(Link.REL_SELF);
}

/**
* Create new {@link Link} with an additional {@link Affordance}.
*
* @param affordance must not be {@literal null}.
* @return
*/
public Link andAffordance(Affordance affordance) {

Assert.notNull(affordance, "Affordance must not be null!");

List<Affordance> newAffordances = new ArrayList<Affordance>();
newAffordances.addAll(this.affordances);
newAffordances.add(affordance);

return withAffordances(newAffordances);
}

/**
* Create new {@link Link} with additional {@link Affordance}s.
*
* @param affordances must not be {@literal null}.
* @return
*/
public Link andAffordances(List<Affordance> affordances) {

List<Affordance> newAffordances = new ArrayList<Affordance>();
newAffordances.addAll(this.affordances);
newAffordances.addAll(affordances);

return withAffordances(newAffordances);
}

/**
* Creats a new {@link Link} with the given {@link Affordance}s.
*
* @param affordances must not be {@literal null}.
* @return
*/
public Link withAffordances(List<Affordance> affordances) {

return new Link(this.rel, this.href, this.hreflang, this.media, this.title, this.type, this.deprecation,
this.template, affordances);
}

/**
* Returns the variable names contained in the template.
*
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/springframework/hateoas/MediaTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
*
* @author Oliver Gierke
* @author Przemek Nowak
<<<<<<< HEAD
* @author Drummond Dawson
=======
>>>>>>> f5bf966... #340 - Add new Affordances API + HAL-FORMS mediatype.
* @author Greg Turnquist
*/
public class MediaTypes {
Expand Down Expand Up @@ -56,4 +59,15 @@ public class MediaTypes {
* Public constant media type for {@code application/alps+json}.
*/
public static final MediaType ALPS_JSON = MediaType.parseMediaType(ALPS_JSON_VALUE);

/**
* Public constant media type for {@code application/prs.hal-forms+json}.
*/
public static final String HAL_FORMS_JSON_VALUE = "application/prs.hal-forms+json";

/**
* Public constant media type for {@code applicatino/prs.hal-forms+json}.
*/
public static final MediaType HAL_FORMS_JSON = MediaType.parseMediaType(HAL_FORMS_JSON_VALUE);

}
3 changes: 3 additions & 0 deletions src/main/java/org/springframework/hateoas/alps/Alps.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import org.springframework.hateoas.alps.Doc.DocBuilder;
import org.springframework.hateoas.alps.Ext.ExtBuilder;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;

/**
* An ALPS document.
*
Expand All @@ -34,6 +36,7 @@
*/
@Value
@Builder(builderMethodName = "alps")
@JsonPropertyOrder({"version", "doc", "descriptors"})
public class Alps {

private final String version = "1.0";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,25 @@
*/
package org.springframework.hateoas.config;

import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.hal.forms.HalFormsWebMvcConfigurer;

/**
* Activates hypermedia support in the {@link ApplicationContext}. Will register infrastructure beans available for
Expand All @@ -39,11 +48,13 @@
* @see LinkDiscoverer
* @see EntityLinks
* @author Oliver Gierke
* @author Greg Turnquist
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ HypermediaSupportBeanDefinitionRegistrar.class, HateoasConfiguration.class })
@Import({ HypermediaSupportBeanDefinitionRegistrar.class, HateoasConfiguration.class,
EnableHypermediaSupport.HypermediaConfigurationImportSelector.class })
public @interface EnableHypermediaSupport {

/**
Expand All @@ -57,6 +68,7 @@
* Hypermedia representation types supported.
*
* @author Oliver Gierke
* @author Greg Turnquist
*/
enum HypermediaType {

Expand All @@ -66,6 +78,51 @@ enum HypermediaType {
* @see http://stateless.co/hal_specification.html
* @see http://tools.ietf.org/html/draft-kelly-json-hal-05
*/
HAL;
HAL,

/**
* HAL-FORMS - Independent, backward-compatible extension of the HAL designed to add runtime FORM support
*
* @see https://rwcbook.github.io/hal-forms/
*/
HAL_FORMS(HalFormsWebMvcConfigurer.class);

private final List<Class<?>> configurations;

HypermediaType(Class<?>... configurations) {
this.configurations = Arrays.asList(configurations);
}
}

@Slf4j
class HypermediaConfigurationImportSelector implements ImportSelector {

@Override
public String[] selectImports(AnnotationMetadata metadata) {

Map<String, Object> attributes = metadata.getAnnotationAttributes(EnableHypermediaSupport.class.getName());

HypermediaType[] types = (HypermediaType[]) attributes.get("type");

/**
* If no types are defined inside the annotation, add them all.
*/
if (types.length == 0) {
types = HypermediaType.values();
}

LOG.debug("Registering support for hypermedia types {} according to configuration on {}", types,
metadata.getClassName());

List<String> configurationNames = new ArrayList<String>();

for (HypermediaType type : types) {
for (Class<?> configuration : type.configurations) {
configurationNames.add(configuration.getName());
}
}

return configurationNames.toArray(new String[0]);
}
}
}
Loading