DATAREST-638 - Consolidated metadata under a single profile link.
Moved /alps into a separate link underneath /profile, so that /schema can ALSO be served there as well. Also added a profile link to the collection resources, linking to collection-specific metadata. We now use strict content negotiation for each form of metadata so clients don't have to navigate a labyrinth of links. To preserve backwards compatibility, make ALPS the default metadata type. Original pull request: #196.
This commit is contained in:
committed by
Oliver Gierke
parent
5f7f912e69
commit
345c198a75
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2015 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.data.rest.webmvc;
|
||||
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.*;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.mapping.RepositoryResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
* Profile-based controller exposing multiple forms of metadata.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @see DATAREST-638
|
||||
* @since 2.4
|
||||
*/
|
||||
@BasePathAwareController
|
||||
public class ProfileController {
|
||||
|
||||
public static final String PROFILE_ROOT_MAPPING = "/profile";
|
||||
public static final String RESOURCE_PROFILE_MAPPING = PROFILE_ROOT_MAPPING + "/{repository}";
|
||||
|
||||
private final RepositoryRestConfiguration configuration;
|
||||
private final RepositoryResourceMappings mappings;
|
||||
private final Repositories repositories;
|
||||
|
||||
/**
|
||||
* Wire up the controller with a copy of {@link RepositoryRestConfiguration}.
|
||||
*
|
||||
* @param configuration must not be {@literal null}.
|
||||
* @param mappings must not be {@literal null}.
|
||||
* @param repositories must not be {@literal null}.
|
||||
*/
|
||||
@Autowired
|
||||
public ProfileController(RepositoryRestConfiguration configuration, RepositoryResourceMappings mappings,
|
||||
Repositories repositories) {
|
||||
|
||||
Assert.notNull(configuration, "RepositoryRestConfiguration must not be null!");
|
||||
Assert.notNull(mappings, "RepositoryResourceMappings must not be null!");
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
|
||||
this.configuration = configuration;
|
||||
this.mappings = mappings;
|
||||
this.repositories = repositories;
|
||||
}
|
||||
|
||||
/**
|
||||
* List the OPTIONS for this controller.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(value = PROFILE_ROOT_MAPPING, method = RequestMethod.OPTIONS)
|
||||
public HttpEntity<?> profileOptions() {
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAllow(Collections.singleton(HttpMethod.GET));
|
||||
|
||||
return new ResponseEntity<Object>(headers, HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* List a profile link for each exported repository.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(value = PROFILE_ROOT_MAPPING, method = GET)
|
||||
HttpEntity<ResourceSupport> listAllFormsOfMetadata() {
|
||||
|
||||
ResourceSupport profile = new ResourceSupport();
|
||||
|
||||
profile.add(new Link(getRootPath(this.configuration)).withSelfRel());
|
||||
|
||||
for (Class<?> domainType : this.repositories) {
|
||||
|
||||
ResourceMetadata mapping = this.mappings.getMetadataFor(domainType);
|
||||
|
||||
if (mapping.isExported()) {
|
||||
profile.add(new Link(getPath(this.configuration, mapping), mapping.getRel()));
|
||||
}
|
||||
}
|
||||
|
||||
return new ResponseEntity<ResourceSupport>(profile, HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return href for the profile root link of a given baseUri.
|
||||
*
|
||||
* @param configuration is the source of the app's baseUri.
|
||||
* @return
|
||||
*/
|
||||
public static String getRootPath(RepositoryRestConfiguration configuration) {
|
||||
|
||||
BaseUri baseUri = new BaseUri(configuration.getBaseUri());
|
||||
return baseUri.getUriComponentsBuilder().path(ProfileController.PROFILE_ROOT_MAPPING).build().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return href for the profile link of a given baseUri and domain type mapping.
|
||||
*
|
||||
* @param configuration is the source of the app's baseUri.
|
||||
* @param mapping provides the resource's path.
|
||||
* @return
|
||||
*/
|
||||
public static String getPath(RepositoryRestConfiguration configuration, ResourceMapping mapping) {
|
||||
|
||||
if (mapping == null) {
|
||||
return getRootPath(configuration);
|
||||
} else {
|
||||
return getRootPath(configuration) + mapping.getPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2015 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.
|
||||
@@ -13,55 +13,50 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc.alps;
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.webmvc.BaseUri;
|
||||
import org.springframework.data.rest.webmvc.RepositoryLinksResource;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.ResourceProcessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ResourceProcessor} to add a {@code profile} link to the root resource to point to the ALPS resources in case
|
||||
* the support for ALPS is activated.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* {@link ResourceProcessor} to add a {@code profile} link to the root resource to point to multiple forms of metadata.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @see DATAREST-638
|
||||
* @since 2.4
|
||||
*/
|
||||
public class AlpsResourceProcessor implements ResourceProcessor<RepositoryLinksResource> {
|
||||
public class ProfileResourceProcessor implements ResourceProcessor<RepositoryLinksResource> {
|
||||
|
||||
private static final String PROFILE_REL = "profile";
|
||||
static final String PROFILE_REL = "profile";
|
||||
|
||||
private final RepositoryRestConfiguration configuration;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AlpsResourceProcessor} with the given {@link RepositoryRestConfiguration}.
|
||||
*
|
||||
* Creates a new {@link ProfileResourceProcessor} with the given {@link RepositoryRestConfiguration}.
|
||||
*
|
||||
* @param configuration must not be {@literal null}.
|
||||
*/
|
||||
@Autowired
|
||||
public AlpsResourceProcessor(RepositoryRestConfiguration configuration) {
|
||||
public ProfileResourceProcessor(RepositoryRestConfiguration configuration) {
|
||||
|
||||
Assert.notNull(configuration, "RepositoryRestConfiguration must not be null!");
|
||||
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.ResourceProcessor#process(org.springframework.hateoas.ResourceSupport)
|
||||
/**
|
||||
* Add a link to the {@link ProfileController}'s base URI to the app's root URI.
|
||||
*
|
||||
* @param resource
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public RepositoryLinksResource process(RepositoryLinksResource resource) {
|
||||
|
||||
if (configuration.metadataConfiguration().alpsEnabled()) {
|
||||
|
||||
BaseUri baseUri = new BaseUri(configuration.getBaseUri());
|
||||
String href = baseUri.getUriComponentsBuilder().path(AlpsController.ALPS_ROOT_MAPPING).build().toString();
|
||||
|
||||
resource.add(new Link(href, PROFILE_REL));
|
||||
}
|
||||
resource.add(new Link(ProfileController.getRootPath(this.configuration), PROFILE_REL));
|
||||
|
||||
return resource;
|
||||
}
|
||||
@@ -202,6 +202,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
Link baseLink = entityLinks.linkToPagedResource(resourceInformation.getDomainType(), pageable.isDefault() ? null
|
||||
: pageable.getPageable());
|
||||
|
||||
links.add(new Link(ProfileController.getPath(this.config, metadata), ProfileResourceProcessor.PROFILE_REL));
|
||||
|
||||
Resources<?> result = toResources(results, assembler, metadata.getDomainType(), baseLink);
|
||||
result.add(links);
|
||||
return result;
|
||||
|
||||
@@ -31,12 +31,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
* @author Greg Turnquist
|
||||
* @see http://json-schema.org/
|
||||
*/
|
||||
@RepositoryRestController
|
||||
@BasePathAwareController
|
||||
class RepositorySchemaController {
|
||||
|
||||
private static final String BASE_MAPPING = "/{repository}";
|
||||
|
||||
private final PersistentEntityToJsonSchemaConverter jsonSchemaConverter;
|
||||
|
||||
/**
|
||||
@@ -48,6 +48,7 @@ class RepositorySchemaController {
|
||||
public RepositorySchemaController(PersistentEntityToJsonSchemaConverter jsonSchemaConverter) {
|
||||
|
||||
Assert.notNull(jsonSchemaConverter, "PersistentEntityToJsonSchemaConverter must not be null!");
|
||||
|
||||
this.jsonSchemaConverter = jsonSchemaConverter;
|
||||
}
|
||||
|
||||
@@ -57,7 +58,8 @@ class RepositorySchemaController {
|
||||
* @param resourceInformation will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(value = BASE_MAPPING + "/schema", method = GET)
|
||||
@RequestMapping(value = ProfileController.RESOURCE_PROFILE_MAPPING, method = GET,
|
||||
produces = RestMediaTypes.SCHEMA_JSON_VALUE)
|
||||
public HttpEntity<JsonSchema> schema(RootResourceInformation resourceInformation) {
|
||||
|
||||
JsonSchema schema = jsonSchemaConverter.convert(resourceInformation.getDomainType());
|
||||
|
||||
@@ -22,21 +22,27 @@ import org.springframework.http.MediaType;
|
||||
* Constants to refer to supported media types.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class RestMediaTypes {
|
||||
|
||||
public static final String SPRING_DATA_COMPACT_JSON_VALUE = "application/x-spring-data-compact+json";
|
||||
public static final String TEXT_URI_LIST_VALUE = "text/uri-list";
|
||||
|
||||
public static final MediaType HAL_JSON = MediaTypes.HAL_JSON;
|
||||
|
||||
public static final MediaType JSON_PATCH_JSON = MediaType.valueOf("application/json-patch+json");
|
||||
public static final MediaType MERGE_PATCH_JSON = MediaType.valueOf("application/merge-patch+json");
|
||||
|
||||
public static final MediaType SCHEMA_JSON = MediaType.valueOf("application/schema+json");
|
||||
public static final String ALPS_JSON_VALUE = "application/alps+json";
|
||||
public static final MediaType ALPS_JSON = MediaType.parseMediaType(ALPS_JSON_VALUE);
|
||||
|
||||
public static final String SCHEMA_JSON_VALUE = "application/schema+json";
|
||||
public static final MediaType SCHEMA_JSON = MediaType.valueOf(SCHEMA_JSON_VALUE);
|
||||
|
||||
public static final MediaType SPRING_DATA_VERBOSE_JSON = MediaType.valueOf("application/x-spring-data-verbose+json");
|
||||
|
||||
public static final String SPRING_DATA_COMPACT_JSON_VALUE = "application/x-spring-data-compact+json";
|
||||
public static final MediaType SPRING_DATA_COMPACT_JSON = MediaType.valueOf(SPRING_DATA_COMPACT_JSON_VALUE);
|
||||
|
||||
public static final String TEXT_URI_LIST_VALUE = "text/uri-list";
|
||||
public static final MediaType TEXT_URI_LIST = MediaType.valueOf(TEXT_URI_LIST_VALUE);
|
||||
}
|
||||
|
||||
@@ -17,43 +17,37 @@ package org.springframework.data.rest.webmvc.alps;
|
||||
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.webmvc.BasePathAwareController;
|
||||
import org.springframework.data.rest.webmvc.BaseUri;
|
||||
import org.springframework.data.rest.webmvc.ProfileController;
|
||||
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
|
||||
import org.springframework.data.rest.webmvc.RestMediaTypes;
|
||||
import org.springframework.data.rest.webmvc.RootResourceInformation;
|
||||
import org.springframework.hateoas.alps.Alps;
|
||||
import org.springframework.hateoas.alps.Descriptor;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Controller exposing semantic documentation for the resources exposed using the Application Level Profile Semantics
|
||||
* format.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Greg Turnquist
|
||||
* @see http://alps.io
|
||||
*/
|
||||
@BasePathAwareController
|
||||
public class AlpsController {
|
||||
|
||||
static final String ALPS_ROOT_MAPPING = "/alps";
|
||||
static final String ALPS_RESOURCE_MAPPING = ALPS_ROOT_MAPPING + "/{repository}";
|
||||
|
||||
private final Repositories repositories;
|
||||
private final ResourceMappings mappings;
|
||||
private final RepositoryRestConfiguration configuration;
|
||||
@@ -83,7 +77,7 @@ public class AlpsController {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(value = { ALPS_ROOT_MAPPING, ALPS_RESOURCE_MAPPING }, method = OPTIONS)
|
||||
@RequestMapping(value = ProfileController.RESOURCE_PROFILE_MAPPING, method = OPTIONS, produces = RestMediaTypes.ALPS_JSON_VALUE)
|
||||
HttpEntity<?> alpsOptions() {
|
||||
|
||||
verifyAlpsEnabled();
|
||||
@@ -94,45 +88,14 @@ public class AlpsController {
|
||||
return new ResponseEntity<Object>(headers, HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes a resource to contain descriptors pointing to the discriptors for individual resources.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(value = ALPS_ROOT_MAPPING, method = GET)
|
||||
HttpEntity<Alps> alps() {
|
||||
|
||||
verifyAlpsEnabled();
|
||||
|
||||
List<Descriptor> descriptors = new ArrayList<Descriptor>();
|
||||
|
||||
for (Class<?> domainType : repositories) {
|
||||
|
||||
ResourceMetadata mapping = mappings.getMetadataFor(domainType);
|
||||
|
||||
if (mapping.isExported()) {
|
||||
|
||||
BaseUri baseUri = new BaseUri(configuration.getBaseUri());
|
||||
UriComponentsBuilder builder = baseUri.getUriComponentsBuilder().path(ALPS_ROOT_MAPPING);
|
||||
String href = builder.path(mapping.getPath().toString()).build().toUriString();
|
||||
descriptors.add(Alps.descriptor().name(mapping.getRel()).href(href).build());
|
||||
}
|
||||
}
|
||||
|
||||
Alps alps = Alps.alps().//
|
||||
descriptors(descriptors).//
|
||||
build();
|
||||
|
||||
return new ResponseEntity<Alps>(alps, HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes an ALPS resource to describe an individual repository resource.
|
||||
*
|
||||
* @param information
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(value = ALPS_RESOURCE_MAPPING, method = GET)
|
||||
@RequestMapping(value = ProfileController.RESOURCE_PROFILE_MAPPING, method = GET,
|
||||
produces = { MediaType.ALL_VALUE, RestMediaTypes.ALPS_JSON_VALUE })
|
||||
HttpEntity<RootResourceInformation> descriptor(RootResourceInformation information) {
|
||||
|
||||
verifyAlpsEnabled();
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.rest.webmvc.RestMediaTypes;
|
||||
import org.springframework.data.rest.webmvc.RootResourceInformation;
|
||||
import org.springframework.hateoas.alps.Alps;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -42,8 +43,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
public class AlpsJsonHttpMessageConverter extends MappingJackson2HttpMessageConverter
|
||||
implements ResponseBodyAdvice<Object> {
|
||||
|
||||
private static final MediaType ALPS_MEDIA_TYPE = MediaType.parseMediaType("application/alps+json");
|
||||
|
||||
private final RootResourceInformationToAlpsDescriptorConverter converter;
|
||||
|
||||
/**
|
||||
@@ -61,7 +60,7 @@ public class AlpsJsonHttpMessageConverter extends MappingJackson2HttpMessageConv
|
||||
mapper.setSerializationInclusion(Include.NON_EMPTY);
|
||||
|
||||
setPrettyPrint(true);
|
||||
setSupportedMediaTypes(Arrays.asList(ALPS_MEDIA_TYPE, MediaType.APPLICATION_JSON, MediaType.ALL));
|
||||
setSupportedMediaTypes(Arrays.asList(RestMediaTypes.ALPS_JSON, MediaType.APPLICATION_JSON, MediaType.ALL));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.core.mapping.ResourceType;
|
||||
import org.springframework.data.rest.core.mapping.SimpleResourceDescription;
|
||||
import org.springframework.data.rest.core.mapping.SupportedHttpMethods;
|
||||
import org.springframework.data.rest.webmvc.ProfileController;
|
||||
import org.springframework.data.rest.webmvc.RootResourceInformation;
|
||||
import org.springframework.data.rest.webmvc.json.JacksonMetadata;
|
||||
import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
|
||||
@@ -60,7 +61,6 @@ import org.springframework.hateoas.alps.Descriptor.DescriptorBuilder;
|
||||
import org.springframework.hateoas.alps.Doc;
|
||||
import org.springframework.hateoas.alps.Format;
|
||||
import org.springframework.hateoas.alps.Type;
|
||||
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -147,9 +147,11 @@ public class RootResourceInformationToAlpsDescriptorConverter {
|
||||
|
||||
ResourceMetadata metadata = mappings.getMetadataFor(type);
|
||||
|
||||
String href = ProfileController.getPath(this.configuration, metadata);
|
||||
|
||||
return descriptor().//
|
||||
id(getRepresentationDescriptorId(metadata)).//
|
||||
href(entityLinks.linkFor(type).slash("schema").toString()).//
|
||||
href(href).//
|
||||
doc(getDocFor(metadata.getItemResourceDescription())).//
|
||||
descriptors(buildPropertyDescriptors(type, metadata.getItemResourceRel())).//
|
||||
build();
|
||||
@@ -178,7 +180,6 @@ public class RootResourceInformationToAlpsDescriptorConverter {
|
||||
* Builds a descriptor for the projection parameter of the given resource.
|
||||
*
|
||||
* @param metadata
|
||||
* @param projectionConfiguration
|
||||
* @return
|
||||
*/
|
||||
private Descriptor buildProjectionDescriptor(ResourceMetadata metadata) {
|
||||
@@ -353,10 +354,11 @@ public class RootResourceInformationToAlpsDescriptorConverter {
|
||||
name(mapping.getRel()).doc(getDocFor(mapping.getDescription()));
|
||||
|
||||
ResourceMetadata targetTypeMetadata = mappings.getMetadataFor(property.getActualType());
|
||||
String localPath = targetTypeMetadata.getRel().concat("#")
|
||||
.concat(getRepresentationDescriptorId(targetTypeMetadata));
|
||||
Link link = ControllerLinkBuilder.linkTo(AlpsController.class).slash(AlpsController.ALPS_ROOT_MAPPING)
|
||||
.slash(localPath).withSelfRel();
|
||||
|
||||
String href = ProfileController.getPath(configuration, targetTypeMetadata) +
|
||||
"#" + getRepresentationDescriptorId(targetTypeMetadata);
|
||||
|
||||
Link link = new Link(href).withSelfRel();
|
||||
|
||||
builder.//
|
||||
type(Type.SAFE).//
|
||||
|
||||
@@ -73,6 +73,7 @@ import org.springframework.data.rest.core.support.UnwrappingRepositoryInvokerFac
|
||||
import org.springframework.data.rest.webmvc.BasePathAwareController;
|
||||
import org.springframework.data.rest.webmvc.BasePathAwareHandlerMapping;
|
||||
import org.springframework.data.rest.webmvc.BaseUri;
|
||||
import org.springframework.data.rest.webmvc.ProfileResourceProcessor;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestController;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestExceptionHandler;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter;
|
||||
@@ -80,7 +81,6 @@ import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping;
|
||||
import org.springframework.data.rest.webmvc.RestMediaTypes;
|
||||
import org.springframework.data.rest.webmvc.ServerHttpRequestMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter;
|
||||
import org.springframework.data.rest.webmvc.alps.AlpsResourceProcessor;
|
||||
import org.springframework.data.rest.webmvc.alps.RootResourceInformationToAlpsDescriptorConverter;
|
||||
import org.springframework.data.rest.webmvc.convert.UriListHttpMessageConverter;
|
||||
import org.springframework.data.rest.webmvc.json.DomainObjectReader;
|
||||
@@ -143,6 +143,7 @@ import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Jon Brisbin
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@Configuration
|
||||
@EnableHypermediaSupport(type = HypermediaType.HAL)
|
||||
@@ -769,8 +770,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AlpsResourceProcessor alpsResourceProcessor() {
|
||||
return new AlpsResourceProcessor(config());
|
||||
public ProfileResourceProcessor profileResourceProcessor(RepositoryRestConfiguration config) {
|
||||
return new ProfileResourceProcessor(config);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.Map;
|
||||
import net.minidev.json.JSONArray;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -64,6 +65,7 @@ public abstract class CommonWebTests extends AbstractWebIntegrationTests {
|
||||
|
||||
/**
|
||||
* @see DATAREST-113
|
||||
* @see DATAREST-638
|
||||
*/
|
||||
@Test
|
||||
public void exposesSchemasForResourcesExposed() throws Exception {
|
||||
@@ -75,12 +77,18 @@ public abstract class CommonWebTests extends AbstractWebIntegrationTests {
|
||||
Link link = client.assertHasLinkWithRel(rel, response);
|
||||
|
||||
// Resource
|
||||
client.request(link);
|
||||
client.follow(link).andExpect(status().is2xxSuccessful());
|
||||
|
||||
// Schema - TODO:Improve by using hypermedia
|
||||
mvc.perform(get(link.expand().getHref() + "/schema").//
|
||||
accept(MediaType.parseMediaType("application/schema+json"))).//
|
||||
andExpect(status().isOk());
|
||||
Link profileLink = client.discoverUnique(link, "profile");
|
||||
|
||||
// Default metadata
|
||||
client.follow(profileLink).andExpect(status().is2xxSuccessful());
|
||||
|
||||
// JSON Schema
|
||||
client.follow(profileLink, RestMediaTypes.SCHEMA_JSON).andExpect(status().is2xxSuccessful());
|
||||
|
||||
// ALPS
|
||||
client.follow(profileLink, RestMediaTypes.ALPS_JSON).andExpect(status().is2xxSuccessful());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2015 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.data.rest.webmvc;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
|
||||
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.LinkDiscoverers;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
/**
|
||||
* Series of tests to verify {@link ProfileController} serves ALPS and JSON Schema metadata from the root level and
|
||||
* at collection resource levels.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 2.4
|
||||
*/
|
||||
@WebAppConfiguration
|
||||
@ContextConfiguration(classes = { JpaRepositoryConfig.class, ProfileIntegrationTests.Config.class})
|
||||
public class ProfileIntegrationTests extends AbstractControllerIntegrationTests {
|
||||
|
||||
@Autowired WebApplicationContext context;
|
||||
@Autowired LinkDiscoverers discoverers;
|
||||
|
||||
private static final String ROOT_URI = "/api";
|
||||
|
||||
@Configuration
|
||||
static class Config extends RepositoryRestConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
|
||||
config.setBasePath(ROOT_URI);
|
||||
}
|
||||
}
|
||||
|
||||
TestMvcClient client;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build();
|
||||
this.client = new TestMvcClient(mvc, this.discoverers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-230
|
||||
* @see DATAREST-638
|
||||
*/
|
||||
@Test
|
||||
public void exposesProfileLink() throws Exception {
|
||||
|
||||
client.follow(ROOT_URI)//
|
||||
.andExpect(status().is2xxSuccessful())//
|
||||
.andExpect(jsonPath("$._links.profile.href", endsWith(ProfileController.PROFILE_ROOT_MAPPING)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-230
|
||||
* @see DATAREST-638
|
||||
*/
|
||||
@Test
|
||||
public void profileRootLinkContainsMetadataForEachRepo() throws Exception {
|
||||
|
||||
Link profileLink = client.discoverUnique(new Link(ROOT_URI), ProfileResourceProcessor.PROFILE_REL);
|
||||
|
||||
assertThat(client.discoverUnique(profileLink, "self", MediaType.ALL), is(notNullValue()));
|
||||
assertThat(client.discoverUnique(profileLink, "people", MediaType.ALL), is(notNullValue()));
|
||||
assertThat(client.discoverUnique(profileLink, "items", MediaType.ALL), is(notNullValue()));
|
||||
assertThat(client.discoverUnique(profileLink, "authors", MediaType.ALL), is(notNullValue()));
|
||||
assertThat(client.discoverUnique(profileLink, "books", MediaType.ALL), is(notNullValue()));
|
||||
assertThat(client.discoverUnique(profileLink, "orders", MediaType.ALL), is(notNullValue()));
|
||||
assertThat(client.discoverUnique(profileLink, "receipts", MediaType.ALL), is(notNullValue()));
|
||||
assertThat(client.discoverUnique(profileLink, "addresses", MediaType.ALL), is(notNullValue()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @see DATAREST-638
|
||||
*/
|
||||
@Test
|
||||
public void profileLinkOnCollectionResourceLeadsToRepositorySpecificMetadata() throws Exception {
|
||||
|
||||
Link peopleLink = client.discoverUnique(new Link(ROOT_URI), "people");
|
||||
Link profileLink = client.discoverUnique(peopleLink, ProfileResourceProcessor.PROFILE_REL);
|
||||
|
||||
client.follow(profileLink, RestMediaTypes.ALPS_JSON)
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, RestMediaTypes.ALPS_JSON_VALUE));
|
||||
|
||||
client.follow(profileLink, RestMediaTypes.SCHEMA_JSON)
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, RestMediaTypes.SCHEMA_JSON_VALUE));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -174,7 +174,30 @@ public class TestMvcClient {
|
||||
* @throws Exception
|
||||
*/
|
||||
public ResultActions follow(String href) throws Exception {
|
||||
return mvc.perform(get(href));
|
||||
return follow(href, MediaType.ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Folow Link with a specific Accept header (media type).
|
||||
*
|
||||
* @param link
|
||||
* @param accept
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public ResultActions follow(Link link, MediaType accept) throws Exception {
|
||||
return follow(link.expand().getHref(), accept);
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow URL supplied as a string with a specific Accept header.
|
||||
* @param href
|
||||
* @param accept
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public ResultActions follow(String href, MediaType accept) throws Exception {
|
||||
return mvc.perform(get(href).header(HttpHeaders.ACCEPT, accept.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,15 +17,19 @@ package org.springframework.data.rest.webmvc.alps;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.webmvc.AbstractControllerIntegrationTests;
|
||||
import org.springframework.data.rest.webmvc.ProfileController;
|
||||
import org.springframework.data.rest.webmvc.RestMediaTypes;
|
||||
import org.springframework.data.rest.webmvc.TestMvcClient;
|
||||
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
|
||||
import org.springframework.data.rest.webmvc.jpa.Item;
|
||||
@@ -82,31 +86,21 @@ public class AlpsControllerIntegrationTests extends AbstractControllerIntegratio
|
||||
* @see DATAREST-230
|
||||
*/
|
||||
@Test
|
||||
public void exposesProfileLink() throws Exception {
|
||||
|
||||
client.follow("/")//
|
||||
.andExpect(status().is2xxSuccessful())//
|
||||
.andExpect(jsonPath("$._links.profile.href", endsWith(AlpsController.ALPS_ROOT_MAPPING)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-230
|
||||
*/
|
||||
@Test
|
||||
public void alpsResourceExposesResourcePerCollectionResource() throws Exception {
|
||||
public void exposesAlpsCollectionResources() throws Exception {
|
||||
|
||||
Link profileLink = client.discoverUnique("profile");
|
||||
Link peopleLink = client.discoverUnique(profileLink, "people", MediaType.ALL);
|
||||
|
||||
assertThat(client.discoverUnique(profileLink, "orders", MediaType.ALL), is(notNullValue()));
|
||||
assertThat(client.discoverUnique(profileLink, "people", MediaType.ALL), is(notNullValue()));
|
||||
assertThat(client.discoverUnique(profileLink, "items", MediaType.ALL), is(notNullValue()));
|
||||
client.follow(peopleLink, RestMediaTypes.ALPS_JSON)//
|
||||
.andExpect(jsonPath("$.version").value("1.0"))//
|
||||
.andExpect(jsonPath("$.descriptors[*].name", hasItems("people", "person")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-230
|
||||
* @see DATAREST-638
|
||||
*/
|
||||
@Test
|
||||
public void exposesAlpsCollectionResources() throws Exception {
|
||||
public void verifyThatAlpsIsDefaultProfileFormat() throws Exception {
|
||||
|
||||
Link profileLink = client.discoverUnique("profile");
|
||||
Link peopleLink = client.discoverUnique(profileLink, "people", MediaType.ALL);
|
||||
@@ -114,6 +108,7 @@ public class AlpsControllerIntegrationTests extends AbstractControllerIntegratio
|
||||
client.follow(peopleLink)//
|
||||
.andExpect(jsonPath("$.version").value("1.0"))//
|
||||
.andExpect(jsonPath("$.descriptors[*].name", hasItems("people", "person")));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +120,7 @@ public class AlpsControllerIntegrationTests extends AbstractControllerIntegratio
|
||||
Link profileLink = client.discoverUnique("profile");
|
||||
Link itemsLink = client.discoverUnique(profileLink, "items", MediaType.ALL);
|
||||
|
||||
client.follow(itemsLink)//
|
||||
client.follow(itemsLink, RestMediaTypes.ALPS_JSON)//
|
||||
// Exposes standard property
|
||||
.andExpect(jsonPath("$.descriptors[*].descriptors[*].name", hasItems("name")))
|
||||
// Does not expose explicitly @JsonIgnored property
|
||||
@@ -145,8 +140,8 @@ public class AlpsControllerIntegrationTests extends AbstractControllerIntegratio
|
||||
|
||||
assertThat(itemsLink, is(notNullValue()));
|
||||
|
||||
client.follow(itemsLink)//
|
||||
.andExpect(jsonPath("$.descriptors[?(@.id == 'item-representation')].href", is(notNullValue())));
|
||||
client.follow(itemsLink, RestMediaTypes.ALPS_JSON)//
|
||||
.andExpect(jsonPath("$.descriptors[?(@.id == 'item-representation')][0].href", endsWith("/profile/items")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,8 +158,9 @@ public class AlpsControllerIntegrationTests extends AbstractControllerIntegratio
|
||||
jsonPath += "descriptors[?(@.name == 'father')][0]."; // First father descriptor
|
||||
jsonPath += "rt"; // Return type
|
||||
|
||||
client.follow(usersLink)//
|
||||
.andExpect(jsonPath(jsonPath, allOf(containsString("alps"), endsWith("-representation"))));
|
||||
client.follow(usersLink, RestMediaTypes.ALPS_JSON)//
|
||||
.andExpect(jsonPath(jsonPath,
|
||||
allOf(containsString(ProfileController.PROFILE_ROOT_MAPPING), endsWith("-representation"))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,7 +172,7 @@ public class AlpsControllerIntegrationTests extends AbstractControllerIntegratio
|
||||
Link profileLink = client.discoverUnique("profile");
|
||||
Link itemsLink = client.discoverUnique(profileLink, "items", MediaType.ALL);
|
||||
|
||||
client.follow(itemsLink)//
|
||||
client.follow(itemsLink, RestMediaTypes.ALPS_JSON)//
|
||||
// Exposes identifier if configured to
|
||||
.andExpect(jsonPath("$.descriptors[*].descriptors[*].name", hasItems("id", "name")));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user